拖动以关闭 UIPresentationController
Posted
技术标签:
【中文标题】拖动以关闭 UIPresentationController【英文标题】:Drag to dismiss a UIPresentationController 【发布时间】:2020-10-25 09:28:45 【问题描述】:我制作了一个适合任何视图控制器的 UIPresentationController,并使用此 tutorial 显示在屏幕的一半上。现在我很想添加阻力来消除这一点。我试图让拖动感觉自然且响应灵敏,就像 Apple ios 13 股票应用程序上“热门故事”的拖动体验一样。我认为 iOS 13 模态拖动关闭会被延续,但它不会传递到这个控制器,但它不会。
我发现的每一段代码和教程都有糟糕的拖动体验。有谁知道如何做到这一点?过去一周我一直在尝试/寻找。提前谢谢你
这是我的演示控制器代码
class SlideUpPresentationController: UIPresentationController
// MARK: - Variables
private var dimmingView: UIView!
//MARK: - View functions
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?)
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
setupDimmingView()
override func containerViewWillLayoutSubviews()
presentedView?.frame = frameOfPresentedViewInContainerView
override var frameOfPresentedViewInContainerView: CGRect
guard let container = containerView else return super.frameOfPresentedViewInContainerView
let width = container.bounds.size.width
let height : CGFloat = 300.0
return CGRect(x: 0, y: container.bounds.size.height - height, width: width, height: height)
override func presentationTransitionWillBegin()
guard let dimmingView = dimmingView else return
containerView?.insertSubview(dimmingView, at: 0)
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:|[dimmingView]|",
options: [],
metrics: nil,
views: ["dimmingView": dimmingView]))
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|[dimmingView]|",
options: [],
metrics: nil,
views: ["dimmingView": dimmingView]))
guard let coordinator = presentedViewController.transitionCoordinator else
dimmingView.alpha = 1.0
return
coordinator.animate(alongsideTransition: _ in
self.dimmingView.alpha = 1.0
)
override func dismissalTransitionWillBegin()
guard let coordinator = presentedViewController.transitionCoordinator else
dimmingView.alpha = 0.0
return
coordinator.animate(alongsideTransition: _ in
self.dimmingView.alpha = 0.0
)
func setupDimmingView()
dimmingView = UIView()
dimmingView.translatesAutoresizingMaskIntoConstraints = false
dimmingView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
dimmingView.alpha = 0.0
let recognizer = UITapGestureRecognizer(target: self,
action: #selector(handleTap(recognizer:)))
dimmingView.addGestureRecognizer(recognizer)
@objc func handleTap(recognizer: UITapGestureRecognizer)
presentingViewController.dismiss(animated: true)
【问题讨论】:
不太清楚你想做什么,你能解释一下吗?谢谢 【参考方案1】:由于您对您想要的拖动体验的描述不是很清楚,希望我没有误会您。
我正在尝试让拖动感觉自然且响应迅速,就像 Apple iOS 13 股票应用中“热门故事”的拖动体验一样。
我得到的是,您希望能够拖动呈现的视图,如果它达到某个点则将其关闭,否则返回其原始位置(因为您可以将视图带到您想要的任何位置)。 为此,我们可以在presentedViewController中添加一个UIPanGesture,然后
根据手势移动presentedView
关闭/移回呈现的视图
class SlideUpPresentationController: UIPresentationController
// MARK: - Variables
private var dimmingView: UIView!
private var originalX: CGFloat = 0
//MARK: - View functions
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?)
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
setupDimmingView()
override func containerViewWillLayoutSubviews()
presentedView?.frame = frameOfPresentedViewInContainerView
override var frameOfPresentedViewInContainerView: CGRect
guard let container = containerView else return super.frameOfPresentedViewInContainerView
let width = container.bounds.size.width
let height : CGFloat = 300.0
return CGRect(x: 0, y: container.bounds.size.height - height, width: width, height: height)
override func presentationTransitionWillBegin()
guard let dimmingView = dimmingView else return
containerView?.insertSubview(dimmingView, at: 0)
// add PanGestureRecognizer for dragging the presented view controller
let viewPan = UIPanGestureRecognizer(target: self, action: #selector(viewPanned(_:)))
containerView?.addGestureRecognizer(viewPan)
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "V:|[dimmingView]|", options: [], metrics: nil, views: ["dimmingView": dimmingView]))
NSLayoutConstraint.activate(NSLayoutConstraint.constraints(withVisualFormat: "H:|[dimmingView]|", options: [], metrics: nil, views: ["dimmingView": dimmingView]))
guard let coordinator = presentedViewController.transitionCoordinator else
dimmingView.alpha = 1.0
return
coordinator.animate(alongsideTransition: _ in
self.dimmingView.alpha = 1.0
)
@objc private func viewPanned(_ sender: UIPanGestureRecognizer)
// how far the pan gesture translated
let translate = sender.translation(in: self.presentedView)
switch sender.state
case .began:
originalX = presentedViewController.view.frame.origin.x
case .changed:
// move the presentedView according to pan gesture
// prevent it from moving too far to the right
if originalX + translate.x < 0
presentedViewController.view.frame.origin.x = originalX + translate.x
case .ended:
let presentedViewWidth = presentedViewController.view.frame.width
let newX = presentedViewController.view.frame.origin.x
// if the presentedView move more than 0.75 of the presentedView's width, dimiss it, else bring it back to original position
if presentedViewWidth * 0.75 + newX > 0
setBackToOriginalPosition()
else
moveAndDismissPresentedView()
default:
break
private func setBackToOriginalPosition()
// ensure no pending layout change in presentedView
presentedViewController.view.layoutIfNeeded()
UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseIn, animations:
self.presentedViewController.view.frame.origin.x = self.originalX
self.presentedViewController.view.layoutIfNeeded()
, completion: nil)
private func moveAndDismissPresentedView()
// ensure no pending layout change in presentedView
presentedViewController.view.layoutIfNeeded()
UIView.animate(withDuration: 0.25, delay: 0.0, options: .curveEaseIn, animations:
self.presentedViewController.view.frame.origin.x = -self.presentedViewController.view.frame.width
self.presentedViewController.view.layoutIfNeeded()
, completion: _ in
// dimiss when the view is completely move outside the screen
self.presentingViewController.dismiss(animated: true, completion: nil)
)
override func dismissalTransitionWillBegin()
guard let coordinator = presentedViewController.transitionCoordinator else
dimmingView.alpha = 0.0
return
coordinator.animate(alongsideTransition: _ in
self.dimmingView.alpha = 0.0
)
func setupDimmingView()
dimmingView = UIView()
dimmingView.translatesAutoresizingMaskIntoConstraints = false
dimmingView.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
dimmingView.alpha = 0.0
let recognizer = UITapGestureRecognizer(target: self,
action: #selector(handleTap(recognizer:)))
dimmingView.addGestureRecognizer(recognizer)
@objc func handleTap(recognizer: UITapGestureRecognizer)
presentingViewController.dismiss(animated: true)
以上代码只是基于您提供的代码的示例,但我希望能够解释您所谓的拖动体验背后发生的事情。希望这会有所帮助;)
这是示例结果:
via GIPHY
【讨论】:
很棒的体验!您可以将其调整为垂直阴暗(向下滑动以关闭)吗?我正在尝试调整代码,但遇到了问题以上是关于拖动以关闭 UIPresentationController的主要内容,如果未能解决你的问题,请参考以下文章
如何让jqueryEasyUI里面的dialog弹出层跳出iframe框架在最外层显示以防止拖动时部门被遮住之后无法拖动