使用平移关闭 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 相对于顶部安全区域的大小的主要内容,如果未能解决你的问题,请参考以下文章