原文鏈接已經打不開:http://blog.cocoabit.com/blog/2013/10/31/guan-yu-uitableview-zhong-cell-zi-gua-ying-gao-du-de-wen-ti/?utm_source=tuicool&utm_medium=referral
最近在做一個app,內容主要是 一個 table view 的 cell 中有一張寬度一定高度不一定的圖片和不一樣高度的文字。每次從服務器樓數據會返回圖片的 URL 地址和文字等內容,但只有圖片下載完成后才能知道圖片的大小。文字可以通過一些 API 計算固定寬度后的高度,但是圖片在不下載完成后是不知道圖片的寬高的。我們“很硬”的后台沒有提供相關的接口。因為項目比較緊,兩個星期完成一個 APP。。。。。兩個星期,尼瑪我一個人碼了小 4w 行的代碼,一個人。。。。
好了言歸正傳,交代了背景,再介紹下我遇到的問題:
1、在不知道圖片寬高的情況下如何動態適配 cell 中的高度。
2、在我重寫 cell 時因為在 cell 底部有可以點擊的button,但是突然在某一天無法點擊了,找了半天也沒有找到原因。
對於第一個問題,首先先介紹下 Table view 的加載流程,主要的就 3 步:
1、通過代理獲得一共有多少個 cell
2、通過代理獲得每個 cell 的高度
3、通過代理填充當前顯示的 cell 中的內容。
table view 會在 layoutSubviews 中做這三件事,而 layoutSubviews 方法除了在 frame 改變后調用還會在 scroll view 滾動時調用, 每次滾動時 !
一開始的做法比較糙,直接用 SDWebImage 的異步加載圖片的方法,在圖片加載完后圖片的高度變了,cell 非常惡心。。。腦補下吧。 方法直接 pass 了。
然后我改進了做法,准備一個 dictionary 記錄每個 cell 的高度(為什么不用 array 后面再談), 在 configure cell 時下載圖片,然后在下載完成的回調里記錄下當前行的行高,然后 reload table cell at indexPaths …., 這樣做的后果是樣式比上一種方法好點了,但是在某一刻,它居然崩潰了。。。。。。刷的太快了。。。。
最后參考蘋果的示例程序 LazyTable 實現了現在很滿意的效果。以下關鍵數據和名稱已替換,但不影響閱讀。#pragma mark - Table view datasource and delega- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{ return [aArray count]; // aArray 為一個數組,保留每個 cell 對應的 model } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { // 先從緩存中查找圖片 UIImage *img = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey: imgKey ]; // 沒有找到已下載的圖片就使用默認的占位圖,當然高度也是默認的高度了,除了高度不固定的文字部分。 if (!img) { img = CachedImage( placeholder name ); } CGFloat height = [MyCustomCell heightWithImage:img text:text]; // 根據傳入的動態內容計算 cell 的高度 return height; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *identifier = @"Cell Identifier"; MyCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; // 如果沒有可以復用的 cell 就新建一個 if (!cell) { cell = [[MyCustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier]; } // 配置 cell [self configureCell:cell atIndexPath:indexPath]; return cell; } - (void)configureCell:(MyCustomCell *)cell atIndexPath:(NSIndexPath *)indexPath { // 正常的設置文字和其他內容 // 開始加載圖片 NSString *imgURL = [self.aArray[indexPath.row] valueForKey: imgKey ]; // 圖片 url UIImage *cachedImage = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:imgURL]; // 沒有已經下載好的圖片 if ( !cachedImage ) { // 要是當前 table view 沒有在被拖動或者自由滑行 if ( !self.tableView.dragging && !self.tableView.decelerating ) { // 下載當前 cell 中的圖片 [self downloadImage:imgURL forIndexPath:indexPath]; } // cell 中圖片先用緩存的占位圖代替 [cell setSpitslotImage:CachedImage(@"placeholder")]; } else { // 找到了緩存的圖片,直接插緩存的圖片 [cell setSpitslotImage:cachedImage]; } } - (void)downloadImage:(NSString *)imageURL forIndexPath:(NSIndexPath *)indexPath { __weak typeof(self) target = self; // 利用 SDWebImage 框架提供的功能下載圖片 [[SDWebImageDownloader sharedDownloader] downloadImageWithURL:URLFromString(imageURL) options:SDWebImageDownloaderUseNSURLCache progress:^(NSUInteger receivedSize,
NSUInteger
expectedSize) { // Do nothing } completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) { // 保存圖片 [[SDImageCache sharedImageCache] storeImage:image forKey:imageURL toDisk:YES]; // 別忘了保存到磁盤上 // 延遲在主線程更新 cell 的高度 [target performSelectorOnMainThread:@selector(reloadCellAtIndexPath:) // 至於這步的原因是為了防止刷的太快了崩潰 withObject:indexPath waitUntilDone:NO]; // 這步會把 selector 的調用放到主線程的 run loop 里排隊 }]; // 風險就是要是這個隊列里排隊的比較多, // 這個后插入的方法執行會被延遲 } // 這個方法沒什么好解釋的了 - (void)loadImageForOnScreenRows { NSArray *visiableIndexPathes = [self.tableView indexPathsForVisibleRows]; for(NSInteger i = 0; i < [visiableIndexPathes count]; i++) { NSString *imgURL = [self.aArray[i] valueForKey: imgKey ]; [self downloadImage:imgURL forIndexPath:visiableIndexPathes[i]]; } } // 方法名為什么叫這個。。。。是我太懶了,之前寫的是刷新某一行的數據,然后發現效率不高,還容易崩潰 // 最后直接刷新全部數據了。。 // 但是別認為調用 table view 的 reloadData 效率就不高,回顧下上面的步驟 // reloadData 實際上就是向代理要行數,要行高,對**顯示出來**的 cell 填充數據 // 我的那份代碼的瓶頸就是 計算行高和填充數據,不過現在看來沒什么不流暢的,所以就沒什么問題 // 其實,為了性能考慮,還是最好服務器返回 每個 cell 中的圖片的寬高,現在的做法只是臨時解決后台xx的問題。。。。。。 - (void)reloadCellAtIndexPath:(NSIndexPath *)indexPath { [self.tableView reloadData];
///個人注釋
// 單行刷新
NSIndexPath *indexPath_1=[NSIndexPath indexPathForRow:1 inSection:0];
NSArray *indexArray=[NSArray arrayWithObject:indexPath_1];
[regTableView reloadRowsAtIndexPaths:indexArray withRowAnimation:UITableViewRowAnimationAutomatic];
} #pragma mark - Scroll view delegate //////////個人注釋//下面的方法可以不要,如果頁面不是統一cell的話 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { // table view 停止拖動了 if (!decelerate) { [self loadImageForOnScreenRows]; } } - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { // table view 停止滾動了 [self loadImageForOnScreenRows]; }
現在來解釋下為什么我保存 cell 高度用字典文件不用 array。 因為。。。。。。我懶。用 array 保存,每個沒有數據的項要填充默認數據,否則腦補下。而字典文件沒有物理上的順序性,可以腦補出一個邏輯上的順序性,通過自定義 key。
現在談談第二個問題: 自定義的 cell 中的的 button 都沒法點擊了,我重寫了 hit test 方法,強制返回那個 button,重寫了 touch began, touch end 強制向下一個響應者發送事件,沒用。。。。坑爹啊。最后我調整 button 的位置,原點移動到了 (0,0) 可以點擊了!!!! 給 contentView 補了個色,尼瑪怎么還是默認高度!!!!!!!原來在重寫 layoutSubviews 方法中我只顧着調整我自己的子 view 的位置了,忘了調整 content view。在方法開頭補上
[super layoutSubviews];
生活瞬間又美麗了!!!!
以后再遇到 cell 中 button 不能點擊后,記得查看那個 button 有沒有超出 它的父視圖的范圍,超出的范圍是無接收點擊事件的!!!切記!!!!!
