iOS --- UITableView的最佳化技巧

來源:互聯網
上載者:User

在iOS開發中, UITableView是最常用到的複雜控制項. 使用不難, 但想用好卻不容易. 需要考慮到後台資料的設計, tableViewCell的設計和最佳化, 以及tableView的效率等問題.
本文主要介紹一下UITableView的常見最佳化技巧, 主要參考部落格:
VVeboTableViewDemo.

tableView的最佳化主要思路是:
1. 非同步渲染內容到圖片。
2. 按照滑動速度按需載入內容。
3. 重寫處理網狀圖片載入。
4. 緩衝一切可快取的, 用空間換時間. 重用cell

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的布局填充等操作比較耗時, 一般可在建立時就布局好. 如可將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];}
提前計算並緩衝cell的屬性及內容

因UITableView繼承自UIScrollView, 因此其布局主要表現為Plain和Grouped兩種風格. 需先確定其contentSize及每個cell的位置, 才會將其放進去. 如:

要顯示100個cell,而當前螢幕只能顯示5個. 則reload的時候,會先調用100次heightForRowAtIndexPath方法, 然後調用5次cellForRowAtIndexPath方法; 滾動螢幕時, 每當cell進入螢幕, 都會調用一次heightForRowAtIndexPath和cellForRowAtIndexPath方法. cellForRowAtIndexPath和heightForRowAtIndexPath是調用最頻繁的方法, 要盡量少地調用這兩個方法.

cell填充與計算布局分離.

cellForRowAtIndexPath只填充cell,
heightForRowAtIndexPath負責計算高度, 將高度等布局緩衝到資料來源中.

對於富文本AttributedString等cell中內容, 可提前建立好, 進行資料緩衝, 然後需要時直接往cell中填充即可.

使用estimatedRowHeight來預估高度, 防止浪費時間計算螢幕外邊的cell, 如

self.tableView.estimatedRowHeight = 88.

cell內容的非同步載入
如web的內容非同步載入, 圖片配合SDWebImage緩衝, 將網路請求結果緩衝. subView的繪製

如果有多個不同風格的cell, 可以給每種cell指定不同的重用標識符. 然後使用dequeue每次將其出列使用即可. 如:

NSString *cellId = [NSString stringWithFormat:@“Cell%d%d”, indexPath.section, indexPath.row]; 
少用addView給cell動態添加view, 減少建立subview的數量如cell大致布局相同, 則可以只定義一種cell, 在初始化時添加, 通過hidden來控制其中內容的顯示. 如有可能, 盡量緩衝subview. 慎用clearColor, 多個view層疊加渲染會消耗更多的時間, 所以盡量不要或者少用透明圖層, 因系統將透明層與下面的view混合起來計算顏色, 渲染速度. 所以, 謹慎使用clearColor. 盡量將opaque設為YES, 盡量將subview的opaque設為YES, 避免GPU對cell其中的內容進行繪製. 避免無用的CALayer渲染特效. 需要繪製陰影的時候,通過指定陰影的路徑提高效率. 重載subView的drawRect方法如果定製cell的過程中需要多個小的元素的話,最好直接對要顯示的多重專案進行繪製,而非採用添加多個subview. UITableView的局部更新

我們常用[self.tableView reloadData]來進行tableView中的資料更新. 如果只是更新某個section的話, 可以使用reloadSections等進行局部的更新

self.tableView reloadRowsAtIndexPaths:<#(NSArray *)#> withRowAnimation:<#(UITableViewRowAnimation)#> [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];
tableView的非同步繪製

對於複雜的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在cell中的失效問題

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事件傳遞

把不需要接受touch的view的userInteractionEnabled設為0. 自訂cell的繪製

添加大量控制項會導致資源開銷很大, 可以考慮直接繪製drawRect.
這一條暫時還不清楚如何?, 因此不夠詳細, 有待進一步補充. cell的按需載入

從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];    }}
不要實現無用的delegate方法

使用tableView要遵循兩個協議, 而我們只需實現必需要的代理方法即可. UITableViewDelegate

主要提供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屬性的控制, 顯示風格等.
除了必要的幾個, heightForRowAtIndexP

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.