表格视图可重复使用的自调整大小的单元格,具有不断变化的子视图

Posted

技术标签:

【中文标题】表格视图可重复使用的自调整大小的单元格,具有不断变化的子视图【英文标题】:Table view reuseable self-sizing cells with changing sub-views 【发布时间】:2015-10-19 08:13:55 【问题描述】:

我有一个UITableView

self.tableView = [[UITableView alloc] init];
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 60.0f;
self.tableView.delegate = self;
self.tableView.dataSource = self;
[self.tableView registerClass:[MyTableViewCell class] forCellReuseIdentifier:NSStringFromClass([MyTableViewCell class])];

还有一个带有自定义视图的自定义UITableViewCell

@implementation MyTableViewCell 



- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier 
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) 
        [self setupView];
    

    return self;


- (void)setupView 
    self.wrapperView = [UIView new];
    [self.contentView addSubview:self.wrapperView];

    self.firstView = [UIView new];
    self.secondView = [UIView new];


- (void)prepareForReuse 
    [super prepareForReuse];

    [self.wrapperView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];


- (void)updateConstraints 
    [self.wrapperView mas_updateConstraints:^(MASConstraintMaker *make) 
        make.edges.equalTo(self.contentView);
    ];

    UIView *previous;
    for (NSUInteger index = 0; index < self.wrapperView.subviews.count; index++) 
        UIView *subview = self.wrapperView.subviews[index];
        [subview mas_makeConstraints:^(MASConstraintMaker *make) 
            if (!previous) 
                make.top.equalTo(self.wrapperView);
            
            else 
                make.top.equalTo(previous.mas_bottom);
            

            make.left.equalTo(self.wrapperView);
            make.right.equalTo(self.wrapperView);
            make.height.equalTo(@50.0f);

            if (index == self.wrapperView.subviews.count - 1) 
                make.bottom.equalTo(self.wrapperView);
            
        ];

        previous = subview;
    

    [super updateConstraints];


- (void)populate:(NSInteger)index 
    [self.wrapperView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];

    [self.wrapperView addSubview:self.firstView];
    if (index % 2 == 0) 
        [self.wrapperView addSubview:self.secondView];
    

    [self updateConstraints];


@end

UITableViewDelegate 是这样实现的:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
    MyTableViewCell *cell = (MyTableViewCell *) [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([MyTableViewCell class])];
    [cell populate:indexPath.row];

    return cell;

如您所见,我交替显示 secondView 和交替显示更改约束(使用砌体)以将 wrapperView 边缘绑定到其子视图(以允许自行调整大小)

问题是UITableView 在重用单元格时似乎无法计算出高度。这会导致在向下滚动单元格时出现以下两个错误:

错误1:(这里抱怨“高度”(50px)太多,因为我们正在重用一个用于显示secondView的单元格)

2015-10-19 09:52:54.075 SelfsizingTest[32296:1615907] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<MASLayoutConstraint:0x7fc5c3adaf40 UIView:0x7fc5c3ad9750.top == UITableViewCellContentView:0x7fc5c3ad8f30.top>",
    "<MASLayoutConstraint:0x7fc5c3adb1c0 UIView:0x7fc5c3ad9750.bottom == UITableViewCellContentView:0x7fc5c3ad8f30.bottom>",
    "<MASLayoutConstraint:0x7fc5c1543da0 UIView:0x7fc5c3ad9980.top == UIView:0x7fc5c3ad9750.top>",
    "<MASLayoutConstraint:0x7fc5c15453c0 UIView:0x7fc5c3ad9980.bottom == UIView:0x7fc5c3ad9750.bottom>",
    "<MASLayoutConstraint:0x7fc5c3c0d5d0 UIView:0x7fc5c3ad9980.height == 50>",
    "<NSLayoutConstraint:0x7fc5c3ae2440 UITableViewCellContentView:0x7fc5c3ad8f30.height == 100>"
)

Will attempt to recover by breaking constraint 
<MASLayoutConstraint:0x7fc5c3c0d5d0 UIView:0x7fc5c3ad9980.height == 50>

错误 2:(这里它抱怨没有足够的“高度”来允许显示 secondView(缺少 50 像素))在重用以前不显示 @ 的单元格时发生987654334@

2015-10-19 10:02:49.667 SelfsizingTest[32565:1625337] Unable to simultaneously satisfy constraints.
    Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "<MASLayoutConstraint:0x7f9628420250 UIView:0x7f962841eb40.top == UITableViewCellContentView:0x7f962841b1e0.top>",
    "<MASLayoutConstraint:0x7f96284204d0 UIView:0x7f962841eb40.bottom == UITableViewCellContentView:0x7f962841b1e0.bottom>",
    "<MASLayoutConstraint:0x7f9628421560 UIView:0x7f962841ed70.height == 50>",
    "<MASLayoutConstraint:0x7f9628628a70 UIView:0x7f962841ed70.top == UIView:0x7f962841eb40.top>",
    "<MASLayoutConstraint:0x7f96286293e0 UIView:0x7f962841eef0.top == UIView:0x7f962841ed70.bottom>",
    "<MASLayoutConstraint:0x7f9628629d80 UIView:0x7f962841eef0.height == 50>",
    "<MASLayoutConstraint:0x7f9628629f00 UIView:0x7f962841eef0.bottom == UIView:0x7f962841eb40.bottom>",
    "<NSLayoutConstraint:0x7f9628422490 UITableViewCellContentView:0x7f962841b1e0.height == 50>"
)

Will attempt to recover by breaking constraint 
<MASLayoutConstraint:0x7f9628629d80 UIView:0x7f962841eef0.height == 50>

所以我的问题是,当单元格在“填充”单元格期间可以更改其子视图结构时,我如何使用自定大小单元格。

我想解决这个问题的一种方法是为每个组合设置一个单独的单元格,但在我的“真实”情况下,这会导致很多单元格。

我上传了一个示例项目,我在其中重现了问题: https://www.dropbox.com/s/uyyzvxpkud7bcta/SelfsizingTest.zip?dl=0

【问题讨论】:

【参考方案1】:

计算布局子视图中的高度并将它们分配到实例变量中。计算将基于所需的数据和涉及的子视图。

- (void)layoutSubviews 

    [super layoutSubviews];
    .....
    self.heightEstimated = <some_variable_you_calulate_required_height_based_onSubview>;

然后创建一个仅用于计算高度的虚拟单元变量

static MyCell *_mannequinCell;

现在只创建一次虚拟单元

+ (MyCell *)mannequinCell 
    if (!_mannequinCell) 
        _mannequinCell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"MyCellIdentifier"];
    
    return _mannequinCell;

现在,当您需要根据数据查找大小时,数据/子视图可能会有所不同。只需创建如下所示的方法即可。

+ (CGFloat)heightRequiredWithData:(Model *)data 
    [[MyCell mannequinCell] populateCellWithData:data];
    [[MyCell mannequinCell] layoutSubviews];
    return [MyCell mannequinCell].heightEstimated;

【讨论】:

这种方法很像在 UITableView 自调整大小支持之前所做的,如果我可以继续使用自调整单元格而不是手动计算单元格的高度,那就太好了。跨度>

以上是关于表格视图可重复使用的自调整大小的单元格,具有不断变化的子视图的主要内容,如果未能解决你的问题,请参考以下文章

为啥自调整大小的表格视图单元格中的 UIImageView 具有内容模式 AspectFit 的原始高度?

自定义表格单元格未调整大小以适合内容视图

具有自我调整大小的单元格的集合视图不再可水平滚动

具有动态调整单元格的可折叠表格视图

iOS 有时会自动调整单元格标签的大小

故事板中的自定义单元格行高度设置没有响应