自定义转场切换到UINavigationController后Push控制器布局异常
View层级与Safe Area绑定异常
转场动画时,目标ViewController(如UINavigationController)的view被直接添加到当前VC的view内部,而非window层级。这导致目标VC的view无法正确关联window的safe area上下文,后续push的VC自然会出现safe area失效问题。残留的视图变换干扰
动画过程中对当前VC的view设置了transform、alpha等属性,替换rootViewController后未重置这些状态。残留的变换会破坏导航控制器内部的动画计算逻辑,导致push无动画。ViewController生命周期触发不完整
目标VC的view仅作为临时子view存在时,其完整生命周期方法(如viewWillAppear)未正确触发,导致UINavigationController的内部状态初始化异常,进一步引发导航动画失效。
1. 修正replace方法,清理残留状态
修改私有方法,确保重置当前VC的视图状态并清理无用视图:
private func replace(with: UIViewController, completion: (() -> Void)?) { guard let baseWindow = self.view.window, baseWindow.rootViewController == self else { completion?() return } // 重置当前视图的所有变换与透明度 self.view.transform = .identity self.view.alpha = 1 self.view.subviews.forEach { $0.alpha = 1 } // 移除动画中添加的临时背景视图 baseWindow.subviews.forEach { subview in if subview.backgroundColor == self.view.backgroundColor && subview != self.view && subview != with.view { subview.removeFromSuperview() } } // 设置新的根控制器并清理旧视图 baseWindow.rootViewController = with self.view.removeFromSuperview() completion?() }
2. 统一所有动画的View层级添加逻辑
所有动画场景中,将目标VC的view添加到window层级,并确保frame正确:
示例:修正fade动画
case .fade: guard let window = self.view.window else { replace(with: to, completion: completion) return } // 触发目标VC的view加载,确保生命周期正常 _ = to.view to.view.frame = window.bounds to.view.alpha = 0 window.insertSubview(to.view, aboveSubview: self.view) UIView.animate(withDuration: 0.3, delay: 0.0, options: [.curveEaseOut], animations: { to.view.alpha = 1 }, completion: { [self] _ in replace(with: to, completion: completion) })
示例:修正slide(.left)动画
case .slide(.left): guard let window = self.view.window else { replace(with: to, completion: completion) return } let width = window.bounds.width _ = to.view to.view.frame = window.bounds to.view.transform = CGAffineTransform(translationX: width, y: 0) window.insertSubview(to.view, aboveSubview: self.view) UIView.animate(withDuration: 0.7, delay: 0.0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.2, options: [.curveEaseInOut], animations: { [self] in self.view.transform = CGAffineTransform(translationX: -width, y: 0) self.view.subviews.forEach({ $0.alpha = 0 }) to.view.transform = .identity }, completion: { [self] _ in replace(with: to, completion: completion) })
注意:所有其他动画(pageIn、pageOut、zoomIn、zoomOut)都需要按照此逻辑修改,将self.view.superview?.insertSubview替换为window.insertSubview,并提前设置目标view的frame。
3. 移除冗余的layoutIfNeeded调用
原代码中部分动画里的self.view.layoutIfNeeded()属于冗余操作,因为当前VC即将被替换,无需再触发其布局,可直接删除这些调用。
修改后,执行以下流程:
- VC1调用
transition.to(NC1, animation: .fade) - VC2调用
self.navigationController?.pushViewController(VC3, animated: true)
此时VC3的safe area会正常适配,push动画也能正常触发。
内容的提问来源于stack exchange,提问作者CristianMoisei

