Отключить жест, чтобы раскрыть модальное представление формы / страницы

В iOS 13 модальные презентации, использующие форму и стиль листа, могут быть отклонены жестом панорамирования. Это проблематично на одном из моих листов формы, потому что пользователь рисует в этом поле, которое мешает жесту. Он тянет экран вниз вместо рисования вертикальной линии.

Как вы можете отключить вертикальное движение, чтобы отклонить жест в контроллере модального представления, представленном в виде листа?

настройка isModalInPresentation = true все еще позволяет листу быть снесенным, это только не отклонит.

17 ответов

Я получил официальный совет для достижения этой цели. Если вы можете предотвратить запуск системы распознавания жестов панорамирования, это предотвратит отклонение жеста. Несколько способов сделать это:

  1. Если ваш рисунок на холсте сделан с помощью распознавателя жестов, например, вашего собственного UIGestureRecognizer подкласс, введите затем began фаза, предшествующая тому, как делает жест отклонения листа. Если вы узнаете так быстро, как UIPanGestureRecognizer, вы выиграете, и жест отклонения листа будет извращен.

  2. Если рисование на холсте выполняется с помощью распознавателя жестов, настройте динамическое требование отказа с помощью -shouldBeRequiredToFailByGestureRecognizer: (или связанный метод делегата), где вы возвращаете NO если переданный в распознаватель жестов является UIPanGestureRecognizer,

  3. Если ваш рисунок на холсте выполнен с помощью ручной сенсорной обработки (например, 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
    }
}

Вот гифка, показывающая поведение:

Этот вопрос, помеченный как повторяющийся, лучше описывает мою проблему: Отключение интерактивного закрытия представленного контроллера представления в iOS 13 при перетаскивании из основного представления

Не нужно изобретать велосипед. Это так же просто, как принять UIAdaptivePresentationControllerDelegateпротокол на вашем destinationViewController, а затем реализуйте соответствующий метод:

      func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
    return false
}

Например, предположим, что ваш destinationViewController подготовлен к переходу, как показано ниже:

      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 или любые другие, чтобы правильно обрабатывать ваше индивидуальное поведение.

Вы можете изменить стиль презентации, если он находится в полноэкранном режиме, раскрывающееся меню для закрытия будет отключено

navigationCont.modalPresentationStyle = .fullScreen

Сначала вы можете получить ссылку на UIPanGestureRecognizer, обрабатывающий закрытие листа страницы в методе viewDidAppear(). Обратите внимание, что эта ссылка равна нулю в viewWillAppear() или viewDidLoad(). Потом просто отключаешь.

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

Если вам нужна дополнительная настройка, а не ее полное отключение, например, при использовании navBar на листе страницы, установите делегатом этого UIPanGestureRecognizer свой собственный контроллер представления. Таким образом, вы можете отключить распознаватель жестов исключительно в вашем contentView, оставив его активным в вашем регионе navBar, реализовав

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {}

Вы можете использовать метод PresentationControllerDidAttemptToDismiss UIAdaptivePresentationControllerDelegate и отключить gestureRecognizer в PresentView. Что-то вроде этого:

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

В iOS 13

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

Для каждого человека, у которого есть проблемы с бегом №3 от Jordans.

Вам нужно искать ROOT viewcontroller, который представлен, в зависимости от вашего стека просмотров, возможно, это не ваше текущее представление.

Пришлось искать свои контроллеры навигации PresentationViewController.

Кстати @Jordam: Спасибо!

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

Я использую это:

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

Попробуем подробнее описать способ 2, уже предложенный @Jordan H:

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

Вот как код работает в моем случае. Без него жест смахивания в основном игнорировался и работал только изредка.

SwiftUI с iOS 15

          .interactiveDismissDisabled()

Например:

          .sheet(isPresented: $add) {
        AddView()
            .interactiveDismissDisabled()
    }

В случае, когда UITableView или UICollectionView инициирует жест закрытия листа страницы, когда пользователь пытается прокрутить верхний край прокручиваемого представления, этот жест можно отключить, добавив невидимый UIRefreshControl это зовёт endRefreshing немедленно.

См. Также /questions/53689844/ios13-predotvraschaet-opuskanie-tableview-kotoryij-prokruchivaetsya-vverh-ot-otk/53689854#53689854

В процессе подготовки (для: отправителя:):

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

iOS отключить распознаватели жестов для модального ViewController

      override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    //if you use UINavigationController
    self.navigationController?.presentationController?.presentedView?.gestureRecognizers?.forEach {
        $0.isEnabled = false
    }

    //if not
    self.presentationController?.presentedView?.gestureRecognizers?.forEach {
        $0.isEnabled = false
    }
}

presentedViewвозвращает не объект viewController.view

      //po navigationController?.presentationController?.presentedView
<UIDropShadowView: 0x10bd69100; frame = (0 57; 390 787); gestureRecognizers = <NSArray: 0x2821d26d0>; layer = <CALayer: 0x282f0c7c0>>

//po self.view
<UIView: 0x10bd67c90; frame = (0 0; 390 787); autoresize = W+H; backgroundColor = <UIDynamicSystemColor: 0x283ada180; name = systemBackgroundColor>; layer = <CALayer: 0x282f0ca40>>

Для навигации по Контроллеру, чтобы избежать смахивания для представленного представления, мы можем использовать:

if #available(iOS 13.0, *) {navController.isModalInPresentation = true}
Другие вопросы по тегам