Cell嵌套UITableView自动布局

简介:

前言

最近有很多小伙伴们都问我有没有cell里面再嵌套tableview的demo,老说不知道怎么做,不知道怎么计算高度啊。其实这个很简单的,昨天晚上正好有点时间,写了这个demo。

本篇文章只讲如何在cell中嵌套UITableView,只是粗浅知识点,只教大家基本的如何去计算UITableview的高度。这里模拟评论写了个demo,增加或者删除一条评论都可以马上更新,得到正确的显示。

效果图

image

这里使用了100条数据,但是界面并不卡。这里使用了笔者所开源的自动计算cell的高度来计算行高的,而且这是带缓存功能的。用于计算的cell是重用的,因此效率是非常高的。

详细使用教程,可阅读开源HYBMasonryAutoCellHeight自动计算行高

实现思路

笔者尝试画了一张图,尽量反映出实现的思路:

image

外层UITableView通过HYBMasonryAutoCellHeight计算cell的高度并缓存起来,而cell中所嵌套的UITableView(显示评论信息的表格)也是通过HYBMasonryAutoCellHeight来计算cell的高度并缓存。当评论TableView增加或者删除一条数据时,通过代理反馈到外层TableView,然后重装计算行高并更新缓存。

数据建模

这里使用了两个模型类HYBTestModel是外层UITableView的数据源模型,而HYBCommentModel是评论UITableView的数据源模型。

HYBTestModel

@interface HYBTestModel : NSObject

@property (nonatomic, copy) NSString *uid;
@property (nonatomic, copy) NSString *title;
@property (nonatomic, copy) NSString *desc;
@property (nonatomic, copy) NSString *headImage;

// 评论数据源
@property (nonatomic, strong) NSMutableArray *commentModels;

// 因为评论是动态的,因此要标识是否要更新缓存
@property (nonatomic, assign) BOOL shouldUpdateCache;

@end

其中,uid必须保证唯一,它是用来缓存高度的唯一标识符,通常model的id作为uniqueKey。增加shouldUpdateCache属性的目的是在增加或者删除评论时,以识别是否需要重新计算行高并刷新对应的缓存高度。

HYBCommentModel

@interface HYBCommentModel : NSObject

@property (nonatomic, copy) NSString *cid;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *reply;
@property (nonatomic, copy) NSString *comment;

@end

这里的cid是指评论id,它是作为缓存行高的key。

外层HYBTestCell

这个是外层UITableView所需要使用的cell,它里面会嵌套着评论的UITableView。当评论内容发生变化时,通过代理来实现数据的刷新,行高的重新计算与缓存。

@class HYBTestModel;

@protocol HYBTestCellDelegate <NSObject>

- (void)reloadCellHeightForModel:(HYBTestModel *)model atIndexPath:(NSIndexPath *)indexPath;

@end

@interface HYBTestCell : UITableViewCell

@property (nonatomic, weak) id delegate;

- (void)configCellWithModel:(HYBTestModel *)model indexPath:(NSIndexPath *)indexPath;

@end

当我们配置其数据时,我们要计算出评论的tablview的高度。这里是通过HYBMasonryAutoCellHeight这个开源库来实现的。当配置数据时,通过遍历所有的评论模型,然后计算cell的行高,累加起来就是Tablview的高度,然后刷新tableview的约束。这样就可以正常地计算出整个外部cell的高度了。

- (void)configCellWithModel:(HYBTestModel *)model indexPath:(NSIndexPath *)indexPath {
  self.indexPath = indexPath;

  self.titleLabel.text = model.title;
  self.descLabel.text = model.desc;
  self.headImageView.image = [UIImage imageNamed:model.headImage];

  self.testModel = model;
  CGFloat tableViewHeight = 0;
  for (HYBCommentModel *commentModel in model.commentModels) {
    CGFloat cellHeight = [HYBCommentCell hyb_heightForTableView:self.tableView config:^(UITableViewCell *sourceCell) {
      HYBCommentCell *cell = (HYBCommentCell *)sourceCell;
      [cell configCellWithModel:commentModel];
    } cache:^NSDictionary *{
      return @{kHYBCacheUniqueKey : commentModel.cid,
               kHYBCacheStateKey : @"",
               kHYBRecalculateForStateKey : @(NO)};
    }];
    tableViewHeight += cellHeight;
  }

  [self.tableView mas_updateConstraints:^(MASConstraintMaker *make) {
    make.height.mas_equalTo(tableViewHeight);
  }];
  self.tableView.dataSource = self;
  self.tableView.delegate = self;
  [self.tableView reloadData];
}

