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

下拉滑动关闭UINavigationController时UINavigationBar安全区适配问题

解决下拉关闭UINavigationController时导航栏位置跳动的问题

我来帮你搞定这个导航栏下拉偏移的问题!你推测的原因完全正确——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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:08:38