禁用手势下拉 form/page sheet 模态呈现
Disable gesture to pull down form/page sheet modal presentation
在 iOS 13 个使用表单和页面 sheet 样式的模态演示文稿中,可以使用向下平移手势关闭。这在我的一种形式 sheet 中是有问题的,因为用户绘制到这个干扰手势的框中。它会将屏幕向下拉,而不是画一条垂直线。
如何在显示为 sheet 的模态视图控制器中禁用垂直滑动以消除手势?
设置isModalInPresentation = true
仍然允许sheet被下拉,只是不会关闭。
这个手势可以在模态视图控制器的 presentedView
属性 中找到。在我调试时,这个 属性 的 gestureRecognizers
数组只有一个项目,打印它的结果是这样的:
UIPanGestureRecognizer: 0x7fd3b8401aa0
(_UISheetInteractionBackgroundDismissRecognizer);
因此,要禁用此手势,您可以执行以下操作:
let vc = UIViewController()
self.present(vc, animated: true, completion: {
vc.presentationController?.presentedView?.gestureRecognizers?[0].isEnabled = false
})
到re-enable它只需将isEnabled
设置回true
:
vc.presentationController?.presentedView?.gestureRecognizers?[0].isEnabled = true
请注意,iOS13 仍处于测试阶段,因此可能会在即将发布的版本中添加更简单的方法。
尽管此解决方案目前似乎有效,但我不推荐它,因为它在某些情况下可能无效,或者可能会在未来 iOS 版本中更改并可能影响您的应用程序。
对于导航控制器,为了避免呈现视图的滑动交互,我们可以使用:
if #available(iOS 13.0, *) {navController.isModalInPresentation = true}
一般来说,您不应尝试禁用滑动关闭功能,因为用户希望所有 form/page sheet 在所有应用程序中的行为都相同。相反,您可能需要考虑使用全屏演示样式。如果您确实想使用无法通过滑动关闭的 sheet,请设置 isModalInPresentation = true
,但请注意,这仍然允许 sheet 被垂直拉下并且它会弹回释放触摸后。查看 UIAdaptivePresentationControllerDelegate 文档以在用户尝试通过滑动等操作关闭它时做出反应。
如果您的应用程序的手势或触摸处理受到滑动关闭功能的影响,我确实收到了 Apple 工程师关于如何解决该问题的一些建议。
如果您可以阻止系统的平移手势识别器开始,这将防止手势关闭。几种方法:
如果您的 canvas 绘图是使用手势识别器完成的,例如您自己的 UIGestureRecognizer
子类,请在 [=53= 之前进入 began
阶段] 的解雇手势。如果你认得快到UIPanGestureRecognizer
,你就赢了,sheet的解散手势将被颠覆。
如果您的 canvas 绘图是使用手势识别器完成的,请使用 -shouldBeRequiredToFailByGestureRecognizer:
(或相关的委托方法)设置动态失败要求,其中您 return NO
如果传入的手势识别器是 UIPanGestureRecognizer
.
如果您的 canvas 绘图是通过手动触摸处理完成的(例如 touchesBegan:
),请覆盖触摸处理视图上的 -gestureRecognizerShouldBegin
,并且 return NO
如果传入的手势识别器是 UIPanGestureRecognizer
.
事实证明,我的设置 #3 运行良好。这允许用户在绘图 canvas 之外的任何地方向下滑动以关闭(如导航栏),同时允许用户在不移动 sheet 的情况下进行绘图,正如人们所期望的那样。
我不建议尝试找到禁用它的手势,因为它看起来相当动态,并且可以在不同尺寸之间切换时重新启用它自己 类 例如,这可能会在未来的版本中改变。
在呈现的 ViewController viewDidLoad:
中使用它
if #available(iOS 13.0, *) {
self.isModalInPresentation = true
}
您可以更改演示文稿样式,如果它在全屏模式下,关闭下拉菜单将被禁用
navigationCont.modalPresentationStyle = .fullScreen
您可以使用 UIAdaptivePresentationControllerDelegate 方法 presentationControllerDidAttemptToDismiss 并禁用 presentedView 上的 gestureRecognizer。
像这样:
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
presentationController.presentedView?.gestureRecognizers?.first?.isEnabled = false
}
我,我用这个:
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
for(UIGestureRecognizer *gr in self.presentationController.presentedView.gestureRecognizers) {
if (@available(iOS 11.0, *)) {
if([gr.name isEqualToString:@"_UISheetInteractionBackgroundDismissRecognizer"]) {
gr.enabled = false;
}
}
}
对于乔丹解决方案#3 有问题的每个人运行。
您必须寻找正在显示的 ROOT viewcontroller,这取决于您的视图堆栈,这可能不是您当前的视图。
我不得不寻找我的导航控制器 PresentationViewController。
顺便说一句@Jordam:谢谢!
UIGestureRecognizer *gesture = [[self.navigationController.presentationController.presentedView gestureRecognizers] firstObject];
if ([gesture isKindOfClass:[UIPanGestureRecognizer class]]) {
UIPanGestureRecognizer * pan = (UIPanGestureRecognizer *)gesture;
pan.delegate = self;
}
就我而言,我有一个模态屏幕,其视图接收触摸以捕获客户签名。
禁用导航控制器中的手势识别器解决了问题,完全阻止了模态交互关闭的触发。
以下方法在我们的模态视图控制器中实现,并通过我们的自定义签名视图中的委托调用。
调用自 touchesBegan
:
private func disableDismissalRecognizers() {
navigationController?.presentationController?.presentedView?.gestureRecognizers?.forEach {
[=10=].isEnabled = false
}
}
调用自 touchesEnded
:
private func enableDismissalRecognizers() {
navigationController?.presentationController?.presentedView?.gestureRecognizers?.forEach {
[=11=].isEnabled = true
}
}
这是一个显示行为的 GIF:
这个被标记为重复的问题更好地描述了我遇到的问题:Disabling interactive dismissal of presented view controller on iOS 13 when dragging from the main view
将尝试更详细地描述@Jordan H 已经建议的方法 2:
1) 为了能够捕获模态 sheet 的平移手势并做出决定,将其添加到视图控制器的 viewDidLoad
:
navigationController?.presentationController?.presentedView?.gestureRecognizers?.forEach {
[=10=].delegate = self
}
2) 使用 gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)
启用捕捉平移手势和您自己的手势的能力
3) 实际决定可以在gestureRecognizer(_:shouldBeRequiredToFailBy:)
示例代码,使滑动手势优先于 sheet 的平移手势(如果两者都存在)。它不会影响没有滑动手势识别器的区域中的原始平移手势,因此原始 "swipe to dismiss" 仍然可以按设计工作。
extension PeopleViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer === UIPanGestureRecognizer.self && otherGestureRecognizer === UISwipeGestureRecognizer.self {
return true
}
return false
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
在我的例子中,我只有几个滑动手势识别器,所以比较类型对我来说就足够了,但如果有更多类型,比较手势识别器本身可能有意义(以编程方式添加的或作为接口的出口构建器),如本文档中所述:https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/coordinating_multiple_gesture_recognizers/preferring_one_gesture_over_another
以下是代码在我的案例中的工作方式。没有它,滑动手势大多被忽略,只是偶尔起作用。
在IOS 13
if #available(iOS 13.0, *) {
obj.isModalInPresentation = true
} else {
// Fallback on earlier versions
}
在 UITableView
或 UICollectionView
启动页面的情况下 sheet 当用户试图滚动超过滚动视图的顶端时关闭手势,这个手势可以是通过添加一个不可见的 UIRefreshControl
立即调用 endRefreshing
来禁用。
另见
准备中(for:sender:) :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == viewControllerSegueID {
let controller = segue.destination as! YourViewController
controller.modalPresentationStyle = .fullScreen
}
}
或者,在您初始化控制器之后:
let controller = YourViewController()
controller.modalPresentationStyle = .fullScreen
您可能首先在 viewDidAppear() 方法中获得对处理页面 sheet 关闭的 UIPanGestureRecognizer 的引用。请注意,此引用在 viewWillAppear() 或 viewDidLoad() 中为 nil。然后你只需禁用它。
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentationController?.presentedView?.gestureRecognizers?.first.isEnabled = false
}
如果您想要更多自定义而不是完全禁用它,例如,在页面中使用导航栏时 sheet,请将该 UIPanGestureRecognizer 的委托设置为您自己的视图控制器。这样,您可以在您的 contentView 中专门禁用手势识别器,同时通过实现
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {}
无需重新发明轮子。很简单,在你的destinationViewController上采用UIAdaptivePresentationControllerDelegate
协议,然后实现相关的方法:
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
return false
}
例如,假设您的 destinationViewController 已准备好进行如下所示的 segue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourIdentifier",
let destinationVC = segue.destination as? DetailViewController
{
//do other stuff
destinationVC.presentationController?.delegate = destinationVC
}
}
然后在destinationVC
(应该采用上述协议)上,您可以实现描述的方法func presentationControllerShouldDismiss(_ presentationController:) -> Bool
或任何其他方法,以便正确处理您的自定义行为。
在 iOS 13 个使用表单和页面 sheet 样式的模态演示文稿中,可以使用向下平移手势关闭。这在我的一种形式 sheet 中是有问题的,因为用户绘制到这个干扰手势的框中。它会将屏幕向下拉,而不是画一条垂直线。
如何在显示为 sheet 的模态视图控制器中禁用垂直滑动以消除手势?
设置isModalInPresentation = true
仍然允许sheet被下拉,只是不会关闭。
这个手势可以在模态视图控制器的 presentedView
属性 中找到。在我调试时,这个 属性 的 gestureRecognizers
数组只有一个项目,打印它的结果是这样的:
UIPanGestureRecognizer: 0x7fd3b8401aa0 (_UISheetInteractionBackgroundDismissRecognizer);
因此,要禁用此手势,您可以执行以下操作:
let vc = UIViewController()
self.present(vc, animated: true, completion: {
vc.presentationController?.presentedView?.gestureRecognizers?[0].isEnabled = false
})
到re-enable它只需将isEnabled
设置回true
:
vc.presentationController?.presentedView?.gestureRecognizers?[0].isEnabled = true
请注意,iOS13 仍处于测试阶段,因此可能会在即将发布的版本中添加更简单的方法。
尽管此解决方案目前似乎有效,但我不推荐它,因为它在某些情况下可能无效,或者可能会在未来 iOS 版本中更改并可能影响您的应用程序。
对于导航控制器,为了避免呈现视图的滑动交互,我们可以使用:
if #available(iOS 13.0, *) {navController.isModalInPresentation = true}
一般来说,您不应尝试禁用滑动关闭功能,因为用户希望所有 form/page sheet 在所有应用程序中的行为都相同。相反,您可能需要考虑使用全屏演示样式。如果您确实想使用无法通过滑动关闭的 sheet,请设置 isModalInPresentation = true
,但请注意,这仍然允许 sheet 被垂直拉下并且它会弹回释放触摸后。查看 UIAdaptivePresentationControllerDelegate 文档以在用户尝试通过滑动等操作关闭它时做出反应。
如果您的应用程序的手势或触摸处理受到滑动关闭功能的影响,我确实收到了 Apple 工程师关于如何解决该问题的一些建议。
如果您可以阻止系统的平移手势识别器开始,这将防止手势关闭。几种方法:
如果您的 canvas 绘图是使用手势识别器完成的,例如您自己的
UIGestureRecognizer
子类,请在 [=53= 之前进入began
阶段] 的解雇手势。如果你认得快到UIPanGestureRecognizer
,你就赢了,sheet的解散手势将被颠覆。如果您的 canvas 绘图是使用手势识别器完成的,请使用
-shouldBeRequiredToFailByGestureRecognizer:
(或相关的委托方法)设置动态失败要求,其中您 returnNO
如果传入的手势识别器是UIPanGestureRecognizer
.如果您的 canvas 绘图是通过手动触摸处理完成的(例如
touchesBegan:
),请覆盖触摸处理视图上的-gestureRecognizerShouldBegin
,并且 returnNO
如果传入的手势识别器是UIPanGestureRecognizer
.
事实证明,我的设置 #3 运行良好。这允许用户在绘图 canvas 之外的任何地方向下滑动以关闭(如导航栏),同时允许用户在不移动 sheet 的情况下进行绘图,正如人们所期望的那样。
我不建议尝试找到禁用它的手势,因为它看起来相当动态,并且可以在不同尺寸之间切换时重新启用它自己 类 例如,这可能会在未来的版本中改变。
在呈现的 ViewController viewDidLoad:
中使用它if #available(iOS 13.0, *) {
self.isModalInPresentation = true
}
您可以更改演示文稿样式,如果它在全屏模式下,关闭下拉菜单将被禁用
navigationCont.modalPresentationStyle = .fullScreen
您可以使用 UIAdaptivePresentationControllerDelegate 方法 presentationControllerDidAttemptToDismiss 并禁用 presentedView 上的 gestureRecognizer。 像这样:
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
presentationController.presentedView?.gestureRecognizers?.first?.isEnabled = false
}
我,我用这个:
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
for(UIGestureRecognizer *gr in self.presentationController.presentedView.gestureRecognizers) {
if (@available(iOS 11.0, *)) {
if([gr.name isEqualToString:@"_UISheetInteractionBackgroundDismissRecognizer"]) {
gr.enabled = false;
}
}
}
对于乔丹解决方案#3 有问题的每个人运行。
您必须寻找正在显示的 ROOT viewcontroller,这取决于您的视图堆栈,这可能不是您当前的视图。
我不得不寻找我的导航控制器 PresentationViewController。
顺便说一句@Jordam:谢谢!
UIGestureRecognizer *gesture = [[self.navigationController.presentationController.presentedView gestureRecognizers] firstObject];
if ([gesture isKindOfClass:[UIPanGestureRecognizer class]]) {
UIPanGestureRecognizer * pan = (UIPanGestureRecognizer *)gesture;
pan.delegate = self;
}
就我而言,我有一个模态屏幕,其视图接收触摸以捕获客户签名。
禁用导航控制器中的手势识别器解决了问题,完全阻止了模态交互关闭的触发。
以下方法在我们的模态视图控制器中实现,并通过我们的自定义签名视图中的委托调用。
调用自 touchesBegan
:
private func disableDismissalRecognizers() {
navigationController?.presentationController?.presentedView?.gestureRecognizers?.forEach {
[=10=].isEnabled = false
}
}
调用自 touchesEnded
:
private func enableDismissalRecognizers() {
navigationController?.presentationController?.presentedView?.gestureRecognizers?.forEach {
[=11=].isEnabled = true
}
}
这是一个显示行为的 GIF:
这个被标记为重复的问题更好地描述了我遇到的问题:Disabling interactive dismissal of presented view controller on iOS 13 when dragging from the main view
将尝试更详细地描述@Jordan H 已经建议的方法 2:
1) 为了能够捕获模态 sheet 的平移手势并做出决定,将其添加到视图控制器的 viewDidLoad
:
navigationController?.presentationController?.presentedView?.gestureRecognizers?.forEach {
[=10=].delegate = self
}
2) 使用 gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:)
3) 实际决定可以在gestureRecognizer(_:shouldBeRequiredToFailBy:)
示例代码,使滑动手势优先于 sheet 的平移手势(如果两者都存在)。它不会影响没有滑动手势识别器的区域中的原始平移手势,因此原始 "swipe to dismiss" 仍然可以按设计工作。
extension PeopleViewController: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool {
if gestureRecognizer === UIPanGestureRecognizer.self && otherGestureRecognizer === UISwipeGestureRecognizer.self {
return true
}
return false
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
在我的例子中,我只有几个滑动手势识别器,所以比较类型对我来说就足够了,但如果有更多类型,比较手势识别器本身可能有意义(以编程方式添加的或作为接口的出口构建器),如本文档中所述:https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/coordinating_multiple_gesture_recognizers/preferring_one_gesture_over_another
以下是代码在我的案例中的工作方式。没有它,滑动手势大多被忽略,只是偶尔起作用。
在IOS 13
if #available(iOS 13.0, *) {
obj.isModalInPresentation = true
} else {
// Fallback on earlier versions
}
在 UITableView
或 UICollectionView
启动页面的情况下 sheet 当用户试图滚动超过滚动视图的顶端时关闭手势,这个手势可以是通过添加一个不可见的 UIRefreshControl
立即调用 endRefreshing
来禁用。
另见
准备中(for:sender:) :
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == viewControllerSegueID {
let controller = segue.destination as! YourViewController
controller.modalPresentationStyle = .fullScreen
}
}
或者,在您初始化控制器之后:
let controller = YourViewController()
controller.modalPresentationStyle = .fullScreen
您可能首先在 viewDidAppear() 方法中获得对处理页面 sheet 关闭的 UIPanGestureRecognizer 的引用。请注意,此引用在 viewWillAppear() 或 viewDidLoad() 中为 nil。然后你只需禁用它。
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
presentationController?.presentedView?.gestureRecognizers?.first.isEnabled = false
}
如果您想要更多自定义而不是完全禁用它,例如,在页面中使用导航栏时 sheet,请将该 UIPanGestureRecognizer 的委托设置为您自己的视图控制器。这样,您可以在您的 contentView 中专门禁用手势识别器,同时通过实现
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {}
无需重新发明轮子。很简单,在你的destinationViewController上采用UIAdaptivePresentationControllerDelegate
协议,然后实现相关的方法:
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
return false
}
例如,假设您的 destinationViewController 已准备好进行如下所示的 segue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourIdentifier",
let destinationVC = segue.destination as? DetailViewController
{
//do other stuff
destinationVC.presentationController?.delegate = destinationVC
}
}
然后在destinationVC
(应该采用上述协议)上,您可以实现描述的方法func presentationControllerShouldDismiss(_ presentationController:) -> Bool
或任何其他方法,以便正确处理您的自定义行为。