使用平移关闭 UINavigationController 调整 UINavigationBar 相对于顶部安全区域的大小

Posted

技术标签:

【中文标题】使用平移关闭 UINavigationController 调整 UINavigationBar 相对于顶部安全区域的大小【英文标题】:Using pan down to dismiss a UINavigationController resizes the UINavigationBar with respect to the top safe area 【发布时间】:2018-05-26 19:51:19 【问题描述】:

我正在使用平移手势识别器来关闭 UINavigationController。我正在使用来自here 的代码,并进行了修改以适合我的 NavigationController,并且我还将手势识别器添加到容器视图内的实际 VC 中。

一切正常,除了当我向下滑动时,只要 y 值 > 0.0,导航栏就会改变其位置。

我认为正在发生的事情是导航栏附加到顶部安全区域,当它离开顶部安全区域时,它会自动将自身调整到视图顶部。

我怎样才能简单地实现它,以便导航栏的大小要么延伸到顶部的安全区域下方,要么完全忽略它,而是限制它所在的视图?

图片如下:

正常:

向下滑动关闭:

这是我用于 UINavigationController 的代码

//
//  PannableViewController.swift
//

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()

        // Listen for pan gesture
        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:
            // If pan started or is ongoing then
            // slide the view to follow the finger
            let translation = panGesture.translation(in: view)
            let y = max(0, translation.y)
            self.slideViewVerticallyTo(y)
            break
        case .ended:
            // If pan ended, decide it we should close or reset the view
            // based on the final position and the speed of the gesture
            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: 
                    // If closing, animate to the bottom of the view
                    self.slideViewVerticallyTo(self.view.frame.size.height)
                , completion:  (isCompleted) in
                    if isCompleted 
                        // Dismiss the view when it dissapeared
                        self.dismiss(animated: false, completion: nil)
                    
                )
             else 
                // If not closing, reset the view to the top
                UIView.animate(withDuration: animationDuration, animations: 
                    self.slideViewVerticallyTo(0)
                )
            
            break
        default:
            // If gesture state is undefined, reset the view to the top
            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;
    

【问题讨论】:

你能分享示例项目吗? @kthorat 是的,给你github.com/returnzer0/SampleNavBar 谢谢。我现在需要离开。很有意思的问题,回来研究一下。 【参考方案1】:

这是我快速整理的内容,请根据需要进行优化和改进。

我已更新您的代码以使用转换委托。阅读 UIViewControllerTransitioningDelegate 和 UIViewControllerAnimatedTransitioning 以更好地了解 View Controller Transitions 的工作原理。

以下是更新的 PannableViewController。有什么变化?

-添加了转场委托,让您更好地控制 VC 之间的转场

-更新了平移手势处理程序

-更改了 minimumScreenRatioToHid(它太高了,如果需要,保持 0.5)

class PannableViewController: UINavigationController 
    public var minimumVelocityToHide = 1500 as CGFloat
    public var minimumScreenRatioToHide = 0.3 as CGFloat
    public var animationDuration = 0.2 as TimeInterval

    private lazy var transitionDelegate: TransitionDelegate = TransitionDelegate()

    override func viewDidLoad() 
        super.viewDidLoad()

        self.transitioningDelegate = self.transitionDelegate

        // Listen for pan gesture
        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(onPan(_:)))

        if let lastVC = self.childViewControllers.last 
            lastVC.view.addGestureRecognizer(panGesture)
        
    

    @objc func onPan(_ panGesture: UIPanGestureRecognizer) 

        let translation = panGesture.translation(in: self.view)
        let verticalMovement = translation.y / self.view.bounds.height
        let downwardMovement = fmaxf(Float(verticalMovement), 0.0)

        let downwardMovementPercent = fminf(downwardMovement, 1.0)
        let progress = CGFloat(downwardMovementPercent)

        let velocity = panGesture.velocity(in: self.view)
        let shouldFinish = progress > self.minimumScreenRatioToHide || velocity.y > self.minimumVelocityToHide

        switch panGesture.state 
        case .began:
            self.transitionDelegate.interactiveTransition.hasStarted = true
            self.dismiss(animated: true, completion: nil)
        case .changed:
            self.transitionDelegate.interactiveTransition.shouldFinish = shouldFinish
            self.transitionDelegate.interactiveTransition.update(progress)
        case .cancelled:
            self.transitionDelegate.interactiveTransition.hasStarted = false
            self.transitionDelegate.interactiveTransition.cancel()
        case .ended:
            self.transitionDelegate.interactiveTransition.hasStarted = false
            self.transitionDelegate.interactiveTransition.shouldFinish ? self.transitionDelegate.interactiveTransition.finish() : self.transitionDelegate.interactiveTransition.cancel()
        default:
            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;
    

