设置高度的方式
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:)