禁用手势以下拉表单/页面表单模式演示

Posted

技术标签:

【中文标题】禁用手势以下拉表单/页面表单模式演示【英文标题】:Disable gesture to pull down form/page sheet modal presentation 【发布时间】:2019-06-22 19:36:59 【问题描述】:

ios 13 中,使用表单和页面表单样式的模态演示可以通过向下平移手势关闭。这在我的一张表单中是有问题的,因为用户在这个框中绘制会干扰手势。它会拉下屏幕而不是画一条垂直线。

如何在以表格形式呈现的模态视图控制器中禁用垂直滑动以关闭手势?

设置isModalInPresentation = true 仍然允许下拉工作表,只是不会关闭。

【问题讨论】:

Apple Developer上有一个解释清楚的文档:developer.apple.com/documentation/uikit/view_controllers/… 但是他们没有解释当这个手势干扰到其他人时如何解决这个问题。 【参考方案1】:

一般来说,您不应尝试禁用滑动关闭功能,因为用户希望所有表单/页面表在所有应用中的行为都相同。相反,您可能需要考虑使用全屏演示样式。如果您确实想使用无法通过滑动关闭的工作表,请设置isModalInPresentation = true,但请注意,这仍然允许垂直下拉工作表,并且在释放触摸时它会弹回。查看UIAdaptivePresentationControllerDelegate 文档以在用户尝试通过滑动等操作将其关闭时做出反应。

如果您的应用的手势或触摸处理受到滑动关闭功能的影响,我确实收到了 Apple 工程师关于如何解决该问题的一些建议。

如果您可以阻止系统的平移手势识别器启动,这将阻止手势解除。有几种方法可以做到这一点:

    如果您的画布绘图是使用手势识别器完成的,例如您自己的 UIGestureRecognizer 子类,请在工作表的关闭手势之前进入 began 阶段。如果你能像UIPanGestureRecognizer一样快速识别,你就赢了,表格的关闭手势将被颠覆。

    如果您的画布绘图是使用手势识别器完成的,请使用-shouldBeRequiredToFailByGestureRecognizer:(或相关的委托方法)设置动态失败要求,如果传入的手势识别器是@987654328,则返回NO @。

    如果您的画布绘图是通过手动触摸处理完成的(例如touchesBegan:),请在您的触摸处理视图上覆盖-gestureRecognizerShouldBegin,如果传入的手势识别器是UIPanGestureRecognizer,则返回NO

事实证明,我的设置 #3 运行良好。这允许用户在绘图画布之外的任何地方向下滑动以关闭(如导航栏),同时允许用户在不移动工作表的情况下进行绘图,正如人们所期望的那样。

我不建议尝试找到禁用它的手势,因为它似乎是相当动态的,并且可以在例如在不同尺寸类别之间切换时重新启用,这可能会在未来的版本中发生变化。

【讨论】:

我遵循 #2 但最终使用委托方法 gestureRecognizer(_:,shouldRecognizeSimultaneouslyWith:) 允许一些识别器一起工作,而其他识别器则不能。 我似乎无法让其中任何一个工作?对我的场景有什么建议吗?我正在展示一个带有照片选择系统的屏幕,使用拖动来选择股票 iOS 照片应用程序使用的。这使用平移手势识别器和UICollectionView。如果我错了,请原谅我,但我们不是必须成为解雇识别器的代表才能使用这些方法中的任何一个吗? #3 非常适合我的应用程序,它允许用户在视图中绘图。 @Jordan H 你能用一些代码更好地解释#3吗? 干得好!很有意思。 #3 非常适合我。【参考方案2】:

这个手势可以在模态视图控制器的presentedView 属性中找到。正如我调试的那样,这个属性的gestureRecognizers 数组只有一个项目,打印它会导致这样的结果:

UIPanGestureRecognizer: 0x7fd3b8401aa0 (_UISheetInteractionBackgroundDismissRecognizer);

因此,要禁用此手势,您可以执行以下操作:

let vc = UIViewController()

self.present(vc, animated: true, completion: 
  vc.presentationController?.presentedView?.gestureRecognizers?[0].isEnabled = false
)

要重新启用它,只需将isEnabled 设置回true

vc.presentationController?.presentedView?.gestureRecognizers?[0].isEnabled = true

请注意,iOS 13 仍处于测试阶段,因此可能会在即将发布的版本中添加更简单的方法。

虽然这个解决方案目前似乎有效,但我不推荐它,因为它在某些情况下可能不起作用,或者可能会在未来的 iOS 版本中发生变化,并可能影响您的应用。

【讨论】:

