iOS:UITableView相關(18-10-20更)

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:圖片相關》

相關文章
相關標籤/搜索