iOS中同时调用pageViewController.transform与setViewControllers引发动画视觉故障
问题
重启旧项目后,需要实现书本翻页动画的同时沿X轴平移。该逻辑在Xcode12.5、Swift5.4、iOS14.5环境下可正常运行,实现方式如下:
在viewDidLoad()中声明transitionStyle为.pageCurl的UIPageViewController:
pageController = UIPageViewController(transitionStyle: .pageCurl, navigationOrientation: .horizontal, options: nil) pageController?.setViewControllers([UIViewController()], direction: .forward, animated: true, completion: nil)
因DataSource协议中spine设置为.mid,故使用通用UIViewController()。
随后在viewDidAppear()中,通过.transform属性执行平移动画,同时调用.setViewControllers()执行翻页动画:
override internal func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) DispatchQueue.main.asyncAfter(deadline: .now()+0.5) { UIView.animate(withDuration: 0.3, delay: 0, animations: { self.pageController?.view.transform = CGAffineTransform(translationX: self.view.bounds.width/4, y: 0) }, completion: nil) self.pageController?.setViewControllers(self.startingPages, direction: .forward, animated: true, completion: nil) } }
如今在Xcode14.2、iOS16.1.1环境下测试,相同代码约80%概率出现视觉故障:书本封面冻结,无法翻页和平移,偶尔还会导致应用崩溃。
问题原因
- iOS 16对
UIPageViewController的.pageCurl过渡动画内部实现做了调整,并行触发视图transform动画和setViewControllers翻页动画时,会引发视图层级渲染冲突,尤其是spine设为.mid的双页布局场景,渲染逻辑和旧版本差异明显。 - 异步延迟的0.5秒不稳定,iOS 16视图生命周期中,
viewDidAppear后0.5秒可能视图还未完成初始渲染,此时同时启动两个动画会打乱内部动画队列,导致图层冻结甚至崩溃。
解决方案
1. 合并动画逻辑,避免并行触发
不要同时启动平移和翻页动画,将翻页动画放在平移动画的completion回调中,确保前一个动画完成后再触发下一个:
override internal func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) DispatchQueue.main.asyncAfter(deadline: .now()+0.5) { UIView.animate(withDuration: 0.3, delay: 0, animations: { self.pageController?.view.transform = CGAffineTransform(translationX: self.view.bounds.width/4, y: 0) }, completion: { _ in self.pageController?.setViewControllers(self.startingPages, direction: .forward, animated: true, completion: nil) }) } }
2. 调整初始视图设置,避免过早动画冲突
在viewDidLoad中设置初始视图控制器时,去掉animated: true,初始加载的动画会和后续动画冲突:
pageController = UIPageViewController(transitionStyle: .pageCurl, navigationOrientation: .horizontal, options: nil) pageController?.setViewControllers([UIViewController()], direction: .forward, animated: false, completion: nil)
3. 改用CATransform3D优化兼容性(可选)
对于.pageCurl这类3D动画,使用CATransform3D做平移能更好地兼容内部3D渲染逻辑:
UIView.animate(withDuration: 0.3, delay: 0, animations: { let transform = CATransform3DMakeTranslation(self.view.bounds.width/4, 0, 0) self.pageController?.view.layer.transform = transform }, completion: { _ in self.pageController?.setViewControllers(self.startingPages, direction: .forward, animated: true, completion: nil) })
4. 优化动画触发时机
将动画触发从viewDidAppear移到viewDidLayoutSubviews,并添加判断确保只执行一次:
private var hasPerformedInitialAnimations = false override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() guard !hasPerformedInitialAnimations else { return } hasPerformedInitialAnimations = true DispatchQueue.main.asyncAfter(deadline: .now()+0.5) { UIView.animate(withDuration: 0.3, delay: 0, animations: { self.pageController?.view.transform = CGAffineTransform(translationX: self.view.bounds.width/4, y: 0) }, completion: { _ in self.pageController?.setViewControllers(self.startingPages, direction: .forward, animated: true, completion: nil) }) } }
内容的提问来源于stack exchange,提问作者oneshot
相关产品推荐
相关产品推荐

