在 iOS 13 中,使用表单和页面表单样式的模态演示可以通过向下平移手势关闭。这在我的一张表单中是有问题的,因为用户在这个框中绘制会干扰手势。它拉下屏幕而不是画一条垂直线。
如何禁用垂直滑动以在以表格形式显示的模态视图控制器中关闭手势?
设置isModalInPresentation = true
仍然允许将工作表拉下,只是不会关闭。
在 iOS 13 中,使用表单和页面表单样式的模态演示可以通过向下平移手势关闭。这在我的一张表单中是有问题的,因为用户在这个框中绘制会干扰手势。它拉下屏幕而不是画一条垂直线。
如何禁用垂直滑动以在以表格形式显示的模态视图控制器中关闭手势?
设置isModalInPresentation = true
仍然允许将工作表拉下,只是不会关闭。
通常,您不应尝试禁用滑动以关闭功能,因为用户希望所有表单/页面表在所有应用程序中都表现相同。相反,您可能需要考虑使用全屏演示样式。如果您确实想使用无法通过滑动关闭的工作表,请设置isModalInPresentation = true
,但请注意,这仍然允许垂直下拉工作表,并且在释放触摸时它会弹回。查看UIAdaptivePresentationControllerDelegate文档以在用户尝试通过滑动等操作将其关闭时做出反应。
如果您的应用程序的手势或触摸处理受到滑动关闭功能的影响,我确实收到了 Apple 工程师关于如何解决该问题的一些建议。
如果您可以阻止系统的平移手势识别器启动,这将阻止手势解除。有几种方法可以做到这一点:
如果您的画布绘图是使用手势识别器完成的,例如您自己的子类,请在工作表的关闭手势之前UIGestureRecognizer
进入阶段。began
如果你能尽快认出UIPanGestureRecognizer
,你就赢了,而且这张表的关闭手势将被颠覆。
如果您的画布绘图是使用手势识别器完成的,请使用-shouldBeRequiredToFailByGestureRecognizer:
(或相关的委托方法)设置动态失败要求,NO
如果传入的手势识别器是UIPanGestureRecognizer
.
如果您的画布绘图是通过手动触摸处理(例如touchesBegan:
)完成的,请覆盖-gestureRecognizerShouldBegin
您的触摸处理视图,NO
如果传入的手势识别器是UIPanGestureRecognizer
.
事实证明,我的设置 #3 运行良好。这允许用户在绘图画布之外的任何地方向下滑动以关闭(如导航栏),同时允许用户在不移动工作表的情况下进行绘图,正如人们所期望的那样。
我不建议尝试找到禁用它的手势,因为它似乎是相当动态的,并且可以在例如在不同尺寸类别之间切换时重新启用,这可能会在未来的版本中改变。
这个手势可以在模态视图控制器的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 版本中发生更改,并可能影响您的应用程序。
在呈现的 ViewController viewDidLoad 中使用它:
if #available(iOS 13.0, *) {
self.isModalInPresentation = true
}
在我的例子中,我有一个模态屏幕,其视图接收触摸以捕获客户签名。
禁用导航控制器中的手势识别器解决了这个问题,完全阻止了模态交互解除被触发。
以下方法在我们的模态视图控制器中实现,并通过委托从我们的自定义签名视图中调用。
调用自touchesBegan
:
private func disableDismissalRecognizers() {
navigationController?.presentationController?.presentedView?.gestureRecognizers?.forEach {
$0.isEnabled = false
}
}
调用自touchesEnded
:
private func enableDismissalRecognizers() {
navigationController?.presentationController?.presentedView?.gestureRecognizers?.forEach {
$0.isEnabled = true
}
}
这个标记为重复的问题更好地描述了我遇到的问题:Disabling interactive dismissal of present view controller on iOS 13 when dragging from the main view
您可以更改演示样式,如果它是全屏的,则下拉关闭将被禁用
navigationCont.modalPresentationStyle = .fullScreen
无需重新发明轮子。就像在你的destinationViewControllerUIAdaptivePresentationControllerDelegate
上采用协议一样简单,然后实现相关的方法:
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
或任何其他方法,以便正确处理您的自定义行为。
您可以使用 UIAdaptivePresentationControllerDelegate 方法presentationControllerDidAttemptToDismiss 并禁用presentedView 上的gestureRecognizer。像这样的东西:
func presentationControllerDidAttemptToDismiss(_ presentationController: UIPresentationController) {
presentationController.presentedView?.gestureRecognizers?.first?.isEnabled = false
}
对于每个在运行 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;
}
您可能首先在 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 {}
在IOS 13
if #available(iOS 13.0, *) {
obj.isModalInPresentation = true
} else {
// Fallback on earlier versions
}
我,我用这个:
-(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;
}
}
}
将尝试更详细地描述@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
}
}
在我的情况下,我只有几个滑动手势识别器,所以比较类型对我来说就足够了,但是如果它们有更多,比较gestureRecognizers本身(以编程方式添加的或者作为界面构建器的出口)可能是有意义的,如中所述此文档:https ://developer.apple.com/documentation/uikit/touches_presses_and_gestures/coordinating_multiple_gesture_recognizers/preferring_one_gesture_over_another
这是代码在我的案例中的工作方式。没有它,滑动手势大多被忽略,只是偶尔起作用。
如果当用户尝试滚动超过滚动视图的顶端时,UITableView
或启动页面表关闭手势,则可以通过添加立即调用的不可见手势来禁用此手势。UICollectionView
UIRefreshControl
endRefreshing
在 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
对于导航控制器,为了避免呈现视图的滑动交互,我们可以使用:
if #available(iOS 13.0, *) {navController.isModalInPresentation = true}