请注意,如果您在此视图控制器中有一个滚动视图,那么一旦您到达顶部,您仍然可以下拉关闭。但是,如果您没有滚动视图,那就太好了。 @JordanH 没错!滚动视图中的另一个手势似乎也在以某种方式处理模态解除。我打印了滚动视图手势,有一个UISwipeDismissalGestureRecognizer。这可能是问题所在。 UITableView 也会创建 _UISwipeDismissalGestureRecognizer。此外,如果您使用根视图控制器创建导航控制器,将堆栈以模式方式呈现为页面/表单并将另一个视图控制器推到其顶部,则向下滑动以关闭手势将通过手势识别器在UIView 层次结构的更高位置创建。如果没有 Apple 明确支持禁用向下滑动以关闭触摸事件处理,唯一可靠的解决方案(从 Xcode 11 beta 3 开始)是使用 UIModalPresentationFullScreen 中的 UIModalPresentationStyle 如果您想更安全地了解禁用的手势,您甚至可以按名称或类型进行搜索。 for gesture in guestures where gesture.name == "_UISheetInteractionBackgroundDismissRecognizer" gesture.isEnabled = false 【参考方案3】:

在呈现的 ViewController viewDidLoad 中使用它:

if #available(iOS 13.0, *) 
    self.isModalInPresentation = true

【讨论】:

如问题中所述,isModalInPresentation = true 仍然允许将工作表拉下,只是不会关闭,这可能正是您需要的,也可能会出现问题,具体取决于您的用例就像我的画布一样。 你是对的。我认为最简单的解决方案是使用旧的全屏模式样式:self.modalPresentationStyle = .fullScreen viewController.isModalInPresentation = true 为我工作 这是正确的答案,你仍然保持下拉的动画,但你永远不会关闭视图。谢谢! 为我工作。比移除手势识别器要好。【参考方案4】:

在我的例子中,我有一个模态屏幕,其视图接收触摸以捕获客户签名。

在导航控制器中禁用手势识别器解决了这个问题,完全阻止了模态交互解除被触发。

以下方法在我们的模态视图控制器中实现,并通过委托从我们的自定义签名视图中调用。

来自touchesBegan:

private func disableDismissalRecognizers() 
    navigationController?.presentationController?.presentedView?.gestureRecognizers?.forEach 
        $0.isEnabled = false
    

来自touchesEnded:

private func enableDismissalRecognizers() 
    navigationController?.presentationController?.presentedView?.gestureRecognizers?.forEach 
        $0.isEnabled = true
    

这是一个显示该行为的 GIF:

这个标记为重复的问题更好地描述了我遇到的问题:Disabling interactive dismissal of presented view controller on iOS 13 when dragging from the main view

【讨论】:

它不适用于 iOS 14; navigationController?.presentationController?.presentedView 始终是 nil。我尝试改用navigationController?.presentationController?.presentedViewController.view,但在其中找不到_UISheetInteractionBackgroundDismissRecognizer... 奇怪,刚刚在 iOS 14 上测试,我们的应用运行良好。使用 Xcode 12 构建,不确定这是否会有所作为 我的错误,我重新打开了一些旧项目并忘记了它是要在另一个屏幕上修复某些内容,它在 iOS 14 上运行良好,感谢您验证并感谢您的回答! 就我而言,navigationController 始终为零,因此我可以使用 presentationController?.presentedView?.gestureRecognizers?.forEach 访问它 几乎对我有用,除了要禁止的手势识别器是self.presentationController?.presentedView?.gestureRecognizers?而不是navigationController?.presentationController?.presentedView?.gestureRecognizers?。查看@M Reza 的回答【参考方案5】:

您可以更改演示样式,如果它在全屏状态下下拉关闭将被禁用

navigationCont.modalPresentationStyle = .fullScreen

【讨论】:

我认为这是正确的答案。为了确定,我实际上将它与 isModalInPresentation 一起使用,并且效果很好。对我来说,关键是将这些设置在父级中。当我尝试在呈现的控制器中设置 viewDidLoad 时,它不起作用。 对我来说,我认为这也是处理这个问题的正确方法。如果它的全屏你不能刷卡和关闭它?试过了,你不能,所以这是正确的【参考方案6】:

无需重新发明***。很简单,在你的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 或任何其他方法,以便正确处理您的自定义行为。

【讨论】:

这应该是公认的答案。它以最简洁的方式正确解决了 OP 的问题。我刚刚实现了这一点,它的工作原理完全符合您的预期。这样你也可以根据什么返回真或假。 可以在viewDidLoad 中设置presentationController?.delegate = self,而不是在前一个视图控制器的prepare(for:sender:) 中设置代理。这样所有代码都在一个视图控制器中。【参考方案7】:

