iOS-UITableView的使用+原理

一. 關(guān)于UITableView

1. UITableViewStyle

作為iOS開發(fā)者UITableView可能是最為常用的一個(gè)控件燕鸽,我們都知道在創(chuàng)建UITableView的時(shí)候有兩種樣式可供選擇UITableViewStylePlain坚弱、UITableViewStyleGrouped拙吉。

關(guān)于這兩種樣式到底有什么區(qū)別穷蛹,該如何選擇。說(shuō)實(shí)話房资,在相當(dāng)長(zhǎng)的一段時(shí)間里我是稀里糊涂的。下面我來(lái)分享一下經(jīng)驗(yàn),僅供參考掉伏,若有偏差望請(qǐng)指正!

① UITableViewStylePlain

  1. 如果有sectionHeader斧散,區(qū)頭會(huì)出現(xiàn)懸浮吸附的效果。
  2. 如果沒(méi)有sectionHeader摊聋、sectionFooter鸡捐,cell會(huì)鋪滿整個(gè)table。
  3. UITableViewStylePlain的tableView可以有一個(gè)section索引栗精,作為一個(gè)bar在table的右邊(例如A ~ Z)闯参。你可以點(diǎn)擊一個(gè)特定的標(biāo)簽,跳轉(zhuǎn)到目標(biāo)section悲立,例如iPhone的通訊錄鹿寨。
UITableViewStylePlain
補(bǔ)充:

UITableViewStylePlain樣式可以使用系統(tǒng)的索引條,使用方法如下:

//返回索引的數(shù)組
-(NSArray<NSString *> *)sectionIndexTitlesForTableView:(UITableView *)tableView{
    //索引文字顏色
    tableView.sectionIndexColor = [UIColor snbcl_colorWithHexString:@"55607C"]; 
    //索引條背景顏色
    tableView.sectionIndexBackgroundColor = [UIColor clearColor]; 
    //返回索引數(shù)組
    return [NSArray arrayWithObjects:@"A",@"B",@"C",@"D",@"E",@"F",@"G",@"H",@"I",@"J",@"K",@"L",@"M",@"N",@"O",@"P",@"Q",@"R",@"S",@"T",@"U",@"V",@"W",@"X",@"Y",@"Z",@"#", nil];
}

//點(diǎn)擊了哪個(gè)索引
-(NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index{
    //點(diǎn)擊系統(tǒng)的某個(gè)索引薪夕,就會(huì)回調(diào)這個(gè)方法脚草,title為索引的標(biāo)題,index為點(diǎn)擊了哪個(gè)索引
    //我們需要返回一個(gè)值原献,用來(lái)告訴系統(tǒng)馏慨,tableView滾動(dòng)到哪個(gè)索引
    return  0;
}

② UITableViewStyleGrouped

  1. 如果有sectionHeader、sectionFooter姑隅,區(qū)頭區(qū)尾會(huì)和cell一樣正常滾動(dòng)写隶,沒(méi)有懸浮吸附的效果。
  2. 如果沒(méi)有人為設(shè)置sectionHeader讲仰、sectionFooter慕趴,會(huì)有個(gè)默認(rèn)的sectionHeader、sectionFooter效果。
  3. 如果不想要這個(gè)默認(rèn)的sectionHeader冕房、sectionFooter效果躏啰,可以設(shè)置他們的高度為很小值,不能為0耙册,為0系統(tǒng)會(huì)使用默認(rèn)值给僵,設(shè)置為最小值之后,cell會(huì)鋪滿整個(gè)table详拙。
  4. UITableViewStyleGrouped的tableView不能有一個(gè)(右邊的)索引帝际,比如iPhone的設(shè)置界面。
UITableViewStyleGrouped

共同點(diǎn):

兩種樣式都可以設(shè)置tableHeaderView饶辙、tableFooterView胡本,并且都沒(méi)有懸浮吸附的效果。

注意點(diǎn):

  1. tableview的sectionFooterHeight畸悬、sectionFooterHeight屬性只在UITableViewStyleGrouped類型侧甫,并且未實(shí)現(xiàn)代理方法tableView:heightForHeader/FooterInSection: 時(shí)有效。
  2. 所以設(shè)置sectionHeader蹋宦、sectionFooter的高度以及自定義sectionHeader披粟、sectionFooter的時(shí)候最好使用如下代理方法,這樣就不用考慮這么多了冷冗。
//設(shè)置sectionHeader高度
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
    return 0.00001;
}
//設(shè)置sectionFooter高度
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
    return 0.00001;
}

