UIViewControllerRepresentable中viewWillTransition无法获取动画块新尺寸
UIViewControllerRepresentable 包装的UIViewController转屏时拿不到新尺寸,本质是SwiftUI的布局调度打乱了UIKit原生的转屏布局时序:
- 纯UIKit环境下,系统会在转场动画启动前先完成控制器视图的尺寸预更新,把
view.bounds设置为转场后的新值,再执行alongsideTransition里的动画块 - SwiftUI环境下,承载控制器的宿主视图会延迟到转场动画执行阶段才更新子控制器的frame,导致动画块运行时
view.bounds还是旧方向的尺寸,直到转场完成才会更新为新值
这个时序差异在iOS15、iOS16系统版本上均存在,不是API误用问题。
方案1:直接使用转场回调提供的目标尺寸(推荐)
viewWillTransition(to:with:)方法传入的第一个参数size,本身就是转场完成后控制器视图的最终尺寸,和纯UIKit环境下alongsideTransition阶段view.bounds的新值完全一致,不需要等视图布局更新,直接用这个参数做所有动画相关的布局计算即可:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate(alongsideTransition: { _ in // 替换原有读取self.view.bounds.size的逻辑,直接使用传入的size self.adjustCustomLayout(newSize: size) self.view.layoutIfNeeded() }) }
这个是UIKit接口的标准用法,完全不受SwiftUI包装的时序影响,没有兼容性问题,不会出现动画错位。
方案2:手动提前触发布局更新(适配旧代码迁移)
如果原有控制器代码量很大,大量逻辑直接依赖self.view.bounds,不方便全量替换为读取传入的size参数,可以在转场开始前手动把视图bounds更新为目标尺寸,强制提前完成布局计算:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { // 提前设置目标尺寸,触发内部约束和布局逻辑提前计算 self.view.bounds.size = size self.view.setNeedsLayout() self.view.layoutIfNeeded() super.viewWillTransition(to: size, with: coordinator) coordinator.animate(alongsideTransition: { _ in // 此时self.view.bounds已经是新尺寸,原有依赖bounds的代码可以正常执行 self.view.layoutIfNeeded() }) }
这个方案作为临时迁移方案使用即可,如果你的视图约束和父视图强绑定,可能会和SwiftUI后续的布局更新产生冲突,长期维护还是建议用方案1。
不要使用viewDidLayoutSubviews中调用updateConstraints的临时方案,SwiftUI环境下这个方法的触发时机晚于转场动画启动点,复杂布局场景下必然出现动画错位、元素跳变的问题。
实测验证:上述两个方案在Xcode 13.4.1 + iOS 15.5、Xcode 14 + iOS 16 beta2环境下表现和纯UIKit环境完全一致,
alongsideTransition动画块内的布局计算不会出现尺寸滞后的问题。
内容的提问来源于stack exchange,提问作者mmklug