您可以使用 UIAdaptivePresentationControllerDelegate 方法presentationControllerDidAttemptToDismiss 并禁用presentedView 上的gestureRecognizer。 像这样的:

func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController)       
    presentationController.presentedView?.gestureRecognizers?.first?.isEnabled = false

【讨论】:

【参考方案8】:

适用于在运行 Jordans 解决方案 #3 时遇到问题的每个人。

您必须寻找正在呈现的 ROOT 视图控制器,具体取决于您的视图堆栈,这可能不是您当前的视图。

我不得不寻找我的导航控制器 PresentationViewController。

顺便说一句@Jordam:谢谢!

UIGestureRecognizer *gesture = [[self.navigationController.presentationController.presentedView gestureRecognizers] firstObject];
if ([gesture isKindOfClass:[UIPanGestureRecognizer class]]) 
    UIPanGestureRecognizer * pan = (UIPanGestureRecognizer *)gesture;
    pan.delegate = self;

【讨论】:

【参考方案9】:

您可能首先在 viewDidAppear() 方法中获得对处理页面表关闭的 UIPanGestureRecognizer 的引用。请注意,该引用在 viewWillAppear() 或 viewDidLoad() 中为零。然后你只需禁用它。

override func viewDidAppear(_ animated: Bool) 
    super.viewDidAppear(animated)
    presentationController?.presentedView?.gestureRecognizers?.first.isEnabled = false

如果您想要更多自定义而不是完全禁用它,例如,在页面表中使用导航栏时,请将 UIPanGestureRecognizer 的委托设置为您自己的视图控制器。这样,您可以在 contentView 中专门禁用手势识别器,同时通过实现

使其在导航栏区域中保持活动状态
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool 

【讨论】:

【参考方案10】:

在 IOS 13 中

if #available(iOS 13.0, *) 
    obj.isModalInPresentation = true
 else 
    // Fallback on earlier versions

【讨论】:

【参考方案11】:

我,我用这个:

-(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;
        
    

【讨论】:

【参考方案12】:

将尝试更详细地描述@Jordan H 已经建议的方法 2:

1) 为了能够捕捉并决定模态表的平移手势,请将其添加到视图控制器的viewDidLoad

navigationController?.presentationController?.presentedView?.gestureRecognizers?.forEach 
   $0.delegate = self

2) 使用gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) 启用捕捉平移手势和您自己的手势的能力

3) 实际决定可以进入gestureRecognizer(_:shouldBeRequiredToFailBy:)

示例代码,使滑动手势优先于工作表的平移手势(如果两者都存在)。它不会影响没有滑动手势识别器的区域中的原始平移手势,因此原始的“滑动关闭”仍然可以按设计工作。

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

在我的例子中,代码是这样工作的。没有它,滑动手势大多被忽略,只是偶尔起作用。

【讨论】:

嘿,我已经尝试了您的解决方案,但我仍然有问题。当我在viewDidAppear 中设置委托时(因为我的presentationController 为零,我只是在道德上提出了一个vc)。并且,我迭代其视图的监督以发现该视图具有PanGesture,并将其委托设置为self。然后我的vc无法向下滑动关闭,还有其他方法可以解决我的问题吗?请帮忙 @Weslie,您需要捕获的是呈现视图的识别器,而不是其父视图的识别器。尝试临时添加gestureRecognizerShouldBegin委托方法,看看哪些手势实际被捕获并调试。【参考方案13】:

如果UITableViewUICollectionView 在用户尝试滚动超过滚动视图的顶端时启动页面关闭手势,则可以通过添加一个不可见的UIRefreshControl 来禁用此手势,该手势调用立即endRefreshing

另见https://***.com/a/58676756/2419404

【讨论】:

【参考方案14】:

在prepare(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

【讨论】:

【参考方案15】:

对于导航控制器,为了避免显示视图的滑动交互,我们可以使用:

if #available(iOS 13.0, *) navController.isModalInPresentation = true

【讨论】:

不,但这并不妨碍手势,这就是问题所在。

以上是关于禁用手势以下拉表单/页面表单模式演示的主要内容,如果未能解决你的问题,请参考以下文章

在 Ionic 2 中禁用侧面菜单滑动以打开登录页面(或任何页面)的手势

禁用和启用页面视图控制器手势识别器?

在 SwiftUI 中的表单上捕捉点击手势

watchOS 上的 SwiftUI 表单:点击手势间歇性失败

我可以禁用 UIPageViewController 的页面边框手势识别器吗?并保持刷卡?

我可以禁用UIPageViewController的页面边框手势识别器吗?刷卡一个?