總結(jié):

  1. 如果有sectionHeader吸附懸浮的需求守屉,或者你不需要sectionHeader、sectionFooter蒿辙,那么建議使用UITableViewStylePlain拇泛。
  2. 如果你需要使用sectionHeader、sectionFooter思灌,并且沒(méi)吸附效果的要求俺叭,那么建議使用UITableViewStyleGrouped。
  3. 很容易理解:Plain樣式有懸浮效果泰偿,所以可以設(shè)置索引條熄守,Grouped樣式本來(lái)就是分組的意思,所以會(huì)有系統(tǒng)自動(dòng)生成的sectionHeader耗跛、sectionFooter

2. UITableViewCellStyle

系統(tǒng)的UITableViewCell有四種樣式裕照,如果cell不是太復(fù)雜我們可以使用系統(tǒng)的cell,先看代碼:

- (UITableView *)tableView {
    if (!_tableView) {
        _tableView = [[UITableView alloc] initWithFrame:CGRectMake(0,StatusAndNavBarHight,ScreenWidth,ViewSafeHeight) style:UITableViewStylePlain];
        _tableView.delegate = self;
        _tableView.dataSource = self;
        // 設(shè)置cell分隔線
        _tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine;
        // 默認(rèn)情況下不顯示cell的地方也有分割線调塌,解決辦法如下:
        _tableView.tableFooterView = [UIView new];
    }
    return _tableView;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *cellId = @"cellid";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellId];
    }
    
    cell.imageView.image = [UIImage imageNamed:@"car"];
    cell.textLabel.text = @"我是測(cè)試標(biāo)題";
    cell.detailTextLabel.text = @"我是測(cè)試副標(biāo)題";
    //設(shè)置附件樣式晋南,一般使用右箭頭樣式
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    
    // 設(shè)置cell選擇樣式 默認(rèn)選中灰色
    cell.selectionStyle = UITableViewCellSelectionStyleDefault;
    // 設(shè)置分隔線的位置 默認(rèn)是{0, 15, 0, 0}即左邊有15的距離沒(méi)橫線
    // 如果設(shè)置為UIEdgeInsetsZero,則整個(gè)寬度都有橫線
    // cell.separatorInset = UIEdgeInsetsZero;
    return cell;
}

系統(tǒng)的UITableViewCell有四個(gè)子控件羔砾,分別是imageView负间、textLabel紊扬、detailTextLabel、accessoryType唉擂。
對(duì)于每一種樣式,并不是所有的子控件都可以顯示檀葛,具體效果如下:

① UITableViewCellStyleDefault

UITableViewCellStyleDefault.png

如果是Default樣式玩祟,最多只能顯示imageView、textLabel屿聋、accessoryType這三個(gè)控件空扎,如果不設(shè)置imageView,則textLabel會(huì)往左靠润讥,如果設(shè)置accessoryType為UITableViewCellAccessoryNone转锈,則向右的箭頭不顯示。

② UITableViewCellStyleSubtitle

UITableViewCellStyleSubtitle.png

同理楚殿,如果不設(shè)置imageView撮慨,則右邊的控件會(huì)左移,如果右邊的控件只設(shè)置一個(gè)那么這個(gè)控件會(huì)居中顯示脆粥。

③ UITableViewCellStyleValue1

UITableViewCellStyleValue1.png

detailTextLabel會(huì)一直固定在右側(cè)砌溺,如果不設(shè)置imageView,textLabel會(huì)左移变隔。

④ UITableViewCellStyleValue2

UITableViewCellStyleValue2.png

藍(lán)色的是textLabel规伐,黑色的是detailTextLabel,如果只設(shè)置一個(gè)匣缘,另外一個(gè)會(huì)往左移猖闪。

補(bǔ)充

  1. 默認(rèn)點(diǎn)擊cell之后一直有選中效果,如果想點(diǎn)擊一下再取消選中效果肌厨,可寫如下代碼:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
    [tableView deselectRowAtIndexPath:indexPath animated:YES];
}
  1. 當(dāng)我們?cè)O(shè)置accessoryType = UITableViewCellAccessoryDisclosureIndicator;會(huì)顯示向右的箭頭培慌,當(dāng)我們?cè)O(shè)置accessoryView之后,優(yōu)先顯示accessoryView不顯示向右的箭頭柑爸,比如設(shè)置accessoryView為UISwitch检柬,如下:

