下拉滑动关闭UINavigationController时UINavigationBar安全区适配问题
我来帮你搞定这个导航栏下拉偏移的问题!你推测的原因完全正确——UINavigationBar默认和顶部安全区绑定,当你把导航控制器的view往下拖动时,系统会自动调整导航栏的布局来适配当前view的安全区变化,导致导航栏位置出现跳动。
下面给你两种符合需求的解决方案,你可以根据自己的UI设计选择:
方案1:让导航栏完全忽略安全区,始终绑定到所在视图顶部
这种方案下,导航栏会和导航控制器的view顶部保持固定距离,下拉时会跟着view一起移动,不会出现错位。
修改你的PannableViewController代码,在viewDidLoad方法中添加导航栏的约束配置:
override func viewDidLoad() { super.viewDidLoad() // 配置导航栏忽略安全区,绑定到view顶部 navigationBar.isTranslucent = false navigationBar.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ navigationBar.topAnchor.constraint(equalTo: view.topAnchor), navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor) ]) // 确保内容视图正确适配导航栏位置 if let firstVC = viewControllers.first { firstVC.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ firstVC.view.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), firstVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), firstVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), firstVC.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) } // 添加平移手势 let panGesture = UIPanGestureRecognizer(target: self, action: #selector(onPan(_:))) if let lastVC = self.childViewControllers.last { lastVC.view.addGestureRecognizer(panGesture) } }
方案2:让导航栏始终延伸到顶部安全区下方(保持与状态栏的间距)
这种方案下,即使下拉导航控制器的view,导航栏也会始终保持在状态栏下方,不会覆盖状态栏。
你可以通过修改导航控制器的additionalSafeAreaInsets,并保持导航栏与安全区的绑定:
override func viewDidLoad() { super.viewDidLoad() // 强制导航栏与顶部安全区保持绑定 navigationBar.prefersLargeTitles = false navigationBar.isTranslucent = false // 固定导航控制器的顶部安全区内边距,避免下拉时变化 let statusBarHeight = UIApplication.shared.windows.first?.windowScene?.statusBarManager?.statusBarFrame.height ?? 0 additionalSafeAreaInsets = UIEdgeInsets(top: statusBarHeight, left: 0, bottom: 0, right: 0) // 添加平移手势 let panGesture = UIPanGestureRecognizer(target: self, action: #selector(onPan(_:))) if let lastVC = self.childViewControllers.last { lastVC.view.addGestureRecognizer(panGesture) } }
同时,你需要确保导航控制器的edgesForExtendedLayout设置为.all(默认就是这个值,一般不用改)。
额外优化建议
- 如果你使用了大标题导航栏,记得把
navigationBar.prefersLargeTitles设为false,大标题的布局逻辑更容易出现安全区适配问题。 - 测试时注意不同设备(比如刘海屏和非刘海屏)的表现,确保适配所有机型。
修改后的完整PannableViewController代码(以方案1为例):
import UIKit class PannableViewController: UINavigationController { public var minimumVelocityToHide = 1500 as CGFloat public var minimumScreenRatioToHide = 0.5 as CGFloat public var animationDuration = 0.2 as TimeInterval override func viewDidLoad() { super.viewDidLoad() // 配置导航栏忽略安全区,绑定到view顶部 navigationBar.isTranslucent = false navigationBar.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ navigationBar.topAnchor.constraint(equalTo: view.topAnchor), navigationBar.leadingAnchor.constraint(equalTo: view.leadingAnchor), navigationBar.trailingAnchor.constraint(equalTo: view.trailingAnchor) ]) // 确保内容视图正确适配导航栏位置 if let firstVC = viewControllers.first { firstVC.view.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ firstVC.view.topAnchor.constraint(equalTo: navigationBar.bottomAnchor), firstVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), firstVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor), firstVC.view.bottomAnchor.constraint(equalTo: view.bottomAnchor) ]) } // 添加平移手势 let panGesture = UIPanGestureRecognizer(target: self, action: #selector(onPan(_:))) if let lastVC = self.childViewControllers.last { lastVC.view.addGestureRecognizer(panGesture) } } func slideViewVerticallyTo(_ y: CGFloat) { self.view.frame.origin = CGPoint(x: 0, y: y) } @objc func onPan(_ panGesture: UIPanGestureRecognizer) { switch panGesture.state { case .began, .changed: let translation = panGesture.translation(in: view) let y = max(0, translation.y) self.slideViewVerticallyTo(y) break case .ended: let translation = panGesture.translation(in: view) let velocity = panGesture.velocity(in: view) let closing = (translation.y > self.view.frame.size.height * minimumScreenRatioToHide) || (velocity.y > minimumVelocityToHide) if closing { UIView.animate(withDuration: animationDuration, animations: { self.slideViewVerticallyTo(self.view.frame.size.height) }, completion: { (isCompleted) in if isCompleted { self.dismiss(animated: false, completion: nil) } }) } else { UIView.animate(withDuration: animationDuration, animations: { self.slideViewVerticallyTo(0) }) } break default: UIView.animate(withDuration: animationDuration, animations: { self.slideViewVerticallyTo(0) }) break } } override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nil, bundle: nil) self.modalPresentationStyle = .overFullScreen; self.modalTransitionStyle = .coverVertical; } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) self.modalPresentationStyle = .overFullScreen; self.modalTransitionStyle = .coverVertical; } }
内容的提问来源于stack exchange,提问作者Munib
相关产品推荐
相关产品推荐

