【转】UITableView 总结
UITableView是UIScrollView的子类,因此它可以自动响应滚动事件(一般为上下滚动)。
它内部包含0到多个UITableViewCell对象,每个table cell展示各自的内容。当新cell需要被显示时,就会调用tableView:cellForRowAtIndexPath:方法来获取或创建一个cell;而不可视时,它又会被释放。由此可见,同一时间其实只需要存在一屏幕的cell对象即可,不需要为每一行创建一个cell。
1.协议介绍
UITableViewDataSource(11)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | //每个section下cell的个数(必须实现) - ( NSInteger )tableView:(UITableView *)tableView numberOfRowsInSection:( NSInteger )section; //通过indexpath返回具体的cell(必须实现) - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:( NSIndexPath *)indexPath; //返回有多少个section(默认是1) - ( NSInteger )numberOfSectionsInTableView:(UITableView *)tableView; //每个section上面的标语内容 - ( NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:( NSInteger )section; //每个section下面的标语内容 - ( NSString *)tableView:(UITableView *)tableView titleForFooterInSection:( NSInteger )section; // Editing //是否可编辑 - ( BOOL )tableView:(UITableView *)tableView canEditRowAtIndexPath:( NSIndexPath *)indexPath; // Moving/reordering // 是否可拖拽 -tableView:moveRowAtIndexPath:toIndexPath: - ( BOOL )tableView:(UITableView *)tableView canMoveRowAtIndexPath:( NSIndexPath *)indexPath; // Index //右侧索引条需要的数组内容 - ( NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView; // return list of section titles to display in section index view (e.g. "ABCD...Z#") //索引值对应的section-index - ( NSInteger )tableView:(UITableView *)tableView sectionForSectionIndexTitle:( NSString *)title atIndex:( NSInteger )index; // tell table which section corresponds to section title/index (e.g. "B",1)) // Data manipulation - insert and delete support // 对Cell编辑后的回调 - ( void )tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:( NSIndexPath *)indexPath; // 对Cell拖拽后的回调 - ( void )tableView:(UITableView *)tableView moveRowAtIndexPath:( NSIndexPath *)sourceIndexPath toIndexPath:( NSIndexPath *)destinationIndexPath; |
UITableViewDelegate(常用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | //将要展示Cell/header/Footer视图回调 - ( void )tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:( NSIndexPath *)indexPath; - ( void )tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:( NSInteger )section NS_AVAILABLE_IOS (6_0); - ( void )tableView:(UITableView *)tableView willDisplayFooterView:(UIView *)view forSection:( NSInteger )section NS_AVAILABLE_IOS (6_0); //完成展示Cell/header/Footer视图回调 - ( void )tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:( NSIndexPath *)indexPath NS_AVAILABLE_IOS (6_0); - ( void )tableView:(UITableView *)tableView didEndDisplayingHeaderView:(UIView *)view forSection:( NSInteger )section NS_AVAILABLE_IOS (6_0); - ( void )tableView:(UITableView *)tableView didEndDisplayingFooterView:(UIView *)view forSection:( NSInteger )section NS_AVAILABLE_IOS (6_0); // Variable height support // 每个cell高度的返回(这里高度通过协议返回,是为了table能准确的定位出要显示的Cell-index,从而满足UITableView的重用机制) - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:( NSIndexPath *)indexPath; // 每个section-header高度的返回 - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:( NSInteger )section; // 每个section-footer高度的返回 - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:( NSInteger )section; // Section header & footer information. Views are preferred over title should you decide to provide both //可返回每个section-header的自定义视图 - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:( NSInteger )section; // custom view for header. will be adjusted to default or specified header height //可返回每个section-footer的自定义视图 - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:( NSInteger )section; // custom view for footer. will be adjusted to default or specified footer height // Selection // Cell高亮的回调,一般式在选择的时候才高亮 - ( BOOL )tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:( NSIndexPath *)indexPath NS_AVAILABLE_IOS (6_0); - ( void )tableView:(UITableView *)tableView didHighlightRowAtIndexPath:( NSIndexPath *)indexPath NS_AVAILABLE_IOS (6_0); - ( void )tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:( NSIndexPath *)indexPath NS_AVAILABLE_IOS (6_0); // Cell选中和取消选择的回调 - ( NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:( NSIndexPath *)indexPath; - ( NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:( NSIndexPath *)indexPath NS_AVAILABLE_IOS (3_0); // Called after the user changes the selection. - ( void )tableView:(UITableView *)tableView didSelectRowAtIndexPath:( NSIndexPath *)indexPath; - ( void )tableView:(UITableView *)tableView didDeselectRowAtIndexPath:( NSIndexPath *)indexPath NS_AVAILABLE_IOS (3_0); |
UITableViewDelegate中的协议还有很多,我只列出了比较常用的,想知道更多的可以查看官方头文件或官方文档
分割线:separator 复制粘贴:Copy/Paste 拖拽:move 索引:index 编辑:editing
2.刷新
下拉刷新:
第三方:EGORefreshTableHeaderView
官方提供(ios6以上系统):UIRefreshControl
UIRefreshControl使用方法非常简单:
UIRefreshControl * _refresh;
1 2 3 4 5 6 | /******内置刷新的常用属性设置******/ _refresh = [[UIRefreshControl alloc] init]; _refresh.tintColor = [UIColor grayColor]; [_refresh addTarget: self action: @selector (pullToRefresh) forControlEvents:UIControlEventValueChanged]; [_tableView addSubview:_refresh]; |
另外UITableViewController中已经自带了UIRefreshControl 为成员变量,只需要增加猝发时间就可以了
上拉刷新
1.第三方:EGORefreshTableFooterView
2.自加按钮:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | - ( void ) initTableFooterView { /******自定义查看更多属性设置******/ if (_bottomRefresh== nil ){ _bottomRefresh = [UIButton buttonWithType:UIButtonTypeCustom]; [_bottomRefresh setTitle:LocalizedString(@ "c_loading" ) forState:UIControlStateNormal]; [_bottomRefresh setTitleColor:[UIColor grayColor] forState:UIControlStateNormal]; [_bottomRefresh setContentEdgeInsets:UIEdgeInsetsMake(0, 0, 5, 0)]; [_bottomRefresh.titleLabel setFont:[UIFont systemFontOfSize:15]]; [_bottomRefresh addTarget: self action: @selector (upToRefresh) forControlEvents:UIControlEventTouchUpInside]; _bottomRefresh.frame = CGRectMake(0, 0, 320, 44); _activityView= [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(90 , 3, 30, 30)]; [_activityView setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleGray]; [_activityView stopAnimating]; [_bottomRefresh addSubview:_activityView]; [_bottomRefresh setHidden: YES ]; [_tableView setTableFooterView:_bottomRefresh]; } |
3.视图滚到最后直接加载更多:
1 2 3 4 5 6 7 8 | - ( void )scrollViewDidScroll:(UIScrollView *)scrollView { int contentHeight=scrollView.contentSize.height; int offsetY=scrollView.contentOffset.y; if (contentHeight>0 && offsetY>0 && offsetY > (contentHeight - scrollView.frame.size.height)){ if (!_isGetMore && !_isNoMore &&!_isUpdate){ //判断是否在加载中或者加载为空,不然或多次加载过多 [ self upToRefresh]; } } |
3.搜索
在xib中拖入search Bar and Search Display控件,实现UISearchBarDelegate协议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | #pragma mark SearchDisplayController DelegateMethod - ( BOOL )searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:( NSString *)searchString { NSPredicate *predicate = [ NSPredicate predicateWithFormat:@ "self CONTAINS %@" , searchString]; if (_searchList) { _searchList = nil ; } _searchList = [ NSMutableArray arrayWithArray:[_dataList filteredArrayUsingPredicate:predicate]]; return YES ; } #pragma mark TableView DataSource and Delegate - ( NSInteger )tableView:(UITableView *)tableView numberOfRowsInSection:( NSInteger )section { if (tableView == self .searchDisplayController.searchResultsTableView) { return _searchList.count; } else { return _rowCount; } } |
不用xib的话,可以直接添加代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | _bSearchBar = [[UISearchBar alloc] init]; _bSearchBar.delegate = self ; [_bSearchBar setPlaceholder:@ "搜索" ]; CGSize size = [_bSearchBar sizeThatFits:_tableView.bounds.size]; _bSearchBar.frame = CGRectMake(0, 0, size.width, size.height); _tableView.tableHeaderView = _byxSearchBar; _bSearchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:_bSearchBar contentsController: self ]; _bSearchDisplayController.searchResultsDataSource = self ; _bSearchDisplayController.searchResultsDelegate = self ; _bSearchDisplayController.delegate = self ; _bSearchDisplayController.searchResultsTitle = @ "" ; _bSearchDisplayController.searchResultsTableView.separatorStyle = UITableViewCellSeparatorStyleNone; |
demo: https://github.com/Jonear/iosDemo/tree/master/TableViewFresh
4.重用
UItableView对Cell有一套重用机制,他会将滚出屏幕外的cell放到一个队列中,滚入屏幕的会从这个队列中获取cell,如果没有再去创建。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = @" cellIdentifier ";
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:windowReuseIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:windowReuseIdentifier];
}
return cell;
}
自定义cell
新建cell文件,继承UITableViewCell
如果你没使用xib或者storyboard的话可以在直接new一个cell,跟普通写法一样
1. 新建cell文件,继承UITableViewCell
2. 如果你没使用xib或者storyboard的话可以在直接new一个cell,跟普通写法一样
1 2 3 4 5 6 | static NSString *cellid = @ "cellIdentifier" ; TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellid]; if (!cell) { cell = [[TableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellid]; } |
3. 如果有xib
1 2 3 4 5 6 | static NSString *CellIdentifier = @ "FriendCell" ; FriendCell *cell= [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (!cell) { [tableView registerNib:[UINib nibWithNibName:@ "FriendCell" bundle: nil ] forCellReuseIdentifier:CellIdentifier]; cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; } |
4.还有个未必好的办法(这个办法也用于在xib多个view中获取到想要的一个)
1 2 3 4 5 6 7 8 9 10 11 | static NSString *reuseId = @ "headCell" ; NADHeadArticleCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseId]; if (!cell) { NSArray *nib = [[ NSBundle mainBundle]loadNibNamed:@ "NADHeadArticleCell" owner: self options: nil ]; for ( id oneObject in nib){ if ([oneObject isKindOfClass:[NADHeadArticleCell class ]]){ cell = (NADHeadArticleCell *)oneObject; break ; } } } |
不使用重用方法
方法1 将获得cell的方法从- (UITableViewCell*)dequeueReusableCellWithIdentifier:(NSString*)identifier 换为-(UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
重用机制调用的就是dequeueReusableCellWithIdentifier这个方法,方法的意思就是“出列可重用的cell”,因而只要将它换为cellForRowAtIndexPath(只从要更新的cell的那一行取出cell),就可以不使用重用机制,因而问题就可以得到解决,虽然可能会浪费一些空间。
第一个方法如果使用下面插入多次可能会有问题:
1 2 | NSIndexPath *indexPath = [ NSIndexPath indexPathForRow:0 inSection:0]; [ self .tableView insertRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; |
方法2 通过为每个cell指定不同的重用标识符(reuseIdentifier)来解决。
重用机制是根据相同的标识符来重用cell的,标识符不同的cell不能彼此重用。于是我们将每个cell的标识符都设置为不同(@"CMainCell%d", indexPath.row)
方法3 删除重用cell的所有子视图
这个方法是通过删除重用的cell的所有子视图,从而得到一个没有特殊格式的cell。
方法4:为不重用的单元格单独生成单独的cell,而不是重用队列中的单元格。
注册Cell
1 2 3 4 5 6 7 8 | // Beginning in iOS 6, clients can register a nib or class for each cell. // If all reuse identifiers are registered, use the newer -dequeueReusableCellWithIdentifier:forIndexPath: to guarantee that a cell instance is returned. // Instances returned from the new dequeue method will also be properly sized when they are returned. - ( void )registerNib:(UINib *)nib forCellReuseIdentifier:( NSString *)identifier NS_AVAILABLE_IOS (5_0); - ( void )registerClass:(Class)cellClass forCellReuseIdentifier:( NSString *)identifier NS_AVAILABLE_IOS (6_0); - ( void )registerNib:(UINib *)nib forHeaderFooterViewReuseIdentifier:( NSString *)identifier NS_AVAILABLE_IOS (6_0); - ( void )registerClass:(Class)aClass forHeaderFooterViewReuseIdentifier:( NSString *)identifier NS_AVAILABLE_IOS (6_0); |
简单讲就是注册完cell后,使用
1 | dequeueReusableCellWithIdentifier:forIndexPath: |
一定会有返回cell,系统在默认没有cell可复用的时候自动new一个cell出来。
5.编辑
1 2 3 4 5 6 7 8 9 10 11 | //是否可编辑 - ( BOOL )tableView:(UITableView *)tableView canEditRowAtIndexPath:( NSIndexPath *)indexPath; //返回编辑的类型1.没有2.删除3.插入 - (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:( NSIndexPath *)indexPath; //删除提示文本 - ( NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:( NSIndexPath *)indexPath NS_AVAILABLE_IOS (3_0); //编辑完成 - ( void )tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:( NSIndexPath *)indexPath; |
滑动更多
继承UITableViewCell,重新初始化方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | - ( void )initializer { int height= self .bounds.size.height; UIScrollView *cellScrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth( self .bounds), height)]; cellScrollView.contentSize = CGSizeMake( self .bounds.size.width+BTN_WIDTH, height); cellScrollView.delegate = self ; cellScrollView.showsHorizontalScrollIndicator = NO ; [cellScrollView setBounces: NO ]; [cellScrollView setPagingEnabled: YES ]; UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget: self action: @selector (scrollViewPressed:)]; [cellScrollView addGestureRecognizer:tapGestureRecognizer]; self .cellScrollView = cellScrollView; UIButton * deleteButton=[[UIButton alloc] initWithFrame:CGRectMake(320, 0, BTN_WIDTH, height-3)]; [deleteButton setTitle:LocalizedString(@ "c_delete" ) forState:UIControlStateNormal]; [deleteButton setImage:[UIImage imageNamed:@ "trash" ] forState:UIControlStateNormal]; [deleteButton setImage:[UIImage imageNamed:@ "trash" ] forState:UIControlStateHighlighted]; [deleteButton.titleLabel setFont:[UIFont systemFontOfSize:12]]; [deleteButton setBackgroundColor:color_with_rgb(211, 49, 49)]; [deleteButton setImageEdgeInsets:UIEdgeInsetsMake(0, 0, 0, 15)]; [deleteButton setTitleEdgeInsets:UIEdgeInsetsMake(0, 5, 0, 0)]; [deleteButton addTarget: self action: @selector (onDeleteCell) forControlEvents:UIControlEventTouchUpInside]; [ self .cellScrollView addSubview:deleteButton]; UIView *scrollViewContentView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth( self .bounds), height)]; scrollViewContentView.backgroundColor = [UIColor whiteColor]; self .scrollViewContentView = scrollViewContentView; [ self .cellScrollView addSubview:scrollViewContentView]; UIView *contentViewParent = self ; if (![ NSStringFromClass ([[ self .subviews objectAtIndex:0] class ]) isEqualToString:@ "UITableViewCellContentView" ]) { // iOS 7 contentViewParent = [ self .subviews objectAtIndex:0]; } NSArray *cellSubviews = [contentViewParent subviews]; [ self insertSubview:cellScrollView atIndex:0]; for (UIView *subview in cellSubviews) { [ self .scrollViewContentView addSubview:subview]; } [ self insertSubview: self .cellScrollView atIndex:0]; } |
demo:http://code4app.com/ios/SWTableViewCell/5269d9376803fa5367000001
6.优化
1.最好改造原生的tableViewCell,尽量不要用xib自定制cell,可以尽量在drawRect中绘制
2.尽量使用重用机制,不要创建太多cell
3.尽量不要使用透明视图,和layer改造
4.重载共同部分可以放在生成Cell部分
5.尽量不要老调用reloaddata,可能的情况下可以考虑使用
1 2 3 4 5 6 7 8 9 | - ( void )insertSections:( NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation; - ( void )deleteSections:( NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation; - ( void )reloadSections:( NSIndexSet *)sections withRowAnimation:(UITableViewRowAnimation)animation NS_AVAILABLE_IOS (3_0); - ( void )moveSection:( NSInteger )section toSection:( NSInteger )newSection NS_AVAILABLE_IOS (5_0); - ( void )insertRowsAtIndexPaths:( NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; - ( void )deleteRowsAtIndexPaths:( NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; - ( void )reloadRowsAtIndexPaths:( NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation NS_AVAILABLE_IOS (3_0); -( void )moveRowAtIndexPath:( NSIndexPath *)indexPath toIndexPath:( NSIndexPath *)newIndexPath NS_AVAILABLE_IOS (5_0); |