總結(jié):

如果我們不需要detailTextLabel,直接使用默認(rèn)的樣式就好竖配,如果需要detailTextLabel何址,可以使用Value1樣式。

3. UITableView的簡(jiǎn)單使用

//創(chuàng)建tableView
- (UITableView *)mineTableView
{
    if (!_mineTableView) {
        _mineTableView = [[UITableView alloc] initWithFrame:CGRectMake(0, StatusBarHeight + NavigationBarHeight, ScreenWidth, ViewSafeHeight) style:UITableViewStyleGrouped];
        _mineTableView.showsVerticalScrollIndicator = NO;
        _mineTableView.separatorStyle = UITableViewCellSeparatorStyleNone;
        _mineTableView.delegate = self;
        _mineTableView.dataSource = self;
        _mineTableView.backgroundColor = [UIColor colorWithHex:0xf3f3f3];
        _mineTableView.accessibilityIdentifier = @"XUMineTableView";
        _mineTableView.estimatedRowHeight = 0;
        _mineTableView.estimatedSectionFooterHeight = 0;
        _mineTableView.estimatedSectionHeaderHeight = 0;
        if (@available(iOS 11.0, *)) {
            [_mineTableView setContentInsetAdjustmentBehavior:UIScrollViewContentInsetAdjustmentNever];
        } else {
            self.automaticallyAdjustsScrollViewInsets = NO;
        }
    }
    return _mineTableView;
}

//獲取cell
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *rid = @"XUMineCellIdentify";
//如果在創(chuàng)建tableView的時(shí)候注冊(cè)cell了进胯,在使用dequeueReusableCellWithIdentifier獲取cell的時(shí)候就不用判空了用爪,因?yàn)樽?cè)后一定可以獲取得到。
    XUMineCell *cell = [tableView dequeueReusableCellWithIdentifier:rid];
//如果在創(chuàng)建tableView的時(shí)候沒(méi)有注冊(cè)cell胁镐,在獲取cell的時(shí)候就需要判空偎血。
    if(cell == nil){
       cell = [[XUMineCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:rid];
    }
}

補(bǔ)充:關(guān)于tableView的tableHeaderView和tableFooterView

  1. 默認(rèn)情況下诸衔,他們的位置布局如下:
tableHeaderView
sectionHeaderView
cell
... 其他cell
sectionFooterView
... 其他組
tableFooterrView

如果tableView有內(nèi)邊距,比如設(shè)置上內(nèi)邊距為100:UIEdgeInsets(top: 100, left: 0, bottom: 0, right: 0)颇玷,那么它們都會(huì)被擠下去笨农,如下圖,藍(lán)色背景的是tableHeaderView帖渠,下面分別是sectionHeaderView谒亦、cell,藍(lán)色背景上面的就是100的內(nèi)邊距空郊。

  1. 如果是通過(guò)frame的方式設(shè)置tableHeaderView份招、tableFooterView,那么tableHeaderView一直在最上方狞甚,tableFooterView一直在最下方锁摔,通過(guò)frame只能修改它們的高度。
  2. 如果是通過(guò)snapkit或者masonry就可以任意修改它們的位置和寬高(不知道為什么)哼审。
  3. reloadData的時(shí)候會(huì)刷新tableHeaderView谐腰、tableFooterView,比如如果修改了tableHeaderView的高度涩盾,就需要reloadData后才會(huì)生效怔蚌。

總結(jié):使用tableHeaderView、tableFooterView旁赊,我們就用frame進(jìn)行布局桦踊。

二. UITableView的重用機(jī)制

系統(tǒng)的UITableView是繼承于UIScrollView,所以可以滑動(dòng)终畅,關(guān)于UITableView最主要的就是重用機(jī)制籍胯,下面驗(yàn)證UITableView的重用機(jī)制,其中_tableview是通過(guò)xib拖過(guò)來(lái)的离福,代碼如下:

@interface ViewController (){
    
    IBOutlet UITableView *_tableview;
   //一共使用了多少個(gè)cell(重用池+現(xiàn)有池個(gè)數(shù))
    NSMutableArray *_cellAry;  
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationController.navigationBar.translucent = NO;

