UITableView用得較多,遇到的狀況也較多,單獨記錄一篇。ios
1、零散的技巧數組
2、取cell緩存
3、cell高度安全
4、導航欄、TableView常見問題相關網絡
5、自定義左滑刪除按鈕圖片app
6、僅作了解異步
1、零散的技巧async
一、 cell的選中效果是cell的屬性,能夠有的有,無的無。字體
// 自定義cell self.selectionStyle = UITableViewCellSelectionStyleNone; // 取cell cell.selectionStyle = UITableViewCellSelectionStyleNone;
二、cell的下劃線是Table的屬性,所有有,或所有無。動畫
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
三、cell下劃線左邊頂住屏幕左邊。
cell.preservesSuperviewLayoutMargins = NO; cell.layoutMargins = UIEdgeInsetsZero; cell.separatorInset = UIEdgeInsetsZero;
後續補充:也能夠隱藏掉系統的下劃線,自定義LineView,要多寬就多寬,且能夠實現不一樣cell不一樣下劃線樣式。
四、cell的重用ID,能夠用類名
NSStringFromClass([MyCell class])
五、根據 indexPath 獲取 cell
[self.mTableView cellForRowAtIndexPath:indexPath];
六、根據 cell 獲取 indexPath
[self.mTableView indexPathForCell:cell];
七、superView
// 第一個superview 是contentView,第二個就cell UITableViewCell *cell = (UITableViewCell*)button.superview.superview;
八、UIScrollView 和 UITableView 的 內邊距差異
// 自動偏移 contentOffset = CGPointMake(0, -200); tableView.contentInset = UIEdgeInsetsMake(200, 0, 200, 0); // 須要設置偏移量。不然停留在偏移量(0.0)。須要再下拉一下, scrollView.contentInset = UIEdgeInsetsMake(200, 0, 200, 0); scrollView.contentOffset = CGPointMake(0, -200);
九、監聽 contentOffset ,能夠獲得相似拖動代理的效果。如寫第三方給別人用。
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{ }
十、取消cell的左滑 編輯、刪除 狀態。如,按了其餘位置的按鈕,cell不會自動復原。
[self.mTableView setEditing:NO animated:YES];
十一、style ,風格樣式
// 分組 風格 // 一、自動間隔開每組 // 二、如需設置間隔須要注意,組頭組尾都要處理。(坑過一次,只設置組尾高度,結果發現怎麼還很高,並且不顯示不能爲0,要 = CGFLOAT_MIN) // 三、滑動,組頭不會懸停 self.mTableView = [[UITableView alloc]initWithFrame:CGRectZero style:UITableViewStyleGrouped]; // 扁平化 風格 // 一、每組的間隙可經過組頭、組尾,自行調整。(相對上面風格,組頭組尾高度默認爲0) // 二、滑動,組頭組尾會懸停 self.mTableView = [[UITableView alloc]initWithFrame:CGRectZero style:UITableViewStylePlain];
十二、獲取當前顯示的cells
// 直接獲得 cells self.mTableView.visibleCells // 獲得 indexPath ,看需求經過 cellForRowAtIndexPath: 轉換。 self.mTableView.indexPathsForVisibleRows
1三、滑動時,使用低分辨率圖片,中止時再加載高分辨率圖片。(利用 代理 和上面 「十二、獲取當前顯示的cells」 )
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate; - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;
1四、UIScrollView、UITableView 實時 位置 相關
參照 《iOS:手勢與矩形、點運算相關》 -> 「一、矩形、點運算」 -> 「四、UIScrollView、UITableView 實時 位置 相關」
1五、拖動狀態。好比判斷當前滾動是否拖動引發。拖動的代理只有開始和結束,拖動中沒有。
mScrollView.dragging
1六、滾動到具體的cell位置。如,一、外賣,左右tableView聯動。二、聊天,滾動到最新信息。
if (self.dataSource.count > 0) { [self.mTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:self.dataSource.count-1 inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES]; }
1七、選中效果,動畫
// 用途可參考外賣、電商類APP(左邊tbV的分類,隨着右邊商品的拖動,跟新cell選中位置) [self.leftTableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:section inSection:0] animated:YES scrollPosition:UITableViewScrollPositionTop];
N、若是一個 tableView 對應多個 dataSource 。經過按鈕切換,那麼要考慮,點擊/滑動 切換時,請求返回的數據,是不是當前 「功能選中」的位置,好比:
判斷對比請求先後的字段 parameterDics、狀態。若不是,
1)、可丟棄。
2)、可刷新該狀態對應的 dataSource 數組(有的話),下次切換,可先刷出數據,再請求。界面友好(防止網絡請求,一片空)。
邊輸入邊搜索,同理,避免,如快速刪除完後,又刷出刪除前的請求數據。
2、取cell
一、cell初始化的一些區別
1)、TableViewCell
1-1)、沒註冊
沒註冊的(一開始會取不到): cell = 從隊列取 if(cell取不到) { 建立cell 建立子視圖,加tag } cell從tag取子視圖,刷新 tag 或 屬性
1-2)、註冊
註冊的(100%取獲得): cell = 從隊列取(有indexPath的方法) 刷新 tag 或 屬性 ( 系統取不到,會走自定義的initWithStyle:reuseIdentifier: if(cell建立成功) { 建立子視圖,加tag } )
2)、CollectionViewCell
2-1)、沒註冊
2-2)、註冊
註冊的(100%取獲得): cell = 從隊列取(有indexPath的方法) if(cell取獲得) { (判斷是否有子視圖)建立子視圖 } 刷新 tag 或 屬性 collectionViewCell 流程有點不一樣 一、沒 TableViewCell 的 initWithStyle:reuseIdentifier: 二、但 每次都能從隊列取到 三、因此 須要判斷取到的cell是否有子視圖,否則會不斷建立
二、加載XIB
1)、從多個cell樣式的XIB加載。只有1個cell樣式,可直接lastObject加載。(先根據不一樣的ID取,取不到再加載。)
1-1)、獲取XIB裏的全部對象
NSArray *cellArry = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([MyTableCell class]) owner:self options:nil];
1-2)、讀取對應的Cell樣式,此時的參數type爲枚舉,或基本數據類型。
cell = [cellArry objectAtIndex:type];
2)、在 UIView + xxx 的類別文件裏,能夠添加這個類。方便加載單種Cell樣式的XIB。
+ (instancetype)viewFromXib { return [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self) owner:nil options:nil] lastObject]; }
3、cell高度
0、不固定內容的cell,可弄數組、模型存高度,以避免每次計算。
貌似系統計算的(下面的四、五、),耗時都長?複雜的cell滑動不流暢?因此仍是能手動就手動咯(下面的二、三、)?
還有,若是是富文本,記得要把font加進去計算,常常算行距的時候,忘了字體大小。
一、所有固定高度
self.tableView.rowHeight = 44;
二、自定義cell類方法
+ (CGFloat)getCellHeight { return 44; } + (CGFloat)getCellHeightWithData:(id)data { // 手動計算label的高度 return 計算高度; }
後續補充:對於固定高度,沒問題,好用。對於根據Data計算的,根據狀況保存計算高度。
三、模型(只有屬性的特殊類)
後續補充:經過get方法讀取。須要才計算(懶加載),可能還要判斷是否計算過,不然每次都要計算?
四、系統自動計算(iOS6後,使用 UIView 的 類別 UIConstraintBasedLayoutFittingSize 的方法,控件須要全是 Autolayout 約束?)
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { // 取出不帶 indexPath 的 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([MyCell class])]; // 填充數據 //cell.model = model[indexPath.row]; [cell initData:data[indexPath.row]]; // 計算高度 // UILayoutFittingCompressedSize 返回最小可能的值 // UILayoutFittingExpandedSize 返回最大可能的值 cellHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height + 0.5f; return cellHeight; }
後續補充:一、根據狀況保存計算高度。
二、普通View非Cell,的高度計算也可用,但一樣要 Autolayout 約束。
三、註冊cell,通常是取出帶indexPath的。不帶indexPath通常是在自寫cell重用機制的用的。
可是,註冊cell 還能夠取出普通的cell樣式,不帶 indexPath。來填充數據,計算高度。
四、對三、補充,若是是xib能夠用 NSBundle。
五、對三、再補充,能夠弄個局部變量,用懶加載獲取普通cell,不用每次都獲取。
六、label類,多行,除了 label.numberOfLines = 0。
好像還須要設置 label.preferredMaxLayoutWidth = SCREEN_WIDTH - 20 ;
五、系統自動計算(iOS8後,UITableViewAutomaticDimension,控件須要全是 Autolayout 約束?)
1)、先給cell高度一個估算值,好讓TableView,知道contentSize有多大
tableView.estimatedRowHeight = 80.0f;
2)、設置爲自動計算
tableView.rowHeight = UITableViewAutomaticDimension;
iOS8後,UITableViewAutomaticDimension自動計算,不用實現 heightForRowAtIndexPath 了,不過爲了兼容ios8前,可能須要再寫、判斷
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { if ( [[[UIDevice currentDevice] systemVersion ] integerValue] >= 8) { return UITableViewAutomaticDimension; } else { } }
4、導航欄、TableView常見問題相關
一、導航欄、TableView
//調整contentInset。
//NO:不調整,按設定的frame、contentInset的顯示
//YES:會調整contentInset.top的高,讓顯示的頂在導航欄下面,【有滑過半透明效果】
self.automaticallyAdjustsScrollViewInsets =NO;
//調整frame
// UIRectEdgeNone //會頂在導航欄下面【沒有滑過半透明效果】
// UIRectEdgeTop //對齊原點
// UIRectEdgeLeft //對齊左邊
// UIRectEdgeBottom //對齊頂部
// UIRectEdgeRight //對齊右邊
// UIRectEdgeAll //對齊全部
self.edgesForExtendedLayout = UIRectEdgeNone;
//導航欄半透明
self.navigationController.navigationBar.translucent = YES;
//隱藏navigationBar(一、它推過的全部的VC共用1個Bar;二、用繼承View的hidden屬性,隱藏不了!)
self.navigationController.navigationBarHidden=YES;
二、iOS11
此處參考自簡書 「iOS 11 安全區域適配總結 」 -- sonialiu
1)、TableView 默認開啓Cell高度估算,關掉。
[UITableView appearance].estimatedRowHeight = 0; [UITableView appearance].estimatedSectionHeaderHeight = 0; [UITableView appearance].estimatedSectionFooterHeight = 0;
2)、ScrollView新增安全區域。
2-1)、若是以前讓TabelView頂住屏幕,而後設置頂部內邊距 = 20+44,恰好在導航欄下面的話,
會被系統向下偏移64的 SafeAreaInsets,再加上本身設置的64,就出現下移64問題。
2-2)、同理,沒導航欄的時候,也會下移20 -> 狀態欄的高度。
2-3)、之前若設置 automaticallyAdjustsScrollViewInsets = YES 讓系統自動調整,不會有問題
解決方案:添加下面,至關於 automaticallyAdjustsScrollViewInsets = NO
#ifdef __IPHONE_11_0 if ([tableView respondsToSelector:@selector(setContentInsetAdjustmentBehavior:)]) { [UIScrollView appearance].contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever; } #endif
2-4)、contentInsetAdjustmentBehavior 其餘類型
UIScrollViewContentInsetAdjustmentScrollableAxes: adjustedContentInset = ( 可滾動方向 ? safeAreaInset + contentInset : contentInset );
UIScrollViewContentInsetAdjustmentNever: adjustedContentInset = contentInset;
UIScrollViewContentInsetAdjustmentAlways: adjustedContentInset = safeAreaInset + contentInset;
UIScrollViewContentInsetAdjustmentAutomatic: (controller裏automaticallyAdjustsScrollViewInsets = YES) && (controller被navigation包含) == Always,不然 == Axes
5、自定義左滑刪除按鈕圖片
參考自簡書 《【支持iOS11】UITableView左滑刪除自定義 - 實現多選項並使用自定義圖片 》 -- pika11
0、寫在前面
儘管iOS11已經支持自定義刪除圖片了,但仍是要兼容之前的。
一、進入編輯模式,標記view爲須要layout。
- (void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath { self.editingIndexPath = indexPath; [vc.view setNeedsLayout]; } - (void)tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath { self.editingIndexPath = nil; }
二、在VC的,layout子View完成的時候,判斷是否須要改變cell刪除樣式
-(void)viewDidLayoutSubviews { [super viewDidLayoutSubviews]; if (self.dataSource.editingIndexPath != nil) { // 改變cell 刪除文字 爲 刪除圖片 [self resetCellDeleteButton]; } }
三、配置,不一樣系統
- (void)resetCellDeleteButton { // 獲取選項按鈕的reference if ( [[[UIDevice currentDevice] systemVersion] integerValue] >= 11 ) { for (UIView *subview in self.mTableView.subviews) { //iOS11(Xcode 8編譯): UITableView -> UITableViewWrapperView -> UISwipeActionPullView if ([subview isKindOfClass:NSClassFromString(@"UITableViewWrapperView")]) { for (UIView *subsubview in subview.subviews) { if ([subsubview isKindOfClass:NSClassFromString(@"UISwipeActionPullView")] && [subsubview.subviews count] >= 1) { #warning - 可能須要判斷類型再去改,會比較好,暫時沒去試。 UIButton *deleteButton = subsubview.subviews.lastObject; [self configDeleteButton:deleteButton]; } } } //iOS11(Xcode 9編譯): UITableView -> UISwipeActionPullView else if ([subview isKindOfClass:NSClassFromString(@"UISwipeActionPullView")] && [subview.subviews count] >= 1) { #warning - 可能須要判斷類型再去改,會比較好,暫時沒去試。 UIButton *deleteButton = subview.subviews.lastObject; [self configDeleteButton:deleteButton]; } } } else { // iOS8-10: UITableView -> UITableViewCell -> UITableViewCellDeleteConfirmationView SignCell *tableCell = [self.mTableView cellForRowAtIndexPath:self.dataSource.editingIndexPath]; for (UIView *subview in tableCell.subviews) { if ( [subview isKindOfClass:NSClassFromString(@"UITableViewCellDeleteConfirmationView")] && [subview.subviews count] >= 1) { UIButton *deleteButton = subview.subviews.lastObject; [self configDeleteButton:deleteButton]; } } } }
四、實現刪除樣式
- (void)configDeleteButton:(UIButton*)deleteButton { deleteButton.backgroundColor = kBgColor; [deleteButton setTitle:@"" forState:UIControlStateNormal]; [deleteButton setImage:[UIImage imageNamed:@"delete"] forState:UIControlStateNormal]; }
6、僅作了解
一、cell 異步加載網絡圖片,主線程更新UI。
1)、如今有了 SDWebImage ,只作爲一種思路瞭解
2)、重用機制。
在取 cell 的同時刷新 imageView 的 tag ,當 imageView 異步獲取到圖片,判斷本身的 tag 仍是不是請求前傳進來的 index + basetag。
若是不加這樣的判斷,當網絡差,會出現圖片錯位的狀況。
3)、cacheDic。
目前寫法,只是一個可變字典。
日後考慮,1)、獲取圖片前,先判斷自定義緩存NSCache(或者字典)是否有相應url名的圖片。
1)、有 -> 直接調用
2)、沒有 -> 去本地查找,url名的圖片
1)、有,提取到緩存NSCache。key = url,object = image。調用
2)、沒有?請求,以url命名保存到本地、緩存,調用。
2)、NSCache設置必定大小,會自動刪除舊。如緩存讀不到,又會去本地讀取,並刷新到NSCache裏。
緩存缺陷,若是後臺更新圖片,且名字用原來的,就不會被刷新。
0、宏定義 #define kImageBaseTag 2000 一、判斷數據 // 更新imageView的標籤 imgView.tag = indexPath.row + kImageBaseTag; // 在單元格顯示的時候,先清掉 imgView.image = nil; // 判斷是否加載過,有就用,沒有就請求 if ([[self.imageCacheDic allKeys] containsObject:self.dataSource[indexPath.row]]) { imgView.image = [self.imageCacheDic objectForKey:indexPath]; }else{ dispatch_async(_queue, ^{ NSURL *url = [NSURL URLWithString:self.dataSource[indexPath.row]]; [imgView requestImgFromUrl:url cache:self.imageCacheDic index:indexPath]; }); } 二、請求數據 #import "UIImageView+MyWebCache.h" -(void)requestImgFromUrl:(NSURL*)url cache:(NSMutableDictionary*)cache indexPath:(NSIndexPath*)indexPath { NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; [request setURL:url]; [request setHTTPMethod:@"GET"]; [request setTimeoutInterval:3]; [[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { UIImage *image = [UIImage imageWithData:data]; dispatch_async(dispatch_get_main_queue(), ^{ if (image != nil) { // 添加到緩存 [cache setObject:image forKey:indexpath]; // 判斷是否須要刷新 if (self.tag == indexPath.row + kBaseImageTag) { self.image = image; } } }); }] resume]; }
二、自定義循環池
NSMutableSet *recyclePool; MyCell *cell = [self.recyclePool anyObject]; if (cell) { // 從循環池內取出 [self.recyclePool removeObject:cell]; } else { // 建立 cell = [MyCell cell]; } // 超出屏幕再 add 進循環池
三、圖片緩存相關
參照《iOS:圖片相關》