标签:uitableview ios 优化
在IOS开发中, UITableView是最常用到的复杂控件. 使用不难, 但想用好却不容易. 需要考虑到后台数据的设计, tableViewCell的设计和优化, 以及tableView的效率等问题.
本文主要介绍一下UITableView的常见优化技巧, 主要参考博客:
VVeboTableViewDemo.
tableView的优化主要思路是:
1. 异步渲染内容到图片。
2. 按照滑动速度按需加载内容。
3. 重写处理网络图片加载。
4. 缓存一切可缓存的, 用空间换时间.
UITableViewCell的重用机制是最常见也是最有效的优化方式之一.
使用dequeueReusableCellWithIdentifier来实现布局相同的cell的重用, 也可以通过cellForRowAtIndexPath 直接复用某个cell. 如
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
WeiboTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (cell == nil) {
cell = [[WeiboTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
[self drawCell:cell withIndexPath:indexPath];
return cell;
]
因为cellForRowAtIndexPath方法调用非常频繁,初始化,上下滚动,刷新都要调用. 所以cell的标示声明为静态变量更好。
static NSString *cellIdentifier = @“Tracks”;
cell的布局填充等操作比较耗时, 一般可在创建时就布局好. 如可将cell单独放到一个class中WeiboTableViewCell, 重写其initWithStyle方法, 在其中将cell的布局设置完成.
创建cell完成之后, 调用drawCell往其中填充内容即可, 即将cell的布局及填充分开执行, 且尽量将要填充的data提前准备好.
- (void)drawCell:(WeiboTableViewCell *)cell withIndexPath:(NSIndexPath *)indexPath {
NSDictionary *data = [datas objectAtIndex:indexPath.row]; // 提前缓存好cell中的内容
cell.selectionStyle = UITableViewCellSelectionStyleNone;
[cell clear];
cell.data = data;
if (needLoadArr.count>0 && [needLoadArr indexOfObject:indexPath]==NSNotFound) {
[cell clear];
return;
}
if (scrollToToping) {
return;
}
[cell draw];
}
因UITableView继承自UIScrollView, 因此其布局主要表现为Plain和Grouped两种风格. 需先确定其contentSize及每个cell的位置, 才会将其放进去. 如:
要显示100个cell,而当前屏幕只能显示5个. 则reload的时候,会先调用100次heightForRowAtIndexPath方法, 然后调用5次cellForRowAtIndexPath方法; 滚动屏幕时, 每当cell进入屏幕, 都会调用一次heightForRowAtIndexPath和cellForRowAtIndexPath方法.
cell填充与计算布局分离.
cellForRowAtIndexPath只填充cell,
heightForRowAtIndexPath负责计算高度, 将高度等布局缓存到数据源中.
对于富文本AttributedString等cell中内容, 可提前创建好, 进行数据缓存, 然后需要时直接往cell中填充即可.
使用estimatedRowHeight来预估高度, 防止浪费时间计算屏幕外边的cell, 如
self.tableView.estimatedRowHeight = 88.
cell内容的异步加载
如web的内容异步加载, 图片配合SDWebImage缓存, 将网络请求结果缓存.
如果有多个不同风格的cell, 可以给每种cell指定不同的重用标识符. 然后使用dequeue每次将其出列使用即可. 如:
NSString *cellId = [NSString stringWithFormat:@“Cell%d%d”, indexPath.section, indexPath.row];
我们常用[self.tableView reloadData]来进行tableView中的数据更新. 如果只是更新某个section的话, 可以使用reloadSections等进行局部的更新
self.tableView reloadRowsAtIndexPaths:<#(NSArray *)#> withRowAnimation:<#(UITableViewRowAnimation)#>
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];
对于复杂的tableView界面, 可考虑异步绘制. 使用dispatch_async和dispatch_sync配合, 将业务逻辑与UI绘制分开. 如:
//异步绘制
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
CGRect rect = [_data[@"frame"] CGRectValue];
UIGraphicsBeginImageContextWithOptions(rect.size, YES, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
//整个内容的背景
[[UIColor colorWithRed:250/255.0 green:250/255.0 blue:250/255.0 alpha:1] set];
CGContextFillRect(context, rect);
//转发内容的背景
if ([_data valueForKey:@"subData"]) {
[[UIColor colorWithRed:243/255.0 green:243/255.0 blue:243/255.0 alpha:1] set];
CGRect subFrame = [_data[@"subData"][@"frame"] CGRectValue];
CGContextFillRect(context, subFrame);
[[UIColor colorWithRed:200/255.0 green:200/255.0 blue:200/255.0 alpha:1] set];
CGContextFillRect(context, CGRectMake(0, subFrame.origin.y, rect.size.width, .5));
}
{
//名字
float leftX = SIZE_GAP_LEFT+SIZE_AVATAR+SIZE_GAP_BIG;
float x = leftX;
float y = (SIZE_AVATAR-(SIZE_FONT_NAME+SIZE_FONT_SUBTITLE+6))/2-2+SIZE_GAP_TOP+SIZE_GAP_SMALL-5;
[_data[@"name"] drawInContext:context withPosition:CGPointMake(x, y) andFont:FontWithSize(SIZE_FONT_NAME)
andTextColor:[UIColor colorWithRed:106/255.0 green:140/255.0 blue:181/255.0 alpha:1]
andHeight:rect.size.height];
//时间+设备
y += SIZE_FONT_NAME+5;
float fromX = leftX;
float size = [UIScreen screenWidth]-leftX;
NSString *from = [NSString stringWithFormat:@"%@ %@", _data[@"time"], _data[@"from"]];
[from drawInContext:context withPosition:CGPointMake(fromX, y) andFont:FontWithSize(SIZE_FONT_SUBTITLE)
andTextColor:[UIColor colorWithRed:178/255.0 green:178/255.0 blue:178/255.0 alpha:1]
andHeight:rect.size.height andWidth:size];
}
//将绘制的内容以图片的形式返回,并调主线程显示
UIImage *temp = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
if (flag==drawColorFlag) {
postBGView.frame = rect;
postBGView.image = nil;
postBGView.image = temp;
}
}
//内容如果是图文混排,就添加View,用CoreText绘制
[self drawText];
}}
NSTimer在UITableViewCell的重用中会失效, 所以不要将timer添加到cell中. 因在滑动tableView时, timer不会触发时间函数. 因它们使用共同的runloop, 而tableView的滑动阻止了timer的时间函数.
可以考虑:
1. 对于显示在cell中的text或其他对象上使用timer.
2. 并行实现, 主要是设置mode参数:
NSTimer* timer = [NSTimer timerWithTimeInterval:0.005 target:self selector:@selector(timerFireMethod:) userInfo:@"finishAnimation" repeats:YES];
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
[currentRunLoop addTimer:timer forMode:NSRunLoopCommonModes];
或者
timer = [NSTimer timerWithTimeInterval:5.0 target:self selector:@selector(SendHeartBeat) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:heartTimer forMode:NSDefaultRunLoopMode];
把不需要接受touch的view的userInteractionEnabled设为0.
添加大量控件会导致资源开销很大, 可以考虑直接绘制drawRect.
这一条暂时还不清楚如何实现, 因此不够详细, 有待进一步补充.
从UIScrollView的角度出发, 对cell进行按需加载, 即滚动很快时候, 只加载目标范围内的cell.
if (needLoadArr.count>0 && [needLoadArr indexOfObject:indexPath]==NSNotFound) {
[cell clear]; return;
}
例如: 如果目标行与当前行相差超过指定行数,只在目标滚动范围的前后指定3行加载。
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
NSIndexPath *ip = [self indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];
NSIndexPath *cip = [[self indexPathsForVisibleRows] firstObject];
NSInteger skipCount = 8;
if (labs(cip.row-ip.row)>skipCount) {
NSArray *temp = [self indexPathsForRowsInRect:CGRectMake(0, targetContentOffset->y, self.width, self.height)];
NSMutableArray *arr = [NSMutableArray arrayWithArray:temp];
if (velocity.y<0) {
NSIndexPath *indexPath = [temp lastObject];
if (indexPath.row+33) {
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row-3 inSection:0]];
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row-2 inSection:0]];
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:0]];
}
}
[needLoadArr addObjectsFromArray:arr];
}
}
使用tableView要遵循两个协议, 而我们只需实现必需要的代理方法即可.
主要提供cell的展示及样式控制, cell的选择, 指定section的头尾显示, 协助完成cell的排序和删除等功能.
//_______________________________________________________________________________________________________________
// this represents the display and behaviour of the cells.
@protocol UITableViewDelegate<NSObject, UIScrollViewDelegate>
@optional
// Display customization
- (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);
- (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
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath;
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section;
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section;
// Use the estimatedHeight methods to quickly calcuate guessed values which will allow for fast load times of the table.
// If these methods are implemented, the above -tableView:heightForXXX calls will be deferred until views are ready to be displayed, so more expensive logic can be placed there.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(7_0);
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section NS_AVAILABLE_IOS(7_0);
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForFooterInSection:(NSInteger)section NS_AVAILABLE_IOS(7_0);
// Section header & footer information. Views are preferred over title should you decide to provide both
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section; // custom view for header. will be adjusted to default or specified header height
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section; // custom view for footer. will be adjusted to default or specified footer height
// Accessories (disclosures).
- (UITableViewCellAccessoryType)tableView:(UITableView *)tableView accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath NS_DEPRECATED_IOS(2_0, 3_0);
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath;
// Selection
// -tableView:shouldHighlightRowAtIndexPath: is called when a touch comes down on a row.
// Returning NO to that message halts the selection process and does not cause the currently selected row to lose its selected look while the touch is down.
- (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);
// Called before the user changes the selection. Return a new indexPath, or nil, to change the proposed selection.
- (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);
// Editing
// Allows customization of the editingStyle for a particular cell located at ‘indexPath‘. If not implemented, all editable cells will have UITableViewCellEditingStyleDelete set for them when the table has editing property set to YES.
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath;
- (NSString *)tableView:(UITableView *)tableView titleForDeleteConfirmationButtonForRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(3_0);
- (NSArray *)tableView:(UITableView *)tableView editActionsForRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(8_0); // supercedes -tableView:titleForDeleteConfirmationButtonForRowAtIndexPath: if return value is non-nil
// Controls whether the background is indented while editing. If not implemented, the default is YES. This is unrelated to the indentation level below. This method only applies to grouped style table views.
- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath;
// The willBegin/didEnd methods are called whenever the ‘editing‘ property is automatically changed by the table (allowing insert/delete/move). This is done by a swipe activating a single row
- (void)tableView:(UITableView*)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath;
- (void)tableView:(UITableView*)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath;
// Moving/reordering
// Allows customization of the target row for a particular row as it is being moved/reordered
- (NSIndexPath *)tableView:(UITableView *)tableView targetIndexPathForMoveFromRowAtIndexPath:(NSIndexPath *)sourceIndexPath toProposedIndexPath:(NSIndexPath *)proposedDestinationIndexPath;
// Indentation
- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath; // return ‘depth‘ of row for hierarchies
// Copy/Paste. All three methods must be implemented by the delegate.
- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath NS_AVAILABLE_IOS(5_0);
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender NS_AVAILABLE_IOS(5_0);
- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender NS_AVAILABLE_IOS(5_0);
@end
以上有非常多的方法, 用于cell属性的控制, 显示风格等.
除了必要的几个, heightForRowAtIndexPath, viewForHeaderInSection, didSelectRowAtIndexPath之外, 如无特殊要求, 可尽量避免实现, 以免耗时.
为UITableView提供显示用的数据, 指定cell的操作类型, 并根据操作及进行相应的数据更新操作. 如数据异常, 则可能导致crash.
//_______________________________________________________________________________________________________________
// this protocol represents the data model object. as such, it supplies no information about appearance (including the cells)
@protocol UITableViewDataSource<NSObject>
@required
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
// Row display. Implementers should *always* try to reuse cells by setting each cell‘s reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
@optional
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; // Default is 1 if not implemented
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; // fixed font style. use custom view (UILabel) if you want something different
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section;
// Editing
// Individual rows can opt out of having the -editing property set for them. If not implemented, all rows are assumed to be editable.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath;
// Moving/reordering
// Allows the reorder accessory view to optionally be shown for a particular row. By default, the reorder control will be shown only if the datasource implements -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#")
- (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
// After a row has the minus or plus button invoked (based on the UITableViewCellEditingStyle for the cell), the dataSource must commit the change
// Not called for edit actions using UITableViewRowAction - the action‘s handler will be invoked instead
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath;
// Data manipulation - reorder / moving support
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath;
@end
常见的方法:
numberOfRowsInSection, cellForRowAtIndexPath, commitEditingStyle等.
版权声明:本文为博主原创文章,未经博主允许不得转载。
标签:uitableview ios 优化
原文地址:http://blog.csdn.net/icetime17/article/details/46709975