计算评论cell的高度

因为评论cell的内容不会改变,要么被删除了,要么就是添加新的评论,因此不需要清除缓存,将
kHYBRecalculateForStateKey对应的值设置为NO即可。因为我们在上面一步配置外部cell的数据的时候,已经调用过下面计算行高的方法来计算了并且会自动缓存起来,所以这一步的调用其实只是直接获取缓存的高度,因为效率是非常高的。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  HYBCommentModel *model = [self.testModel.commentModels objectAtIndex:indexPath.row];

  return [HYBCommentCell hyb_heightForTableView:self.tableView config:^(UITableViewCell *sourceCell) {
    HYBCommentCell *cell = (HYBCommentCell *)sourceCell;
    [cell configCellWithModel:model];
  } cache:^NSDictionary *{
    return @{kHYBCacheUniqueKey : model.cid,
             kHYBCacheStateKey : @"",
             kHYBRecalculateForStateKey : @(NO)};
  }];
}

增加、删除评论

选中某条评论的时候,就增加一条评论,然后通过代理反馈到控制器类刷新数据。同样,当删除一条评论的时候,也通过代理反馈到控制器类,刷新数据:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  [tableView deselectRowAtIndexPath:indexPath animated:YES];

  // 添加一条数据
  HYBCommentModel *model = [[HYBCommentModel alloc] init];
  model.name = @"标哥";
  model.reply = @"标哥的技术博客";
  model.comment = @"哈哈,我被点击后自动添加了一条数据的,不要在意我~";
  model.cid = [NSString stringWithFormat:@"commonModel%ld",  self.testModel.commentModels.count + 1];
  [self.testModel.commentModels addObject:model];

  if ([self.delegate respondsToSelector:@selector(reloadCellHeightForModel:atIndexPath:)]) {
    self.testModel.shouldUpdateCache = YES;
    [self.delegate reloadCellHeightForModel:self.testModel atIndexPath:self.indexPath];
  }
}

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
  return YES;
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
  if (editingStyle == UITableViewCellEditingStyleDelete) {
    [self.testModel.commentModels removeObjectAtIndex:indexPath.row];

    if ([self.delegate respondsToSelector:@selector(reloadCellHeightForModel:atIndexPath:)]) {
      self.testModel.shouldUpdateCache = YES;
      [self.delegate reloadCellHeightForModel:self.testModel atIndexPath:self.indexPath];
    }
  }
}

控制器刷新数据

当增加或者删除一条评论的时候,testModel会将shouldUpdateCache设置为YES,以便在reload对应的某一行之后,行高可以重新计算并更新缓存的高度而不是使用之前所缓存起来的高度:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  HYBTestModel *model = [self.datasource objectAtIndex:indexPath.row];

  CGFloat h = [HYBTestCell hyb_heightForTableView:tableView config:^(UITableViewCell *sourceCell) {
    HYBTestCell *cell = (HYBTestCell *)sourceCell;
    [cell configCellWithModel:model indexPath:indexPath];
  } cache:^NSDictionary *{
    NSDictionary *cache = @{kHYBCacheUniqueKey : model.uid,
             kHYBCacheStateKey  : @"",
             kHYBRecalculateForStateKey : @(model.shouldUpdateCache)};
    model.shouldUpdateCache = NO;
    return cache;
  }];

  return h;
}