    _cellAry = [NSMutableArray new];
    _tableview.estimatedRowHeight = 0; // 預(yù)估高度,默認(rèn)是44, ios11
    [_tableview registerClass:[UITableViewCell class] forCellReuseIdentifier:@"cell"];
}

#pragma mark - tableView delegate
// 646  一屏幕最多展示多少個(gè)cell:5
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return 30;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    NSLog(@"%ld", (long)indexPath.row);
    return 200;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
    BOOL isContain = NO;
    for(NSValue *value in _cellAry){
        if ([value.nonretainedObjectValue isEqual:cell]) {
            isContain = YES;
            break;
        }
    }
    if (!isContain) {
        //弱引用
        //詳情可參考:http://www.reibang.com/p/51156d4ae885
        NSValue *value = [NSValue valueWithNonretainedObject:cell];
        [_cellAry addObject:value];
    }
    cell.textLabel.text = [@(indexPath.row) description];
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    // 驗(yàn)證預(yù)估高度
    // NSLog(@"%f", _tableview.contentSize.height);
    NSLog(@"==%lu", (unsigned long)_cellAry.count);
}

當(dāng)我們拖動(dòng)tableView并且點(diǎn)擊其中一個(gè)Cell, 會(huì)打印如下:

2019-10-22 14:10:20.631702+0800 TableviewAnly[11882:1011989] 11
2019-10-22 14:10:20.632040+0800 TableviewAnly[11882:1011989] 11
2019-10-22 14:10:21.415626+0800 TableviewAnly[11882:1011989] 12
2019-10-22 14:10:21.416440+0800 TableviewAnly[11882:1011989] 12
2019-10-22 14:14:49.007275+0800 TableviewAnly[11882:1011989] ==5

可以發(fā)現(xiàn)數(shù)組中一共有5個(gè)cell, 但是如下圖, 最多只能展示4個(gè)Cell杖狼,另外1個(gè)Cell去哪了呢?

重用池.png

簡(jiǎn)析:

  1. 重用機(jī)制,會(huì)一直保持cell不變的數(shù)量(5)
  2. 問(wèn)題:當(dāng)界面顯示4個(gè)時(shí)候, 另外1個(gè)去哪里了妖爷?
    重用池(沒(méi)有顯示在界面上的cell: 1個(gè))+ 現(xiàn)有池(visible即顯示在界面上的cell: 4個(gè))= 5個(gè)蝶涩。
  3. 問(wèn)題:是如何控制顯示哪些的呢? 哪些顯示在界面上絮识,哪些不顯示呢绿聘?
    table在加載的時(shí)候,每個(gè)cell的位置信息被保存起來(lái)了次舌,數(shù)據(jù)先行熄攘,UI后走(位置信息被保存下來(lái)了,那么哪些顯示哪些不顯示自然就知道了)彼念。

總結(jié):

通過(guò)上面的代碼我們就知道現(xiàn)有池有4個(gè)cell,重用池有1個(gè)cell,所以一共5個(gè)cell冬竟,而且滾動(dòng)的時(shí)候一直是5個(gè)cell,從而就驗(yàn)證系統(tǒng)UITableView的重用機(jī)制洼畅。

三. 模仿系統(tǒng)的UITableView

因?yàn)橄到y(tǒng)的tableView在加載的時(shí)候,每個(gè)cell的位置信息被保存起來(lái)了棚赔,所以我們先創(chuàng)建個(gè)模型帝簇,代碼如下:

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface EOCCellModel : NSObject

@property (nonatomic, assign)CGFloat y;
@property (nonatomic, assign)CGFloat height;

@end

自定義一個(gè)繼承于UIScrollView的EOCTableView,代碼如下:
EOCTableView.h文件

#import <UIKit/UIKit.h>

@class EOCTableView;

//數(shù)據(jù)源代理
@protocol EOCTableViewDelegate

