UITableView-FDTemplateLayoutCell 學習筆記

本文轉載至 http://www.tuicool.com/articles/I7ji2uM

Basic Information

  • Name : UITableView-FDTemplateLayoutCell
  • Site : https://github.com/forkingdog/UITableView-FDTemplateLayoutCell
  • Repo : https://github.com/forkingdog/UITableView-FDTemplateLayoutCell
  • Revision : e3ee86ce419d18d3ff735056f1474f2863e43003
  • Description : 簡單易用的UITableViewCell自動高度。 做者的博客文章 http://blog.sunnyxx.com/2015/05/17/cell-height-calculation/

Global Note

簡單易用,但在一些複雜界面(例如聊天窗口)中使用時仍是須要考慮更多優化問題。java

File Notes

0. UITableView+FDTemplateLayoutCell.h

  • Path : /Classes/UITableView+FDTemplateLayoutCell.h
  • Line : 35 - 35
  • Note :
- (__kindof UITableViewCell *)fd_templateCellForReuseIdentifier:(NSString *)identifier;

__kindof XXXClass 能夠這麼用git

1. UITableView+FDTemplateLayoutCell.h

  • Path : /Classes/UITableView+FDTemplateLayoutCell.h
  • Line : 28 - 28
  • Note :
@interface UITableView (FDTemplateLayoutCell)

UITableView的extensiongithub

2. UITableView+FDTemplateLayoutCell.h

  • Path : /Classes/UITableView+FDTemplateLayoutCell.h
  • Line : 87 - 99
  • Note :
@interface UITableViewCell (FDTemplateLayoutCell) /// Indicate this is a template layout cell for calculation only. /// You may need this when there are non-UI side effects when configure a cell. /// Like: /// - (void)configureCell:(FooCell *)cell atIndexPath:(NSIndexPath *)indexPath { /// cell.entity = [self entityAtIndexPath:indexPath]; /// if (!cell.fd_isTemplateLayoutCell) { /// [self notifySomething]; // non-UI side effects /// } /// } /// @property (nonatomic, assign) BOOL fd_isTemplateLayoutCell;

使用 UITableViewCell 模板Cell計算高度,經過 fd_isTemplateLayoutCell 可在Cell內部判斷當前是不是模板Cell。能夠省去一些與高度無關的操做。sql

3. UITableView+FDTemplateLayoutCell.m

  • Path : /Classes/UITableView+FDTemplateLayoutCell.m
  • Line : 221 - 229
  • Note :
@implementation UITableViewCell (FDTemplateLayoutCell) - (BOOL)fd_isTemplateLayoutCell { return [objc_getAssociatedObject(self, _cmd) boolValue]; } - (void)setFd_isTemplateLayoutCell:(BOOL)isTemplateLayoutCell { objc_setAssociatedObject(self, @selector(fd_isTemplateLayoutCell), @(isTemplateLayoutCell), OBJC_ASSOCIATION_RETAIN); }

使用runtime增長屬性的實現。數組

SEL類型的_cmd , 每一個方法內部都有,表示方法自身。 所以,能夠NSStringFromSelector(_cmd)返回當前方法名稱。緩存

使用 get的SEL(也就是_cmd)做爲objc_getAssociatedObject的key。值得學習。但要注意set中也要用相同的key,也就是@selector(fd_isTemplateLayoutCell)。ruby

4. UITableView+FDTemplateLayoutCell.m

  • Path : /Classes/UITableView+FDTemplateLayoutCell.m
  • Line : 36 - 43
  • Note :
static const CGFloat systemAccessoryWidths[] = { [UITableViewCellAccessoryNone] = 0, [UITableViewCellAccessoryDisclosureIndicator] = 34, [UITableViewCellAccessoryDetailDisclosureButton] = 68, [UITableViewCellAccessoryCheckmark] = 40, [UITableViewCellAccessoryDetailButton] = 48 }; contentViewWidth -= systemAccessoryWidths[cell.accessoryType];

指定索引定義數組的方式。oc的小技巧真很多。ide

