美文网首页ios实用开发技巧
UITableView Self Sizing Cells 到底

UITableView Self Sizing Cells 到底

作者: 灰豹儿 | 来源:发表于2017-12-03 19:05 被阅读0次

    随着iOS11的到来,开发app支持的最低版本也就是iOS8了。iOS8中添加的Self Sizing Cells功能有没有使用过呢?比自己计算cell的高度真的快很多吗?今天写个demo来看一看。demo中会创建两个Controller,分别使用手动计算cell高度的方式和cell自己计算高度的方式加载同一类型同样数据的cell。
    Demo地址:https://github.com/huibaoer/Demo_TableViewOptimize.git

    1. 编写自定义cell

    TableViewCell是一个能展示图片和文字,或者只展示文字的cell,为了快速编写,使用xib方式创建。cell上方是一个640:530的imageView,下方是一个展示文字的label。当传进来的数据有图片和文字的时候就同时显示;当传进来的数据只有文字的时候就隐藏imageView只显示文字。约束一般情况下动态修改都是修改constant值,但是imageView没有高度约束,它的高度是由宽高比计算来的,想使用通过改变imageView的高度达到只显示文字的效果不是很好实现。所以为label分别添加了两个top约束,通过active属性控制哪个约束生效来达到展示图文或者只展示文字的效果。
    创建问基本UI后,为cell添加手动计算高度的方法。

    + (CGFloat)cellHeightWithDictionary:(NSDictionary *)dic {
        CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
        NSString *image = dic[@"image"];
        CGFloat imageHeight = -10;
        if (image) {
            imageHeight = (screenWidth-32)*530.0/640;
        }
        NSString *text = dic[@"text"];
        NSDictionary *attributes = @{NSFontAttributeName : [UIFont systemFontOfSize:17.f ]};
        CGRect rect = [text boundingRectWithSize:CGSizeMake(screenWidth-32, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading  attributes:attributes context:nil];
    
        return 16 + imageHeight + 10 + ceil(rect.size.height) + 16 + 0.5;//系统分割线占了0.5
    }
    

    添加数据的方法,这里cell根据传入的数据不同,显示不同的内容和样式。

    - (void)setDictionary:(NSDictionary *)dic {
        _descLabel.text = dic[@"text"];
    
        NSString *image = dic[@"image"];
        if (image) {
            _labelTop2Image.active = YES;
            _labelTop2ContentView.active = NO;
            _mediaImgView.hidden = NO;
            _mediaImgView.image = [UIImage imageNamed:image];
        } else {
            _labelTop2Image.active = NO;
            _labelTop2ContentView.active = YES;
            _mediaImgView.hidden = YES;
        }
        [self setNeedsLayout];
    }
    
    2. 创建一点假数据

    DataAccess类负责数据的提供,demo中就直接本地随机创建的假数据。为了保证两个Controller获取的数据一致。DataAccess就只有在单例初始化的时候创建了一次数据。

    static DataAccess *instance = nil;
    + (instancetype)sharedInstance {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            instance = [[[self class] alloc] init];
        });
        return instance;
    }
    
    - (instancetype)init {
        self = [super init];
        if (self) {
            _array = [[self class] getData];
        }
        return self;
    }
    
    + (NSArray *)getData {
        NSMutableArray *arr = [NSMutableArray array];
        for (int i = 0; i < 99; i++) {
            NSDictionary *dic = [self generateCellData];
            [arr addObject:dic];
        }
        return arr;
    }
    
    + (NSDictionary *)generateCellData {
        NSMutableString *str = [NSMutableString string];
        int random = arc4random()%30 + 1;
        for (int i = 0; i < random; i++) {
            [str appendString:@"随机生成的内容"];
        }
        NSString *imageName = @"eva_image";
        if (random % 3 == 0) {
            imageName = nil;
        }
        NSMutableDictionary *dic = [NSMutableDictionary dictionaryWithObject:str forKey:@"text"];
        if (imageName) {
            [dic setObject:imageName forKey:@"image"];
        }
        return dic;
    }
    
    3. 手动计算cell高度的tableView

    在FirstViewController中我们让tableView用手动方式计算cell, heightForRowAtIndexPath方法将会被调用多次来计算每个要展示的cell的高度

    @interface FirstViewController () <UITableViewDataSource, UITableViewDelegate>
    @property (nonatomic, strong) NSArray *dataArray;
    @property (nonatomic, strong) UITableView *tableView;
    @end
    
    @implementation FirstViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
        _dataArray = [DataAccess sharedInstance].array;
    
        _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
        _tableView.dataSource = self;
        _tableView.delegate = self;
        [_tableView registerNib:[UINib nibWithNibName:@"TableViewCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"cell"];
        [self.view addSubview:_tableView];
    
    }
    
    #pragma mark - tableView
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return _dataArray.count;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        NSDictionary *dic = _dataArray[indexPath.row];
        TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
        [cell setDictionary:dic];
        return cell;
    }
    
    - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        NSDictionary *dic = _dataArray[indexPath.row];
        CGFloat cellHeight = [TableViewCell cellHeightWithDictionary:dic];
        return cellHeight;
    }
    @end
    
    
    4. cell 自动计算高度

    SecondViewController中使用自动计算高度的方式,对比下代码发现去掉了heightForRowAtIndexPath方法,多了estimatedRowHeight和rowHeight两个属性的设置。因为cell内部UI元素的纵向约束都已经加好了,所以cell就可以根据具体的内容自动计算高度撑起来。iOS11中estimatedRowHeight属性默认是开启的,看来苹果已经很推荐我们使用这种方式了,不过一些老代码可能需要将其关闭。

    @interface SecondViewController () <UITableViewDataSource, UITableViewDelegate>
    @property (nonatomic, strong) NSArray *dataArray;
    @property (nonatomic, strong) UITableView *tableView;
    
    @end
    
    @implementation SecondViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view.
    
        _dataArray = [DataAccess sharedInstance].array;
    
        _tableView = [[UITableView alloc] initWithFrame:self.view.bounds style:UITableViewStylePlain];
        _tableView.dataSource = self;
        _tableView.delegate = self;
        _tableView.estimatedRowHeight = 300;
        _tableView.rowHeight = UITableViewAutomaticDimension;
        [_tableView registerNib:[UINib nibWithNibName:@"TableViewCell" bundle:[NSBundle mainBundle]] forCellReuseIdentifier:@"cell"];
        [self.view addSubview:_tableView];
    }
    
    #pragma mark - tableView
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return _dataArray.count;
    }
    
    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
        TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
        NSDictionary *dic = [_dataArray objectAtIndex:indexPath.row];
        [cell setDictionary:dic];
        return cell;
    }
    
    
    5. 看看结果

    demo写完了,我们使用 Instruments 下的 Time Profiler 看看两个页面的tableView的耗时情况,连接真机,开始启动后,我分别将两个tableView的内容滚动到了最低端,没有做其他的操作。可以看出耗时最高的就是手动计算cell高度的方法。看来使用Self Sizing cells还是能提升tableView的不少性能的。


    Demo_TableViewOptimize.png
    6. TableView的一些其他优化
    • cell复用,cell的复用是tableView展示内容的一个良好机制,可以有效的减少cell对象的个数减少内存占用,需要注意的是根据业务需要尽量减少cell的类型,理论上可以减少cell复用池中的总个数。另外自定义cell的一些UI不要在cell的调用过程中频繁创建,尽量在cell创建时只创建一次,通过显示隐藏等方式使cell展示不同的样式。nib文件可以方便我们创建UI添加约束,但是相较于纯代码编写,纯代码的效率更高,可根据实际情况取舍。
    • cell高度的计算,传统的手动计算cell高度的方式,尽量避免在heightForRow方法中频繁计算,可考虑在数据获取后,统一计算一次cell高度存起来,在heightForRow方法中直接返回。现在Self Sizing Cells是另一种更好的优化方式。
    • tableView在快速滚动时,会频繁调用cellForRow方法获取cell,尽量快速的返回cell可以避免由于等待cell的返回造成的卡顿。建议在cellForRow方法中不要对cell做过多的设置和数据绑定,可以将这些操作放在willDisplayCell回调中。
    • 在实际项目中,往往性能瓶颈在网络请求,适当的做一些缓存可以提升用户体验。

    相关文章

      网友评论

        本文标题:UITableView Self Sizing Cells 到底

        本文链接:https://www.haomeiwen.com/subject/gxqzbxtx.html