iOS 15下UITableViewCell高度约束行为异常问题
问题根因
iOS 15对UITableViewCell的Auto Layout执行时序、默认行高计算逻辑做了两处不兼容改动,直接导致原有代码初始加载失效:
- 平级约束冲突:你给foldView设置的折叠高度约束优先级是
.defaultHigh(值为750),addEmbeddedView方法中给动态内嵌视图设置的底部约束优先级同样是.defaultHigh(750)。iOS 15之前的Autolayout引擎会优先保留先激活的约束(即awakeFromNib阶段激活的折叠高度约束),但iOS 15调整了动态添加约束的处理顺序,后添加的子视图撑满父视图的约束会和原有平级约束产生冲突,系统会优先选择满足子视图内容尺寸的约束,直接把折叠高度约束判定为可打破的无效约束,导致foldView被直接撑开。 - 布局时序变动:iOS 15之前,cell在
awakeFromNib执行完成后会立即更新约束,赶在tableView首次计算行高前应用折叠状态;但iOS 15把cell首次约束更新的时机延后到了tableView计算行高的阶段,此时你动态添加的内嵌视图约束已经完成激活,而你没有主动触发约束刷新,系统直接按照内容撑满的尺寸计算了初始行高。只有手动调用beginUpdates/endUpdates触发行高重算时,系统才会重新遍历所有约束的激活状态,这也是点击单元格后功能完全正常的核心原因。
适配修复方案
按以下步骤修改即可兼容全系统版本,不需要改动现有展开折叠的交互逻辑:
- 调整约束优先级,从根源避免平级冲突
把折叠用的高度约束优先级从.defaultHigh提升到UILayoutPriority(rawValue: 900),保证折叠状态下高度约束的优先级永远高于内嵌视图的撑满约束,不会被子视图约束顶掉,同时修正layoutIfNeeded的调用对象为cell的布局根节点contentView:
var expanded: Bool = false { didSet { let duration = CATransaction.animationDuration() UIView.animate(withDuration: duration) { self.arrow.transform = self.expanded ? CGAffineTransform(rotationAngle: .pi / 2) : .identity // 高度约束优先级设为900,高于子视图的defaultHigh(750) self.height.priority = UILayoutPriority(rawValue: 900) self.height.isActive = !self.expanded self.foldView.alpha = self.expanded ? 1 : 0 // iOS15下必须调用contentView的layoutIfNeeded才能触发行高刷新 self.contentView.layoutIfNeeded() } } }
- 主动同步初始状态与动态加视图后的约束状态
在awakeFromNib初始化阶段主动触发一次布局更新,保证初始折叠状态在系统计算首次行高前生效;在addEmbeddedView方法末尾补一次状态同步,避免动态添加视图后约束被冲掉:
override func awakeFromNib() { super.awakeFromNib() container.layer.cornerRadius = 8 container.layer.cornerCurve = .continuous container.backgroundColor = Colors_8_5.offWhite.color self.contentView.backgroundColor = .clear self.container.clipsToBounds = true self.contentView.clipsToBounds = false self.clipsToBounds = false self.selectionStyle = .none self.backgroundColor = .clear [layer1, layer2, layer3].forEach { $0.masksToBounds = false $0.backgroundColor = UIColor.clear.cgColor self.contentView.layer.insertSublayer($0, at: 0) } // 设置初始折叠状态后主动触发布局刷新 self.expanded = false self.setNeedsLayout() self.layoutIfNeeded() } func addEmbeddedView(_ view: UIView) { self.foldView.subviews.forEach { $0.removeFromSuperview() } self.foldView.addSubview(view) view.translatesAutoresizingMaskIntoConstraints = false let constraints = [ view.leadingAnchor.constraint(equalTo: self.foldView.leadingAnchor), view.trailingAnchor.constraint(equalTo: self.foldView.trailingAnchor), view.topAnchor.constraint(equalTo: self.foldView.topAnchor), view.bottomAnchor.constraint(equalTo: self.foldView.bottomAnchor, constant: -9) ] constraints[3].priority = .defaultHigh NSLayoutConstraint.activate(constraints) // 添加完内嵌视图后同步当前展开/折叠状态,刷新约束 let currentState = self.expanded self.expanded = currentState }
- 补全tableView的行高配置适配iOS15默认逻辑
如果你的tableView没有手动设置自动行高,建议在初始化时补全以下配置,避免首次加载行高计算异常:
// 预估行高设置为折叠状态下的cell大致高度即可,减少加载时的位置跳动 tableView.estimatedRowHeight = 100 tableView.rowHeight = UITableView.automaticDimension
效果验证
修改后首次加载cell时,高优先级的折叠高度约束会优先生效,foldView会被约束到指定折叠高度,内嵌视图因为底部约束优先级更低会被正常裁剪在foldView范围内,不会把cell撑成展开状态;点击cell触发beginUpdates/endUpdates时,约束失活/激活的逻辑和原有表现完全一致,展开折叠动画无异常。
内容的提问来源于stack exchange,提问作者tePoloN
相关产品推荐
相关产品推荐