以下是 UIViewControllerTransitioningDelegate 和几个模型类。

class InteractiveTransition: UIPercentDrivenInteractiveTransition 
        public var hasStarted: Bool = false
        public var shouldFinish: Bool = false
    

class Transition 
    public var isPresenting: Bool = false
    public var presentDuration: TimeInterval = 0.5
    public var dismissDuration: TimeInterval = 0.5


class TransitionDelegate: NSObject, UIViewControllerTransitioningDelegate 

    lazy var transition: Transition = Transition()
    lazy var interactiveTransition: InteractiveTransition = InteractiveTransition()

    func animationController(forPresented presented: UIViewController,
                             presenting: UIViewController,
                             source: UIViewController) -> UIViewControllerAnimatedTransitioning? 

        self.transition.isPresenting = true
        return self
    

    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? 
        self.transition.isPresenting = false
        return self
    

    func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? 
        return self.interactiveTransition.hasStarted ? self.interactiveTransition : nil
    



extension TransitionDelegate:  UIViewControllerAnimatedTransitioning 

    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval 
        return self.transition.isPresenting ? self.transition.presentDuration : self.transition.dismissDuration
    

    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) 

        guard let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from),
            let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to) else 
                transitionContext.completeTransition(false)
                return
        

        let containerView = transitionContext.containerView
        containerView.backgroundColor = UIColor.black.withAlphaComponent(0.5)

        if self.transition.isPresenting 

            let finalFrameForVC = transitionContext.finalFrame(for: toVC)
            toVC.view.frame = finalFrameForVC.offsetBy(dx: 0, dy: UIScreen.main.bounds.size.height)
            containerView.addSubview(toVC.view)

            // Additional ways to animate, Spring velocity & damping
            UIView.animate(withDuration: self.transition.presentDuration,
                           delay: 0.0,
                           options: .transitionCrossDissolve,
                           animations: 
                            toVC.view.frame = finalFrameForVC
            , completion:  _ in
                transitionContext.completeTransition(true)
            )

         else 

            var finalFrame = fromVC.view.frame
            finalFrame.origin.y += finalFrame.height

            // Additional ways to animate, Spring velocity & damping
            UIView.animate(withDuration: self.transition.dismissDuration,
                           delay: 0.0,
                           options: .curveEaseOut,
                           animations: 
                            fromVC.view.frame = finalFrame
                            toVC.view.alpha = 1.0
            ,
                           completion:  _ in
                            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            )
        
    

【讨论】:

以上是关于使用平移关闭 UINavigationController 调整 UINavigationBar 相对于顶部安全区域的大小的主要内容,如果未能解决你的问题,请参考以下文章

使用平移手势识别器关闭视图控制器

UIView/Drawer 使用平移/滑动手势

防止 UITableViewCells 在平移时突出显示

在 UIPercentDrivenInteractiveTransition 中缓慢平移会导致故障

如何在关闭视图控制器时获得淡出动画

ScrollView 平移手势和 panGestureRecognizer 冲突