UIStackView子视图进出动画异常从左上角弹出的正确实现
问题根源
你现在遇到的动画完全失效、子视图从屏幕左上角弹出的核心原因有三个:
addArrangedSubview是即时触发约束更新的方法,把它放在动画闭包里,并不会让这个添加动作进入动画过渡流程- 新加入的子视图在首次完成AutoLayout计算前,默认frame为
CGRectZero,也就是对应父视图的坐标系原点位置,这个未处理的初始状态会变成你动画的起始帧 - 你在动画闭包里的
layoutIfNeeded调用顺序错误,会导致布局计算在动画执行途中才完成,直接打断平滑过渡
正确的动画实现方案
添加子视图的入场动画步骤
- 动画触发前,先把目标子视图加入stackView,同时提前设置好动画的初始状态
// 提前完成子视图的arrangedSubview注册 self.stackView.addArrangedSubview(someChildView) // 设置动画初始状态:完全透明、高度为0 someChildView.alpha = 0 let heightConstraint = someChildView.heightAnchor.constraint(equalToConstant: 0) heightConstraint.isActive = true // 立刻强制全链路布局计算,让子视图拿到正确的目标位置和尺寸 self.view.layoutIfNeeded() - 在动画闭包里修改目标状态,按从内到外的顺序触发布局更新
UIView.animate(withDuration: 2, delay: 0, options: .curveLinear) { // 把子视图的高度约束改成真实需要的尺寸 heightConstraint.constant = someChildView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).height // 恢复子视图透明度 someChildView.alpha = 1 // 从最靠近子视图的层级开始触发布局,逐层向外刷新 someChildView.layoutIfNeeded() self.stackView.layoutIfNeeded() self.contentView.layoutIfNeeded() self.scrollView.layoutIfNeeded() self.view.layoutIfNeeded() }
隐藏/移除子视图的出场动画步骤
- 动画触发前,先记录当前子视图的真实高度值
- 在动画闭包里,把之前保存的高度约束改回0,同时把子视图的alpha设为0
- 等动画完成的回调执行后,再调用
removeFromSuperview()把视图从stackView的arranged列表中移除,不要直接在动画过程中移除视图
常见踩坑提醒
- 不要把
addArrangedSubview/removeFromSuperview这类即时修改视图层级的操作直接放在动画闭包里,这类操作的约束更新逻辑不会被UIView动画上下文捕获 - 动画开始前必须通过全链路的
layoutIfNeeded把所有约束计算完成,不然新视图永远会以坐标系原点的零尺寸状态当动画起点 - 垂直方向的UIStackView嵌在UIScrollView里的时候,布局更新必须从最内层的子视图开始,逐层向外触发刷新,不然contentSize的计算会滞后一个动画周期,导致过渡不流畅
内容的提问来源于stack exchange,提问作者ffritz
相关产品推荐
相关产品推荐

