拖动以关闭 UIPresentationController
Drag to dismiss a UIPresentationController
我制作了一个适用于任何视图控制器的 UIPresentationController,并使用此 tutorial 显示在屏幕的一半上。现在我很想添加阻力以消除这一点。我试图让拖动感觉自然且反应灵敏,就像 Apple iOS 13 股票应用程序上“Top Stories”的拖动体验一样。我认为 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)
}
}
由于你对拖拽体验的描述不是很清楚,希望我没有误会你。
I'm trying to have the drag feel natural and responsive like the drag experience for "Top Stories" on the Apple iOS 13 stocks app.
我得到的是,你希望能够拖动呈现的视图,如果它到达某个点则将其关闭,否则返回到它的原始位置(当然你可以将视图带到你想要的任何位置).
为此,我们可以在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)
}
}
以上代码只是基于您提供的代码的一个示例,但我希望它能解释您所谓的拖动体验背后发生的事情。希望这会有所帮助 ;)
示例结果如下:
我制作了一个适用于任何视图控制器的 UIPresentationController,并使用此 tutorial 显示在屏幕的一半上。现在我很想添加阻力以消除这一点。我试图让拖动感觉自然且反应灵敏,就像 Apple iOS 13 股票应用程序上“Top Stories”的拖动体验一样。我认为 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)
}
}
由于你对拖拽体验的描述不是很清楚,希望我没有误会你。
I'm trying to have the drag feel natural and responsive like the drag experience for "Top Stories" on the Apple iOS 13 stocks app.
我得到的是,你希望能够拖动呈现的视图,如果它到达某个点则将其关闭,否则返回到它的原始位置(当然你可以将视图带到你想要的任何位置). 为此,我们可以在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) } }
以上代码只是基于您提供的代码的一个示例,但我希望它能解释您所谓的拖动体验背后发生的事情。希望这会有所帮助 ;)
示例结果如下: