调用reloadData后UITableViewCell内容高度约束无法刷新如何解决?
AutoSizing UITableView Cell约束冲突解决
问题场景
我做了一个AutoSizing UITableView演示项目:点击TableView的cell时,将cell内红色子视图的高度约束从初始60改为100,随后调用reloadData。但执行后出现约束冲突警告,核心是系统首次布局生成的UIView-Encapsulated-Layout-Height约束(高度70)和修改后的子视图高度约束(100)冲突。
ViewController代码
#import "ViewController.h" #import "TestCell.h" @interface ViewController ()<UITableViewDelegate,UITableViewDataSource> @property (weak, nonatomic) IBOutlet UITableView *tableView; @property (nonatomic,assign) NSInteger value; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; self.tableView.dataSource = self; self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone; self.tableView.delegate = self; [self.tableView registerNib:[UINib nibWithNibName:@"TestCell" bundle:nil] forCellReuseIdentifier:@"TestCell"]; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 3; } - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { TestCell* cell = (TestCell*)[tableView dequeueReusableCellWithIdentifier:@"TestCell" forIndexPath:indexPath]; cell.value = self.value; return cell; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 1; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { self.value += 1; [self.tableView reloadData]; } @end
TestCell代码
#import "TestCell.h" @interface TestCell() @property (weak, nonatomic) IBOutlet UIView *redView; @property (weak, nonatomic) IBOutlet NSLayoutConstraint *heightConstraint;//red view's height constraint @end @implementation TestCell - (void)awakeFromNib { [super awakeFromNib]; } - (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; } - (void)setValue:(NSInteger)value { if (value > 1) { self.heightConstraint.constant = 100; } [self setNeedsLayout]; [self setNeedsUpdateConstraints]; } @end
约束冲突警告日志
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. ( "<NSLayoutConstraint:0x6000021279d0 UIView:0x10d615680.height == 100 (active)>", "<NSLayoutConstraint:0x600002126f80 V:[UIView:0x10d615680]-(10)-| (active, names: '|':UITableViewCellContentView:0x10d6154c0 )>", "<NSLayoutConstraint:0x600002127070 V:|-(0)-[UIView:0x10d615680] (active, names: '|':UITableViewCellContentView:0x10d6154c0 )>", "<NSLayoutConstraint:0x6000021277a0 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x10d6154c0.height == 70 (active)>" ) Will attempt to recover by breaking constraint <NSLayoutConstraint:0x6000021279d0 UIView:0x10d615680.height == 100 (active)>
解决方法
方法1:触发tableView重新估算cell高度
系统的UIView-Encapsulated-Layout-Height约束优先级为990,我们可以通过让tableView重新计算cell高度来覆盖旧约束。修改ViewController的didSelectRowAtIndexPath方法:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { self.value += 1; // 重置estimatedRowHeight,强制tableView重新估算高度 self.tableView.estimatedRowHeight = UITableViewAutomaticDimension; // 刷新单个cell,比reloadData更高效 [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; }
方法2:提高自定义约束优先级
将红色视图的高度约束优先级设为最高(1000),冲突时系统会优先保留我们的约束。修改TestCell的setValue:方法:
- (void)setValue:(NSInteger)value { if (value > 1) { self.heightConstraint.constant = 100; // 设置约束优先级为最高(UILayoutPriorityRequired对应1000) self.heightConstraint.priority = UILayoutPriorityRequired; } // 直接触发布局更新,比setNeedsLayout更直接 [self layoutIfNeeded]; }
方法3:动态修改系统约束(不推荐)
遍历cell contentView的约束,找到系统生成的高度约束并修改其值。但该方式依赖系统约束的identifier,不同iOS版本可能有变化,稳定性差:
- (void)layoutSubviews { [super layoutSubviews]; for (NSLayoutConstraint *constraint in self.contentView.constraints) { if (constraint.firstAttribute == NSLayoutAttributeHeight && constraint.identifier && [constraint.identifier containsString:@"UIView-Encapsulated-Layout-Height"]) { // 根据value计算正确的contentView高度:红色视图高度+上下间距 constraint.constant = self.value > 1 ? 110 : 70; break; } } }
核心原因
UIView-Encapsulated-Layout-Height是UITableView为优化性能,首次布局cell时生成的固定高度约束。当修改cell内部约束后,如果未触发tableView重新估算高度,旧的系统约束会和新的自定义约束产生冲突。
内容的提问来源于stack exchange,提问作者ximmyxiao
相关产品推荐
相关产品推荐