5. UITableView+FDTemplateLayoutCell.m

  • Path : /Classes/UITableView+FDTemplateLayoutCell.m
  • Line : 57 - 64
  • Note :
// 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];

AutoLayout 計算Size的方法 systemLayoutSizeFittingSize。 這裏新增一個寬度的約束,計算高度後再移除掉。 不錯的想法。函數

6. UITableView+FDTemplateLayoutCell.m

  • Path : /Classes/UITableView+FDTemplateLayoutCell.m
  • Line : 79 - 81
  • Note :
// Try '- sizeThatFits:' for frame layout. // Note: fitting height should not include separator view. fittingHeight = [cell sizeThatFits:CGSizeMake(contentViewWidth, 0)].height;

不使用AutoLayout的狀況下,使用sizeThatFits來獲取大小。自定義cell須要實現這個函數。學習

7. UITableView+FDTemplateLayoutCell.m

  • Path : /Classes/UITableView+FDTemplateLayoutCell.m
  • Line : 100 - 121
  • Note :
- (__kindof UITableViewCell *)fd_templateCellForReuseIdentifier:(NSString *)identifier { NSAssert(identifier.length > 0, @"Expect a valid identifier - %@", identifier); NSMutableDictionary<NSString *, UITableViewCell *> *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; }

關鍵的函數。

  • 給當前TableView關聯一個可變字典。
  • 每一種 CellIdentifier 一個Key。
  • 用於存儲模板Cell。
  • 模板Cell用於在內存中構建Cell,並對這個模板Cell計算高度。

8. UITableView+FDIndexPathHeightCache.m

  • Path : /Classes/UITableView+FDIndexPathHeightCache.m
  • Line : 26 - 26
  • Note :
typedef NSMutableArray<NSMutableArray<NSNumber *> *> FDIndexPathHeightsBySection;

緩存高度。每一個section一個數組。

9. UITableView+FDIndexPathHeightCache.m

  • Path : /Classes/UITableView+FDIndexPathHeightCache.m
  • Line : 29 - 30
  • Note :
@property (nonatomic, strong) FDIndexPathHeightsBySection *heightsBySectionForPortrait; @property (nonatomic, strong) FDIndexPathHeightsBySection *heightsBySectionForLandscape;

橫屏豎屏各自緩存。

10. UITableView+FDIndexPathHeightCache.m

  • Path : /Classes/UITableView+FDIndexPathHeightCache.m
  • Line : 45 - 45
  • Note :
return UIDeviceOrientationIsPortrait([UIDevice currentDevice].orientation) ? self.heightsBySectionForPortrait: self.heightsBySectionForLandscape;

判斷橫豎屏

11. UITableView+FDIndexPathHeightCache.m

  • Path : /Classes/UITableView+FDIndexPathHeightCache.m
  • Line : 65 - 73
  • Note :
