使用向下平移关闭 UINavigationController 相对于顶部安全区域调整 UINavigationBar 的大小
Using pan down to dismiss a UINavigationController resizes the UINavigationBar with respect to the top safe area
我正在使用平移手势识别器关闭 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;
}
}
这是我快速整理的东西,请大家根据需要进行优化和改进。
我已更新您的代码以使用转换委托。阅读 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。我正在使用 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;
}
}
这是我快速整理的东西,请大家根据需要进行优化和改进。
我已更新您的代码以使用转换委托。阅读 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)
})
}
}
}