UITableView高度优化

设置高度的方式

UITableView的高度设置一般有以下两种方式:
方式一

self.tableView.rowHeight = 44;

方式二

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    return 44;
}

如果实现了以上方法后,通过rowHeight 的设置是无效的。第二种方式适用于具有多种 cell 高度的UITableView。

对于显示效率来说,明细是第一种方式更加高效,UITableView的rowHeight的默认值是44,如果没有使用以上两种方式设置,UITableView的cell还是有默认的44高度的。

iOS 7.0 的预估高度

UITableView在iOS 7的时候引入一个属性estimatedRowHeight,通过设置UITableView的属性estimatedRowHeight。可以设置UITableView的预估Cell高度,对于包含不同高度的cell,可以让 高度的计算推迟到滚动时再计算,而不是在加载时计算全部cell高度。(因为UITableView在加载的时候要计算contentSize来确定如滚动条的大小显示)

但是这么设置一样会有以下的以下问题

  • 因为高度的计算由加载推迟到了滚动,其实计算量并没有少,滚动时计算高度可能也会感觉到卡顿;
  • 因为设置了预估高度,所以UITableView在加载时会算出自身的contentSize,当滚动时,计算出真正的高度,就会导致contentSize的变动,这时就会导致UITableView的滚动条会闪动跳变。

iOS 8.0的self-sizing cell

iOS 8.0中引入了一个新的概念self-sizing cell,字面意思就是cell关心自身布局即可,无需关心Cell的高度,通过如下设置便可

self.tableView.rowHeight = UITableViewAutomaticDimension;//iOS8默认就是这个,可以省略

这样一来,开发者便不用关心预估高度,也不用复写
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

要注意的是,Cell里面的View需要添加到self.contentView,然后确保布局的Top和Bottom是正确的便可。

如果需要self-sizing cell与frame cell共存,
可以如下设置

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.section > 0) {
        return UITableViewAutomaticDimension;
    }else {
        return 100;
    }
}

frame cell的高度缓存

对于frame cell,如果高度的计算是比较耗时的,我们可以对UITableView的高度做缓存,这样可以让UITableView滚动时,计算高度可以快速得到高度值返回。

建立UITableView高度缓存Category

大概代码如下,缓存可以使用第三方框架YYCache来实现。

#import "UITableView+HeightCache.h"
#import 
#import 

@interface UITableView ()

@property (nonatomic, strong) YYCache *cacheManager;

@end

@implementation UITableView (HeightCache)

+(void)load{
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
      @autoreleasepool {
          addAndExchangeMethod(self.class, @selector(setDelegate:), self.class, @selector(my_setDelegate:));
      }
  });
}

static inline void my_addAndExchangeMethod(Class originalClass, SEL originalSel, Class replacedClass, SEL replacedSel)
{
  Method originalMethod = class_getInstanceMethod(originalClass, originalSel);
  Method replacedMethod = class_getInstanceMethod(replacedClass, replacedSel);
  BOOL didAddMethod = class_addMethod(originalClass, replacedSel, method_getImplementation(replacedMethod), method_getTypeEncoding(replacedMethod));
  if (didAddMethod) {
      Method newMethod = class_getInstanceMethod(originalClass, replacedSel);
      method_exchangeImplementations(originalMethod, newMethod);
  }else {
      method_exchangeImplementations(originalMethod, replacedMethod);
  }
}

- (void)my_setDelegate:(id)delegate{
  [self my_setDelegate:delegate];
  if ([delegate respondsToSelector:@selector(tableView:heightForRowAtIndexPath:)]) {
      static dispatch_once_t onceToken;
      dispatch_once(&onceToken, ^{
          @autoreleasepool {
              my_addAndExchangeMethod([delegate class], @selector(tableView:heightForRowAtIndexPath:), self.class, @selector(my_tableView:heightForRowAtIndexPath:));
          }
      });
      
  }
}

- (CGFloat)my_tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
  // Hit cache
  NSString *key = [NSString stringWithFormat:@"tableViewHeightKey-%p-%ld-%ld",tableView,indexPath.section,indexPath.row];
  NSNumber *heightNumber = [tableView.cacheManager.memoryCache objectForKey:key];
  if (heightNumber) {
      CGFloat cachedHeight = heightNumber.floatValue;
      return cachedHeight;
  }
  //New
  CGFloat height = [self my_tableView:tableView heightForRowAtIndexPath:indexPath];
  if (height > 0) {
      heightNumber = [NSNumber numberWithFloat:height];
      [tableView.cacheManager.memoryCache setObject:heightNumber forKey:key];
  }
  return height;
}

#pragma mark – setter && getter

- (YYCache *)cacheManager {
  YYCache *cache = objc_getAssociatedObject(self, _cmd);
  if (!cache) {
      NSString *cacheName = [NSString stringWithFormat:@"tableViewHeightCacheMgr-%p",self];
      cache = [[YYCache alloc]initWithName:cacheName];
      objc_setAssociatedObject(self, _cmd, cache, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  }
  return cache;
}
@end

自动刷新缓存

可以继续hook
tableview的插入、删除、刷新方法,实现缓存的刷新。
如hook如下方法
@selector(insertRowsAtIndexPaths:withRowAnimation:)

你可能感兴趣的:(UITableView高度优化)