You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 23:50:43