如何获取自定义转场中目标ViewController的UINavigationBar快照?
这个问题我之前踩过坑!核心原因是在animateTransition(using:)方法执行时,目标ViewController(也就是你说的toVC)还没被正式加入导航控制器的视图层级——要么它的navigationController关联还没生效,要么导航栏还没根据toVC的配置完成布局和样式更新,直接快照自然会返回nil。
下面给你两种可靠的解决思路,你可以根据自己的场景选:
方式一:手动构建临时导航栏快照
这种方式不依赖真实导航控制器的导航栏,而是完全复刻toVC的导航栏配置,生成一个临时导航栏再快照,适合需要精准控制样式的场景:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let toVC = transitionContext.viewController(forKey: .to) else { transitionContext.completeTransition(false) return } // 1. 创建匹配导航控制器样式的临时导航栏 let statusBarHeight = UIApplication.shared.statusBarFrame.height let navBarFrame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 44 + statusBarHeight) let tempNavBar = UINavigationBar(frame: navBarFrame) // 同步导航栏基础样式 if let navBar = toVC.navigationController?.navigationBar { tempNavBar.barStyle = navBar.barStyle tempNavBar.tintColor = navBar.tintColor tempNavBar.backgroundColor = navBar.backgroundColor tempNavBar.setBackgroundImage(navBar.backgroundImage(for: .default), for: .default) tempNavBar.shadowImage = navBar.shadowImage } // 2. 同步toVC的导航栏内容(标题、按钮等) let navItem = UINavigationItem(title: toVC.title ?? "") navItem.leftBarButtonItems = toVC.navigationItem.leftBarButtonItems navItem.rightBarButtonItems = toVC.navigationItem.rightBarButtonItems navItem.titleView = toVC.navigationItem.titleView tempNavBar.setItems([navItem], animated: false) // 3. 强制布局,确保内容渲染完成 tempNavBar.layoutIfNeeded() // 4. 生成快照 guard let navBarSnapshot = tempNavBar.snapshotView(afterScreenUpdates: true) else { transitionContext.completeTransition(false) return } // 后续转场动画逻辑 let containerView = transitionContext.containerView containerView.addSubview(toVC.view) toVC.view.isHidden = true // 先隐藏真实视图,用快照做过渡 containerView.addSubview(navBarSnapshot) // 这里写你的动画代码... // 动画结束后清理 UIView.animate(withDuration: transitionDuration(using: transitionContext)) { // 动画效果 } completion: { _ in toVC.view.isHidden = false navBarSnapshot.removeFromSuperview() transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } }
方式二:临时将toVC加入导航栈触发布局
这种方式更贴近真实场景,适合需要完全复用导航控制器现有样式的情况,但要注意临时修改导航栈的副作用:
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let toVC = transitionContext.viewController(forKey: .to), let navController = toVC.navigationController else { transitionContext.completeTransition(false) return } let containerView = transitionContext.containerView containerView.addSubview(toVC.view) // 1. 临时把toVC推到导航栈,触发导航栏更新 let originalTopVC = navController.topViewController navController.pushViewController(toVC, animated: false) // 2. 强制导航控制器布局,确保导航栏渲染完成 navController.view.layoutIfNeeded() // 3. 获取导航栏快照 guard let navBarSnapshot = navController.navigationBar.snapshotView(afterScreenUpdates: true) else { // 快照失败要恢复原导航栈 if originalTopVC != nil { navController.popViewController(animated: false) } transitionContext.completeTransition(false) return } // 4. 恢复原导航栈(转场完成后系统会正式处理栈变化) if originalTopVC != nil { navController.popViewController(animated: false) } // 后续动画逻辑 toVC.view.isHidden = true containerView.addSubview(navBarSnapshot) // 你的动画代码... UIView.animate(withDuration: transitionDuration(using: transitionContext)) { // 动画效果 } completion: { _ in toVC.view.isHidden = false navBarSnapshot.removeFromSuperview() transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } }
关键注意点
- 无论哪种方式,都必须调用
layoutIfNeeded()强制布局,否则导航栏的内容可能还没渲染,快照依然会为空。 - 如果你的
toVC有特殊配置(比如隐藏导航栏、自定义返回按钮),一定要在代码里同步这些设置,否则快照会和真实效果不一致。
内容的提问来源于stack exchange,提问作者Can
相关产品推荐
相关产品推荐