@required
- (NSInteger)tableView:(EOCTableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (CGFloat)tableView:(EOCTableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (UITableViewCell *)tableView:(EOCTableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;

@end

@interface EOCTableView : UIScrollView

@property (nonatomic,weak)id<EOCTableViewDelegate>delegate; //數(shù)據(jù)源代理

//刷新cell方法
- (void)reloadData; 
//重用方法
- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath;

@end

EOCTableView.m文件

#import "EOCTableView.h"
#import "EOCCellModel.h"

@interface EOCTableView(){
    NSMutableDictionary *_visibleCellDict; // 現(xiàn)有池
    NSMutableArray *_reusePoolCellAry; // 重用池
    NSMutableArray *_cellInfoArr; // cell 信息 (y值,高度,數(shù)量信息)
}

@end

// 667  cell高度 60忆嗜,    660/60 = 11,7個(gè)像素可以顯示2個(gè)殘的  界面最多可以顯示 11 + 2 = 13

@implementation EOCTableView

/*
 1現(xiàn)有池
 2重用池
 3位置信息
 */
- (instancetype)initWithFrame:(CGRect)frame{
    
    self = [super initWithFrame:frame];
    if (self) {
        _visibleCellDict = [NSMutableDictionary new];
        _reusePoolCellAry = [NSMutableArray new];
        _cellInfoArr = [NSMutableArray new];
    }
    return self;
}

#pragma mark - 數(shù)據(jù),UI
/*
 數(shù)據(jù)崎岂, UI
 */
- (void)reloadData{
    
    // 1 處理數(shù)據(jù)
    [self dataHandle];
    // 2 UI 處理
    [self setNeedsLayout];
    
}

// 1. 處理數(shù)據(jù)捆毫, 數(shù)據(jù)model不會(huì)復(fù)用,只是保存下來(lái)
- (void)dataHandle{
    
    // 1.1 獲取cell的數(shù)量
    NSInteger allCellCount = [self.delegate tableView:self numberOfRowsInSection:0];
    
    [_cellInfoArr removeAllObjects]; // 移除舊的信息
    
    CGFloat totalCellHeight = 0;
    for (int i = 0; i < allCellCount; i++) {
        
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
        CGFloat cellHeight = [self.delegate tableView:self heightForRowAtIndexPath:indexPath];
        //1.2 獲取cell的高度和y值, 并保存起來(lái)
        EOCCellModel *model = [EOCCellModel new];
        model.y = totalCellHeight;
        model.height = cellHeight;
        
        [_cellInfoArr addObject:model];
        
        totalCellHeight += cellHeight;
    }
    
    //根據(jù)總高度設(shè)置可以滑動(dòng)的范圍
    [self setContentSize:CGSizeMake(self.frame.size.width, totalCellHeight)];
}

// 2. UI處理, UI會(huì)復(fù)用
- (void)layoutSubviews{
    
    [super layoutSubviews];
    
    // 2.1 計(jì)算可視范圍冲甘,要顯示哪些cell绩卤,并把相關(guān)cell顯示到界面
    CGFloat startY = self.contentOffset.y;
    CGFloat endY = self.contentOffset.y + self.frame.size.height;
    if (startY < 0) {
        startY = 0;
    }
    if (endY > self.contentSize.height) {
        endY = self.contentSize.height;
    }
    
    // 2.2 計(jì)算邊界的cell索引 (從哪幾個(gè)到哪幾個(gè)cell, 如第3個(gè)到第9個(gè))
    
    EOCCellModel *startModel = [EOCCellModel new];
    startModel.y = startY;
    
    EOCCellModel *endModel = [EOCCellModel new];
    endModel.y = endY;
    
    // 2.3 目地就是獲取可視區(qū)域顯示cell的索引范圍
    //使用二分查找,替換下面的方法,效率更高
    //查找:用二分查找(1024 = 2的10次方 查找次數(shù)最多10次)
    NSInteger startIndex = [self binarySerchOC:_cellInfoArr target:startModel];
    NSInteger endIndex = [self binarySerchOC:_cellInfoArr target:endModel];
    
//    // 2.3.1 開始索引
//    for(NSInteger i = 0; i < _cellInfoArr.count; i++){
//
//        EOCCellModel *cellModel = _cellInfoArr[i];
//
//        if (cellModel.y <= startY && cellModel.y + cellModel.height > startY) {
//            startIndex = I;
//            break;
//        }
//    }
//    // 2.3.2 結(jié)束索引
//    for (NSInteger i = startIndex + 1; i < _cellInfoArr.count; i++) {
//
//        EOCCellModel *cellModel = _cellInfoArr[i];
//        if (cellModel.y < endY && cellModel.y + cellModel.height >= endY) {
//            endIndex = I;
//            break;
//        }
//    }
    
    // 2.4 UI操作 獲取cell江醇,并顯示到View上
    for (NSInteger i = startIndex; i <= endIndex; i++) {
        
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:i inSection:0];
        //這個(gè)代理方法里面執(zhí)行了重用機(jī)制方法dequeueReusableCellWithIdentifier
        UITableViewCell *cell = [self.delegate tableView:self cellForRowAtIndexPath:indexPath];
        // 當(dāng)row = 3 的時(shí)候, 如果現(xiàn)有池有個(gè)相同的CellA,就返回cellA
        // 當(dāng)row = 4 的時(shí)候濒憋,現(xiàn)有池不存相關(guān)的cell,然后去重用池讀取cell陶夜,發(fā)現(xiàn)重用池有一個(gè)cellA凛驮,于是就返回了cellA
        
        EOCCellModel *cellModel = _cellInfoArr[i];
        cell.frame = CGRectMake(0, cellModel.y, self.frame.size.width, cellModel.height);
        
        if (![cell superview]) {
            [self addSubview:cell]; // 添加到tableview上  // addsubivew
        }
    }
    
    // 2.5 從現(xiàn)有池里面移走不在界面上的cell,移動(dòng)到重用池里(把不在可視區(qū)域的cell移到重用池)
    NSArray *visibelCellKey = _visibleCellDict.allKeys;
    for (NSInteger i = 0; i < visibelCellKey.count; i++) {
        
        NSInteger index = [visibelCellKey[i] integerValue];
        if (index < startIndex || index > endIndex) {
            
            [_reusePoolCellAry addObject:_visibleCellDict[visibelCellKey[i]]];
            [_visibleCellDict removeObjectForKey:visibelCellKey[I]];
        }
    }
}

#pragma mark - 重用的根本方法  先從現(xiàn)有池拿,沒(méi)有再?gòu)闹赜贸啬?沒(méi)有再創(chuàng)建
// 重用池Model/UI 和 現(xiàn)有池Model/UI
// 重用池和現(xiàn)有池有同一個(gè) cellA条辟,現(xiàn)有池的cellA 就會(huì)返回出來(lái)
- (__kindof UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath{
    
    // 1. 是否在現(xiàn)有池里面,如果有,從現(xiàn)有池里面讀取對(duì)應(yīng)的cell返回
    UITableViewCell *cell = _visibleCellDict[@(indexPath.row)];
    if(!cell){
        // 2. 現(xiàn)有池如沒(méi)有黔夭,再看重用池
        // 2.1 重用池是否有沒(méi)有用的cell,有就返回cell
        if(_reusePoolCellAry.count > 0){
            cell = _reusePoolCellAry.firstObject;
        
        }else{
        // 2.2 重用池沒(méi)有就創(chuàng)建
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
        }
        // 2.3 保存到現(xiàn)有池,移除重用池
        [_visibleCellDict setObject:cell forKey:@(indexPath.row)];// 保存到現(xiàn)有池
        [_reusePoolCellAry removeObject:cell]; // 移除重用池
    }
    
    return cell;
}

#pragma mark - 二分查找查找索引操作
- (NSInteger)binarySerchOC:(NSArray*)dataAry target:(EOCCellModel*)targetModel{
    
    NSInteger min = 0;
    NSInteger max = dataAry.count - 1;
    NSInteger mid;
    while (min < max) {
        mid = min + (max - min)/2;
        // 條件判斷
        EOCCellModel *midModel = dataAry[mid];
        if (midModel.y < targetModel.y && midModel.y + midModel.height > targetModel.y) {
            return mid;
        }else if(targetModel.y < midModel.y){
            max = mid;// 在左邊
            if (max - min == 1) {
                return min;
            }
        }else {
            min = mid;// 在右邊
            if (max - min == 1) {
                return max;
            }
        }
    }
    return -1;
}

@end

在SecondViewCtr添加如下代碼:

#import "SecondViewCtr.h"
#import "EOCTableView.h"

@interface SecondViewCtr ()<EOCTableViewDelegate>{
    EOCTableView *_tableView;
}
@end

@implementation SecondViewCtr

- (void)viewDidLoad {
    [super viewDidLoad];
    _tableView = [[EOCTableView alloc] initWithFrame:self.view.frame];
    _tableView.delegate = self;
    [_tableView reloadData];
    [self.view addSubview:_tableView];
}

- (NSInteger)tableView:(EOCTableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return 200;
}

//性能優(yōu)化1: 假如高度都不一樣 【tableView reload】去計(jì)算 重新計(jì)算200個(gè)cell的高度,計(jì)算量會(huì)很大,所以常見的優(yōu)化手段就是保存高度
- (CGFloat)tableView:(EOCTableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return  60;
}

- (UITableViewCell *)tableView:(EOCTableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
//性能優(yōu)化2:UI,cell的處理更少占用主線程(使用SDWebImage異步加載,緩存)
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
     cell.textLabel.text = [@(indexPath.row) description];
    return cell;
}
@end

運(yùn)行后羽嫡,效果圖如下:
自定義tableView.png

滑動(dòng)后本姥,在EOCTableView的layoutSubviews方法打個(gè)斷點(diǎn),po一下:

(lldb) po _visibleCellDict.allKeys.count
12

(lldb) po _reusePoolCellAry.count
2

發(fā)現(xiàn)現(xiàn)有池12個(gè)杭棵,重用池2個(gè)婚惫,實(shí)現(xiàn)了模仿系統(tǒng)的tableView。

總結(jié):

系統(tǒng)tableView重用Cell的根本方法就是dequeueReusableCellWithIdentifier方法魂爪,這個(gè)方法是在系統(tǒng)調(diào)用cellForRowAtIndexPath方法的內(nèi)部調(diào)用的先舷,dequeueReusableCellWithIdentifier內(nèi)部做的事是:先從現(xiàn)有池拿,沒(méi)有再?gòu)闹赜贸啬米沂蹋瑳](méi)有再創(chuàng)建密浑。

關(guān)于代碼的講解就省略了,看注釋吧粗井。

四. 系統(tǒng)UITableView的優(yōu)化

最基本的兩個(gè)方法:

  1. 緩存Cell高度
    假如高度都不一樣 [tableView reload] 去計(jì)算尔破,重新計(jì)算200個(gè)cell的高度街图,計(jì)算量會(huì)很大,所以常見的優(yōu)化手段就是保存高度懒构。
  2. 異步加載Cell圖片
    Cell的處理更少占用主線程(使用SDWebImage異步加載餐济、緩存)

代碼如上??:

五. 實(shí)現(xiàn)UITaleView懸浮兩個(gè)頭視圖的效果

自定義taleView,代碼如下:

#import <UIKit/UIKit.h>
@interface TaskTableView : UITableView

@property (nonatomic, weak)UIView *secionOneHeadView;
@property (nonatomic, weak)UIView *secionTwoHeadView;

@end
#import "TaskTableView.h"

@implementation TaskTableView

- (void)layoutSubviews{
    [super layoutSubviews];
    // 重新布局headView位置
    CGFloat headViewWidth = self.secionOneHeadView.frame.size.width;
    CGFloat headViewHeight = self.secionOneHeadView.frame.size.height;
    //當(dāng)前面五個(gè)cell已經(jīng)滑過(guò)去的時(shí)候,重新布局
    if (self.contentOffset.y > 70 * 5 ) {
        //重新設(shè)偏移量
        self.secionOneHeadView.frame = CGRectMake(0, self.contentOffset.y, headViewWidth, headViewHeight);
        self.secionTwoHeadView.frame = CGRectMake(0, self.contentOffset.y + headViewHeight, headViewWidth, headViewHeight);
    }
    //系統(tǒng)默認(rèn)會(huì)移除,所以我們重新添加上去
    if (![self.secionOneHeadView superview]) {
        [self addSubview:self.secionOneHeadView];
    }
}
@end

在ThridViewCtr.xib內(nèi)容如下圖:
xib.png

在ThridViewCtr.m實(shí)現(xiàn)如下代碼:

#import "ThridViewCtr.h"
#import "TaskTableView.h"
@interface ThridViewCtr ()<UITableViewDataSource, UITableViewDelegate>{

    IBOutlet TaskTableView *_tableview;
    IBOutlet UIView *_sectionOneHeadView;
    IBOutlet UIView *_sectionTwoHeadView;
}
@end

@implementation ThridViewCtr

- (void)viewDidLoad {
    [super viewDidLoad];
 
    [_sectionOneHeadView removeFromSuperview];
    [_sectionTwoHeadView removeFromSuperview];
    
    _tableview.secionOneHeadView  = _sectionOneHeadView;
    _tableview.secionTwoHeadView  = _sectionTwoHeadView;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
    return 2;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    if (section == 0) {
        return 5;
    }
    return 20;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 70;
}

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{
    return 80;
}

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
    if (section == 0) {
        return _sectionOneHeadView;
    }else{
        return _sectionTwoHeadView;
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    if (!cell) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
    }
    cell.textLabel.text = [NSString stringWithFormat:@"%d--%d", indexPath.section, indexPath.row];
    return cell;
}
@end