- (CGFloat)heightForIndexPath:(NSIndexPath *)indexPath {
    [self buildCachesAtIndexPathsIfNeeded:@[indexPath]];
    NSNumber *number = self.heightsBySectionForCurrentOrientation[indexPath.section][indexPath.row];
#if CGFLOAT_IS_DOUBLE return number.doubleValue; #else return number.floatValue; #endif }

CGFLOAT_IS_DOUBLE 注意這個。

12. UITableView+FDIndexPathHeightCache.m

  • Path : /Classes/UITableView+FDIndexPathHeightCache.m
  • Line : 124 - 124
  • Note :
[self methodSignatureForSelector:nil];

這個沒看懂啊 NSMethodSignature

13. UITableView+FDIndexPathHeightCache.m

  • Path : /Classes/UITableView+FDIndexPathHeightCache.m
  • Line : 133 - 145
  • Note :
// We just forward primary call, in crash report, top most method in stack maybe FD's, // but it's really not our bug, you should check whether your table view's data source and // displaying cells are not matched when reloading. static void __FD_TEMPLATE_LAYOUT_CELL_PRIMARY_CALL_IF_CRASH_NOT_OUR_BUG__(void (^callout)(void)) { callout(); } #define FDPrimaryCall(...) do {__FD_TEMPLATE_LAYOUT_CELL_PRIMARY_CALL_IF_CRASH_NOT_OUR_BUG__(^{__VA_ARGS__});} while(0) @implementation UITableView (FDIndexPathHeightCacheInvalidation) - (void)fd_reloadDataWithoutInvalidateIndexPathHeightCache { FDPrimaryCall([self fd_reloadData];); }

一個奇技淫巧。看來這樣能夠在棧回朔中顯示出這個「提示用的」方法名稱。

14. UITableView+FDIndexPathHeightCache.m

  • Path : /Classes/UITableView+FDIndexPathHeightCache.m
  • Line : 147 - 168
  • Note :
+ (void)load { // All methods that trigger height cache's invalidation SEL selectors[] = { @selector(reloadData), @selector(insertSections:withRowAnimation:), @selector(deleteSections:withRowAnimation:), @selector(reloadSections:withRowAnimation:), @selector(moveSection:toSection:), @selector(insertRowsAtIndexPaths:withRowAnimation:), @selector(deleteRowsAtIndexPaths:withRowAnimation:), @selector(reloadRowsAtIndexPaths:withRowAnimation:), @selector(moveRowAtIndexPath:toIndexPath:) }; for (NSUInteger index = 0; index < sizeof(selectors) / sizeof(SEL); ++index) { SEL originalSelector = selectors[index]; SEL swizzledSelector = NSSelectorFromString([@"fd_" stringByAppendingString:NSStringFromSelector(originalSelector)]); Method originalMethod = class_getInstanceMethod(self, originalSelector); Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector); method_exchangeImplementations(originalMethod, swizzledMethod); } }

神奇的load方法。當第一次看到load方法,驚呼Objective C真是太靈活了。

load 文檔參考以下:

Invoked whenever a class or category is added to the Objective-C runtime; implement this method to perform class-specific behavior upon loading.
The load message is sent to classes and categories that are both dynamically loaded and statically linked, but only if the newly loaded class or category implements a method that can respond. The order of initialization is as follows: All initializers in any framework you link to. All +load methods in your image. All C++ static initializers and C/C++ __attribute__(constructor) functions in your image. All initializers in frameworks that link to you. In addition: A class’s +load method is called after all of its superclasses’ +load methods. A category +load method is called after the class’s own +load method. In a custom implementation of load you can therefore safely message other unrelated classes from the same image, but any load methods implemented by those classes may not have run yet. 

15. UITableView+FDIndexPathHeightCache.m

  • Path : /Classes/UITableView+FDIndexPathHeightCache.m
  • Line : 162 - 166
  • Note :
SEL originalSelector = selectors[index]; SEL swizzledSelector = NSSelectorFromString([@"fd_" stringByAppendingString:NSStringFromSelector(originalSelector)]); Method originalMethod = class_getInstanceMethod(self, originalSelector); Method swizzledMethod = class_getInstanceMethod(self, swizzledSelector); method_exchangeImplementations(originalMethod, swizzledMethod);

method_exchangeImplementations 能夠交換兩個Method。

相似作Windows開發時的API HOOK(detours、mhook),這Objective C的Runtime都給提供好了,更上層一些。

俗稱「swizzle method」。

16. UITableView+FDKeyedHeightCache.m

  • Path : /Classes/UITableView+FDKeyedHeightCache.m
  • Line : 75 - 86
  • Note :
@implementation UITableView (FDKeyedHeightCache) - (FDKeyedHeightCache *)fd_keyedHeightCache { FDKeyedHeightCache *cache = objc_getAssociatedObject(self, _cmd); if (!cache) { cache = [FDKeyedHeightCache new]; objc_setAssociatedObject(self, _cmd, cache, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } return cache; } @end

每一個TableView關聯一個高度緩存

17. UITableView+FDTemplateLayoutCellDebug.h

  • Path : /Classes/UITableView+FDTemplateLayoutCellDebug.h
  • Line : 25 - 25
  • Note :
@interface UITableView (FDTemplateLayoutCellDebug)

附加功能推薦用Category這種方式增長。

Summarize

相關文章
相關標籤/搜索