至於UITableView的瓶頸在哪裡,我相信網上隨便一搜就能瞭解的大概,我這裡順便提供下資訊點:
1 2 3 |
//罪魁禍首 tableView:cellForRowAtIndexPath: tableView:heightForRowAtIndexPath: |
框架同樣根據這兩個痛點給出瞭解決方案:
高度計算
fd_heightForCellWithIdentifier:configuration方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier configuration:(void (^)(id cell))configuration { if (!identifier) { return 0; } UITableViewCell *templateLayoutCell = [self fd_templateCellForReuseIdentifier:identifier]; // Manually calls to ensure consistent behavior with actual cells. (that are displayed on screen) [templateLayoutCell prepareForReuse]; // Customize and provide content for our template cell. if (configuration) { configuration(templateLayoutCell); } return [self fd_systemFittingHeightForConfiguratedCell:templateLayoutCell]; } |
這裡先是通過呼叫fd_templateCellForReuseIdentifier:從dequeueReusableCellWithIdentifier取出之後,如果需要做一些額外的計算,比如說計算cell高度, 可以手動呼叫 prepareForReuse方法,以確保與實際cell(顯示在螢幕上)行為一致。接著執行configuration引數對Cell內容進行配置。最後通過呼叫fd_systemFittingHeightForConfiguratedCell:方法計算實際的高度並返回。
fd_systemFittingHeightForConfiguratedCell方法
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
- (CGFloat)fd_systemFittingHeightForConfiguratedCell:(UITableViewCell *)cell { CGFloat contentViewWidth = CGRectGetWidth(self.frame); // If a cell has accessory view or system accessory type, its content view's width is smaller // than cell's by some fixed values. if (cell.accessoryView) { contentViewWidth -= 16 + CGRectGetWidth(cell.accessoryView.frame); } else { static const CGFloat systemAccessoryWidths[] = { [UITableViewCellAccessoryNone] = 0, [UITableViewCellAccessoryDisclosureIndicator] = 34, [UITableViewCellAccessoryDetailDisclosureButton] = 68, [UITableViewCellAccessoryCheckmark] = 40, [UITableViewCellAccessoryDetailButton] = 48 }; contentViewWidth -= systemAccessoryWidths[cell.accessoryType]; } // If not using auto layout, you have to override "-sizeThatFits:" to provide a fitting size by yourself. // This is the same height calculation passes used in iOS8 self-sizing cell's implementation. // // 1. Try "- systemLayoutSizeFittingSize:" first. (skip this step if 'fd_enforceFrameLayout' set to YES.) // 2. Warning once if step 1 still returns 0 when using AutoLayout // 3. Try "- sizeThatFits:" if step 1 returns 0 // 4. Use a valid height or default row height (44) if not exist one CGFloat fittingHeight = 0; if (!cell.fd_enforceFrameLayout & contentViewWidth > 0) { // Add a hard width constraint to make dynamic content views (like labels) expand vertically instead // of growing horizontally, in a flow-layout manner. NSLayoutConstraint *widthFenceConstraint = [NSLayoutConstraint constraintWithItem:cell.contentView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:1.0 constant:contentViewWidth]; [cell.contentView addConstraint:widthFenceConstraint]; // Auto layout engine does its math fittingHeight = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; [cell.contentView removeConstraint:widthFenceConstraint]; [self fd_debugLog:[NSString stringWithFormat:@"calculate using system fitting size (AutoLayout) - %@", @(fittingHeight)]]; } if (fittingHeight == 0) { #if DEBUG // Warn if using AutoLayout but get zero height. if (cell.contentView.constraints.count > 0) { if (!objc_getAssociatedObject(self, _cmd)) { NSLog(@"[FDTemplateLayoutCell] Warning once only: Cannot get a proper cell height (now 0) from '- systemFittingSize:'(AutoLayout). You should check how constraints are built in cell, making it into 'self-sizing' cell."); objc_setAssociatedObject(self, _cmd, @YES, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } } #endif // Try '- sizeThatFits:' for frame layout. // Note: fitting height should not include separator view. fittingHeight = [cell sizeThatFits:CGSizeMake(contentViewWidth, 0)].height; [self fd_debugLog:[NSString stringWithFormat:@"calculate using sizeThatFits - %@", @(fittingHeight)]]; } // Still zero height after all above. if (fittingHeight == 0) { // Use default row height. fittingHeight = 44; } // Add 1px extra space for separator line if needed, simulating default UITableViewCell. if (self.separatorStyle != UITableViewCellSeparatorStyleNone) { fittingHeight += 1.0 / [UIScreen mainScreen].scale; } return fittingHeight; } |
這裡作者考慮到了如果Cell使用了accessory view或者使用了系統的accessory type,需要減掉相應的寬度。接著判斷如果使用了AutoLayout,則使用iOS 6提供的systemLayoutSizeFittingSize方法獲取高度。如果高度為0,則嘗試使用Frame Layout的方式,呼叫重寫的sizeThatFits方法進行獲取。如果還是為0,則給出預設高度並返回。
Cell重用
fd_templateCellForReuseIdentifier方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
- (__kindof UITableViewCell *)fd_templateCellForReuseIdentifier:(NSString *)identifier { NSAssert(identifier.length > 0, @"Expect a valid identifier - %@", identifier); NSMutableDictionary *templateCellsByIdentifiers = objc_getAssociatedObject(self, _cmd); if (!templateCellsByIdentifiers) { templateCellsByIdentifiers = @{}.mutableCopy; objc_setAssociatedObject(self, _cmd, templateCellsByIdentifiers, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } UITableViewCell *templateCell = templateCellsByIdentifiers[identifier]; if (!templateCell) { templateCell = [self dequeueReusableCellWithIdentifier:identifier]; NSAssert(templateCell != nil, @"Cell must be registered to table view for identifier - %@", identifier); templateCell.fd_isTemplateLayoutCell = YES; templateCell.contentView.translatesAutoresizingMaskIntoConstraints = NO; templateCellsByIdentifiers[identifier] = templateCell; [self fd_debugLog:[NSString stringWithFormat:@"layout cell created - %@", identifier]]; } return templateCell; } |
這裡通過dequeueReusableCellWithIdentifier方法從佇列中獲取templateCell,並通過fd_isTemplateLayoutCell屬性標識其只用來充當模板計算,並不真正進行呈現,最後通過關聯物件的方式進行存取。
注意:這裡通過dequeueReusableCellWithIdentifier進行獲取,也就意味著你必須對指定的Identifier先進行註冊,註冊可以通過以下三中方法:
1 2 3 |
1.使用storyboard中的Cell原型 2.使用registerNib:forCellReuseIdentifier: 3.使用registerClass:forCellReuseIdentifier: |
到這裡最重要的幾個方法已經講完了,除此之外框架還針對獲取的高度進行了快取。快取的方式分為兩種 :
1 2 |
1.根據IndexPath進行快取(fd_heightForCellWithIdentifier:cacheByIndexPath:configuration) 2.根據實體的唯一識別符號進行快取(fd_heightForCellWithIdentifier:cacheByKey:configuration) |
總結:
UITableView優化方案其實還有很多,不同的場景選用不同的方案,實現效果達到預期,這才是我麼最終的目標。我這裡簡單介紹下其他的優化的細節:
1 2 3 4 |
1.複雜介面的時候,我們可以嘗試非同步手動進行繪製。 2.針對超出螢幕的Cell進行預快取 3.存在大量圖片的時候,只針對目標範圍內的圖片進行非同步載入並快取結果。 4.設定Views/Layers為不透明。 |
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式