運(yùn)行后向上拖動(dòng)tableView發(fā)現(xiàn), 兩個(gè)headerView會(huì)一直停留在頂部, 效果圖如下:
兩個(gè)頭視圖懸浮.png

總結(jié):

實(shí)現(xiàn)方法就是拿到頭視圖引用胆剧,在tableView的layoutSubviews方法里面修改頭視圖偏移量絮姆,再重新添加到tableView上。

六. 其他注意點(diǎn)

  1. 調(diào)用reloadData方法并不是立馬執(zhí)行秩霍,是異步的篙悯,會(huì)在下一個(gè)RunLoop循環(huán)里面執(zhí)行。
  2. tableview.estimatedRowHeight = 0;
    ① tableView的預(yù)估高度铃绒,iOS11之后出現(xiàn)的鸽照,在一個(gè)cell還沒(méi)有顯示到界面上,系統(tǒng)會(huì)給一個(gè)預(yù)估高度颠悬,默認(rèn)44矮燎。
    ② 如果這個(gè)屬性不設(shè)置為0,在這個(gè)時(shí)候去獲取tableview.contentSize.height是不準(zhǔn)的赔癌。
    ③ 這個(gè)在MJ_footer里面诞外,如果不設(shè)置預(yù)估高度為0,拿到的contentSize不準(zhǔn)灾票,會(huì)出現(xiàn)bug峡谊。

