一. 關(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
- 如果有sectionHeader斧散,區(qū)頭會(huì)出現(xiàn)懸浮吸附的效果。
- 如果沒(méi)有sectionHeader摊聋、sectionFooter鸡捐,cell會(huì)鋪滿整個(gè)table。
- UITableViewStylePlain的tableView可以有一個(gè)section索引栗精,作為一個(gè)bar在table的右邊(例如A ~ Z)闯参。你可以點(diǎn)擊一個(gè)特定的標(biāo)簽,跳轉(zhuǎn)到目標(biāo)section悲立,例如iPhone的通訊錄鹿寨。
補(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
- 如果有sectionHeader、sectionFooter姑隅,區(qū)頭區(qū)尾會(huì)和cell一樣正常滾動(dòng)写隶,沒(méi)有懸浮吸附的效果。
- 如果沒(méi)有人為設(shè)置sectionHeader讲仰、sectionFooter慕趴,會(huì)有個(gè)默認(rèn)的sectionHeader、sectionFooter效果。
- 如果不想要這個(gè)默認(rèn)的sectionHeader冕房、sectionFooter效果躏啰,可以設(shè)置他們的高度為很小值,不能為0耙册,為0系統(tǒng)會(huì)使用默認(rèn)值给僵,設(shè)置為最小值之后,cell會(huì)鋪滿整個(gè)table详拙。
- UITableViewStyleGrouped的tableView不能有一個(gè)(右邊的)索引帝际,比如iPhone的設(shè)置界面。
共同點(diǎn):
兩種樣式都可以設(shè)置tableHeaderView饶辙、tableFooterView胡本,并且都沒(méi)有懸浮吸附的效果。
注意點(diǎn):
- tableview的sectionFooterHeight畸悬、sectionFooterHeight屬性只在UITableViewStyleGrouped類型侧甫,并且未實(shí)現(xiàn)代理方法tableView:heightForHeader/FooterInSection: 時(shí)有效。
- 所以設(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é):
- 如果有sectionHeader吸附懸浮的需求守屉,或者你不需要sectionHeader、sectionFooter蒿辙,那么建議使用UITableViewStylePlain拇泛。
- 如果你需要使用sectionHeader、sectionFooter思灌,并且沒(méi)吸附效果的要求俺叭,那么建議使用UITableViewStyleGrouped。
- 很容易理解: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
如果是Default樣式玩祟,最多只能顯示imageView、textLabel屿聋、accessoryType這三個(gè)控件空扎,如果不設(shè)置imageView,則textLabel會(huì)往左靠润讥,如果設(shè)置accessoryType為UITableViewCellAccessoryNone转锈,則向右的箭頭不顯示。
② UITableViewCellStyleSubtitle
同理楚殿,如果不設(shè)置imageView撮慨,則右邊的控件會(huì)左移,如果右邊的控件只設(shè)置一個(gè)那么這個(gè)控件會(huì)居中顯示脆粥。
③ UITableViewCellStyleValue1
detailTextLabel會(huì)一直固定在右側(cè)砌溺,如果不設(shè)置imageView,textLabel會(huì)左移变隔。
④ UITableViewCellStyleValue2
藍(lán)色的是textLabel规伐,黑色的是detailTextLabel,如果只設(shè)置一個(gè)匣缘,另外一個(gè)會(huì)往左移猖闪。
補(bǔ)充:
- 默認(rèn)點(diǎn)擊cell之后一直有選中效果,如果想點(diǎn)擊一下再取消選中效果肌厨,可寫如下代碼:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
- 當(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
- 默認(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)邊距空郊。
- 如果是通過(guò)frame的方式設(shè)置tableHeaderView份招、tableFooterView,那么tableHeaderView一直在最上方狞甚,tableFooterView一直在最下方锁摔,通過(guò)frame只能修改它們的高度。
- 如果是通過(guò)snapkit或者masonry就可以任意修改它們的位置和寬高(不知道為什么)哼审。
- 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去哪了呢?
簡(jiǎn)析:
- 重用機(jī)制,會(huì)一直保持cell不變的數(shù)量(5)
- 問(wèn)題:當(dāng)界面顯示4個(gè)時(shí)候, 另外1個(gè)去哪里了妖爷?
重用池(沒(méi)有顯示在界面上的cell: 1個(gè))+ 現(xiàn)有池(visible即顯示在界面上的cell: 4個(gè))= 5個(gè)蝶涩。 - 問(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)行后羽嫡,效果圖如下:滑動(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è)方法:
- 緩存Cell高度
假如高度都不一樣 [tableView reload] 去計(jì)算尔破,重新計(jì)算200個(gè)cell的高度街图,計(jì)算量會(huì)很大,所以常見的優(yōu)化手段就是保存高度懒构。 - 異步加載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)容如下圖:在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ì)一直停留在頂部, 效果圖如下:總結(jié):
實(shí)現(xiàn)方法就是拿到頭視圖引用胆剧,在tableView的layoutSubviews方法里面修改頭視圖偏移量絮姆,再重新添加到tableView上。
六. 其他注意點(diǎn)
- 調(diào)用reloadData方法并不是立馬執(zhí)行秩霍,是異步的篙悯,會(huì)在下一個(gè)RunLoop循環(huán)里面執(zhí)行。
- 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峡谊。