#pragma mark - HYBTestCellDelegate
- (void)reloadCellHeightForModel:(HYBTestModel *)model atIndexPath:(NSIndexPath *)indexPath {
  [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}

源代码

大家可以到笔者的GITHUB下载Demo运行起来看看效果,代码中对于防止反复计算问题是没有处理的,为了demo的足够简单,不添加任何控制逻辑。在真正开发中,要想性能更高,需要自己处理~

下载地址:CoderJackyHuang:CellEmbedTableView

最后

给大家推荐笔者所开源的自动计算行高的库,使用起来是很方便的!

欢迎大家star,若有问题及时反馈与作者!

阅读原文

文章内容更新只会在原文处更新,请阅读原文:http://www.henishuo.com/ios-cell-embed-tableview/

目录
相关文章
|
缓存 NoSQL 安全
Linux设备驱动程序(四)——调试技术3
Linux设备驱动程序(四)——调试技术3
344 0
|
8月前
|
人工智能 Cloud Native Serverless
阿里云爸爸发福利!DeepSeek-R1满血版深度体验,4种部署攻略+隐藏羊毛大公开💎
本文介绍了四种部署DeepSeek-R1模型的方式:基于百炼调用满血版API、基于PAI部署、基于函数计算部署和基于GPU云服务器部署。每种方式各有优劣,适合不同需求的用户。其中,基于百炼调用满血版API无需部署,提供满血版模型和100万免费Token,适合快速体验;基于PAI部署适合需要微调模型的用户;基于函数计算部署提供WEB交互界面;基于GPU云服务器部署则适合技术能力强、有硬件资源的用户。方案还提供了免费试用入口和实践体验总结,帮助开发者更好地理解和使用DeepSeek-R1模型。
417 62
|
10月前
|
JavaScript
node环境之Error: Cannot find module ‘chalk’ 报错无法解决的问题—-网上说让你npm install chalk 基本是没有用的-优雅草央千澈解决方案
node环境之Error: Cannot find module ‘chalk’ 报错无法解决的问题—-网上说让你npm install chalk 基本是没有用的-优雅草央千澈解决方案
671 13
node环境之Error: Cannot find module ‘chalk’ 报错无法解决的问题—-网上说让你npm install chalk 基本是没有用的-优雅草央千澈解决方案
|
10月前
|
存储 缓存 网络协议
Linux操作系统的内核优化与性能调优####
本文深入探讨了Linux操作系统内核的优化策略与性能调优方法,旨在为系统管理员和高级用户提供一套实用的指南。通过分析内核参数调整、文件系统选择、内存管理及网络配置等关键方面,本文揭示了如何有效提升Linux系统的稳定性和运行效率。不同于常规摘要仅概述内容的做法,本摘要直接指出文章的核心价值——提供具体可行的优化措施,助力读者实现系统性能的飞跃。 ####
|
算法 JavaScript 开发者
通过UIActivityViewController分享多图片及如何把分享时微信图标放置到第一位
通过UIActivityViewController分享多图片及如何把分享时微信图标放置到第一位
214 0
|
Dart 对象存储 Android开发
带你阅读 Flutter Demo(flutter 保姆级入门教程)
带你阅读 Flutter Demo(flutter 保姆级入门教程)
1590 0
|
域名解析 网络协议 Unix
DDNS 简介
一、概念DDNS(Dynamic Domain Name Server)是动态域名服务的缩写!DDNS是将用户的动态IP地址映射到一个固定的域名解析服务上,用户每次连接网络的时候客户端程序就会通过信息传 递把该主机的动态IP地址传送给位于服务商主机上的服务器程序,服务项目器程序负责提供DNS服务并实现动态域名解析。
16630 0
|
iOS开发
加载中,加载中......使用SwiftUI设计2种Loading动画
加载中,加载中......使用SwiftUI设计2种Loading动画
694 0
|
Android开发 UED iOS开发
干货!14个最新优质加载动画设计,让等待成为一种享受
互联网时代,网络“提速”日益频繁,人们打开Web或软件的速度越来越快,一般页面缓冲和加载地过程也是几不可查。然而,在某些情况下,例如软件急需加载大量页面,首页急需加载大量内容,用户下载文件过大,甚至是网页软件信息处理急需时间等等,难免会出现需要用户等待的时候。
2551 0