Demo地址:https://github.com/iamkata/tableView

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市刊苍,隨后出現(xiàn)的幾起案子靖苇,更是在濱河造成了極大的恐慌,老刑警劉巖班缰,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件贤壁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡埠忘,警方通過(guò)查閱死者的電腦和手機(jī)脾拆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)莹妒,“玉大人名船,你說(shuō)我怎么就攤上這事≈嫉。” “怎么了渠驼?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)鉴腻。 經(jīng)常有香客問(wèn)我迷扇,道長(zhǎng)百揭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任蜓席,我火速辦了婚禮器一,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘厨内。我一直安慰自己祈秕,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布雏胃。 她就那樣靜靜地躺著请毛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瞭亮。 梳的紋絲不亂的頭發(fā)上方仿,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音街州,去河邊找鬼兼丰。 笑死玻孟,一個(gè)胖子當(dāng)著我的面吹牛唆缴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播黍翎,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼面徽,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了匣掸?” 一聲冷哼從身側(cè)響起趟紊,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎碰酝,沒(méi)想到半個(gè)月后霎匈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡送爸,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年铛嘱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片袭厂。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡墨吓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出纹磺,到底是詐尸還是另有隱情帖烘,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布橄杨,位于F島的核電站秘症,受9級(jí)特大地震影響照卦,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜历极,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一窄瘟、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧趟卸,春花似錦蹄葱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至邻邮,卻和暖如春竣况,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背筒严。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工丹泉, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人鸭蛙。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓摹恨,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親娶视。 傳聞我的和親對(duì)象是個(gè)殘疾皇子晒哄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,092評(píng)論 1 32
  • 序引 本系列文章將介紹iOS開發(fā)中的UITableView控件,將會(huì)分成四篇文章完整的講述UITableView的...
    yetCode閱讀 2,261評(píng)論 3 40
  • 掌握 設(shè)置UITableView的dataSource肪获、delegate UITableView多組數(shù)據(jù)和單組數(shù)據(jù)...
    JonesCxy閱讀 1,119評(píng)論 0 2
  • 我們?cè)谏弦黄锻ㄟ^(guò)代碼自定義不等高cell》中學(xué)習(xí)了tableView的相關(guān)知識(shí)寝凌,本文將在上文的基礎(chǔ)上,利用sto...
    啊世ka閱讀 1,501評(píng)論 2 7
  • UITableView繼承于UIScrollView,可以滾動(dòng)孝赫。 UITableView的每一條數(shù)據(jù)對(duì)應(yīng)的單元格叫...
    肉肉要次肉閱讀 574評(píng)論 5 4