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

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

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

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

В Apple Developer есть хорошо объясненный документ: developer.apple.com/documentation/uikit/view_controllers/…

Stleamist 01.06.2020 02:03

Но они не объясняют, как решить, когда этот жест мешает другим, как задается этот вопрос.

teradyl 12.04.2021 21:08
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
98
2
44 117
15
Перейти к ответу Данный вопрос помечен как решенный

Ответы 15

Этот жест можно найти в свойстве 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 и, возможно, повлияет на ваше приложение.

Обратите внимание, что если у вас есть представление прокрутки в этом контроллере представления, кажется, что вы все еще можете потянуть вниз, чтобы закрыть, как только вы достигли вершины. Но если у вас нет прокрутки, это здорово.

Jordan H 23.06.2019 17:46

@JordanH Верно! Кажется, другой жест из представления прокрутки также каким-то образом обрабатывает модальное закрытие. Я напечатал жесты просмотра прокрутки, и там есть UISwipeDismissalGestureRecognizer. Это может быть проблемой.

M Reza 23.06.2019 18:04
UITableView также создает _UISwipeDismissalGestureRecognizer. Более того, если вы создадите навигационный контроллер с корневым контроллером представления, модально представите стек в виде листа страницы/формы и поместите другой контроллер представления поверх него, жест прокрутки вниз для закрытия закроет весь стек через распознаватель жестов, созданный где-то выше по иерархии UIView. Без явной поддержки Apple для отключения обработки сенсорных событий смахиванием вниз, чтобы закрыть, единственным надежным решением (начиная с Xcode 11 beta 3) является использование UIModalPresentationStyle из UIModalPresentationFullScreen.
Gary 16.07.2019 14:30

Вы даже можете выполнять поиск по имени или типу, если хотите быть в большей безопасности в отношении того, какой жест вы отключаете. for gesture in guestures where gesture.name == "_UISheetInteractionBackgroundDismissRecognizer" { gesture.isEnabled = false }

spfursich 17.02.2020 21:19

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

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

Нет, но это не мешает жест, о чем и идет речь.

matt 04.11.2019 16:14
Ответ принят как подходящий

Как правило, не следует пытаться отключить функцию смахивания для закрытия, так как пользователи ожидают, что все листы форм/страниц будут вести себя одинаково во всех приложениях. Вместо этого вы можете рассмотреть возможность использования стиля полноэкранного представления. Если вы хотите использовать лист, который нельзя закрыть с помощью смахивания, установите isModalInPresentation = true, но обратите внимание, что это по-прежнему позволяет листу опускаться вертикально, и он подпрыгивает обратно после отпускания касания. Ознакомьтесь с документацией по UIAdaptivePresentationControllerDelegate, чтобы реагировать, когда пользователь пытается закрыть его, проведя пальцем по экрану, помимо других действий.

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

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

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

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

  3. Если ваш рисунок на холсте выполняется с ручной обработкой касаний (например, touchesBegan:), переопределите -gestureRecognizerShouldBegin в представлении обработки касаний и верните NO, если переданный распознаватель жестов является UIPanGestureRecognizer.

С моей установкой №3 оказалось, что она работает очень хорошо. Это позволяет пользователю провести пальцем вниз в любом месте за пределами холста для рисования, чтобы закрыть (например, панель навигации), и в то же время позволяет пользователю рисовать, не перемещая лист, как и следовало ожидать.

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

Я последовал #2, но в итоге использовал метод делегата gestureRecognizer(_:,shouldRecognizeSimultaneouslyWith:), чтобы позволить некоторым распознавателям работать вместе, а другим нет.

Doug 05.09.2019 11:09

Кажется, я не могу заставить что-либо из этого работать? Любые советы для моего сценария? Я представляю экран с системой выбора фотографий, используя перетаскивание, чтобы выбрать то, что использует стандартное приложение для фотографий iOS. Это использует распознаватель жестов панорамирования и UICollectionView. Простите меня, если я ошибаюсь, но не должны ли мы стать делегатом отклоняющего распознавателя, чтобы использовать любой из этих методов?

simonthumper 19.10.2019 11:17

Не бери в голову! Использовал shouldRequireFailureOf вместо shouldBeRequiredToFailBy ?‍♂️

simonthumper 19.10.2019 11:23

Приходя из Android, я искал способ, которым мои пользовательские представления могли бы взять на себя сенсорное управление, и жестRecognizerShouldBegin идеален! Спасибо!

Jona 23.10.2019 16:19

№ 3 отлично сработал для моего приложения, которое позволяет пользователю рисовать в представлении.

Rory Prior 24.10.2019 23:32

@Jordan H Не могли бы вы лучше объяснить № 3 с помощью кода?

Giorgio 30.10.2019 10:11

@ jordan-h У меня тоже проблемы с работой #3. Я добавил разные распознаватели (касание, панорамирование, длительное нажатие...) как в вид контроллера представления, так и в вид чертежа, но безуспешно. Модальное окно все еще перемещается при рисовании на виде.

Eneko Alonso 31.10.2019 18:31

Отличная работа! Очень интересно. №3 работает идеально для меня.

Artem Kirillov 08.11.2019 19:59

Вся эта дополнительная работа, потому что Apple захотела изменить стиль модального представления...

anon_nerd 31.12.2019 17:43

Для # 2 мне нужно было отличить UIPanGestureRecognizer, которым я владел, от одного, контролирующего UITableView/UIScrollView, и другого, контролирующего модальное окно. Все три из них являются UIPanGestureRecognizers, поэтому проверка их типа не помогает. Следующее, что лучше всего сделать, это проверить представление, на которое они нацелены. Модальный распознаватель жестов нацелен на UIDropShadowView, который является частным API, поэтому вы не можете проверить это явно. Вместо этого, в зависимости от того, как устроена иерархия ваших представлений, вы можете проверить, не является ли это UIDropShadowView вашим собственным подпредставлением, чтобы исключить его.

Ruiz 23.01.2020 21:25

# 3 очень хорошо работает для меня с формой захвата подписи

Jim Geldermann 01.03.2021 15:19

# 3 также работал для UIControl, который реализовал методы «отслеживания» для обработки касаний — я использовал это внутри func: `return isTracking? !(gestureRecognizer — это UIPanGestureRecognizer): true`

androidguy 30.05.2021 04:55

Используйте это в представленном ViewController viewDidLoad:

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

Как отмечалось в вопросе, isModalInPresentation = true по-прежнему позволяет вытягивать лист, он просто не убирается, что может быть именно тем, что вам нужно, или может быть проблематичным в зависимости от вашего варианта использования, как это было для моего холста для рисования.

Jordan H 26.09.2019 17:44

Ты прав. Я думаю, что самое простое решение — использовать старый полноэкранный модальный стиль: self.modalPresentationStyle = .fullScreen

Zoltan Vinkler 27.09.2019 09:34
viewController.isModalInPresentation = true сработало для меня
BharathRao 07.01.2020 08:39

это правильный ответ, вы все еще сохраняете анимацию опускания, но вы никогда не закрываете вид этим. Спасибо!

Radu Ursache 13.01.2020 14:39

Работает на меня. Это лучше, чем удаление распознавателей жестов.

Benoit Deldicque 10.02.2020 15:41

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

navigationCont.modalPresentationStyle = .fullScreen

Я думаю, что это правильный ответ. На самом деле я использовал это вместе с isModalInPresentation, просто чтобы быть уверенным, и это работает отлично. Ключом для меня было установить их в родителях. Когда я попытался установить viewDidLoad в представленном контроллере, это НЕ сработало.

biomiker 27.07.2020 19:33

для меня я думаю, что это также правильный способ справиться с этим. Если это полноэкранный режим, вы не можете смахнуть и закрыть его? пробовал, не получается, так что это правильно

Legend_ 33 10.03.2021 11:34

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

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-контроллер представления, который представлен, в зависимости от вашего стека представлений, возможно, это не ваше текущее представление.

Пришлось искать свои навигационные контроллеры 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 {
        $0.isEnabled = false
    }
}

Звонили из touchesEnded:

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

Вот GIF, показывающий поведение:

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

Это не работает на iOS 14; navigationController?.presentationController?.presentedView всегда nil. Я пытался вместо этого использовать navigationController?.presentationController?.presentedViewC‌​ontroller.view, но не могу найти _UISheetInteractionBackgroundDismissRecognizer там...

Ido 31.03.2021 17:37

Странно, только что протестировали на iOS 14, наше приложение отлично работает. Построен с Xcode 12, не уверен, что это будет иметь значение

Eneko Alonso 31.03.2021 17:45

Моя ошибка, я снова открыл какой-то старый проект и забыл, что это означало исправить что-то на другом экране, он отлично работает на iOS 14, спасибо за подтверждение и спасибо за ваш ответ!

Ido 31.03.2021 20:02

В моем случае navigationController всегда равен нулю, поэтому я смог получить к нему доступ с помощью presentationController?.presentedView?.gestureRecognizers?.f‌​orEach

Chuck Krutsinger 10.09.2021 00:47

У меня почти сработало, за исключением того, что распознаватели жестов, которые нужно было заблокировать, были self.presentationController?.presentedView?.gestureRecognize‌​rs?, а не navigationController?.presentationController?.presentedView?‌​.gestureRecognizers?‌​. См. ответ @M Reza

J Kasparian 05.03.2022 14:19

Постараюсь более подробно описать метод 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
    }
}

В моем случае у меня есть только несколько распознавателей жестов смахивания, поэтому мне достаточно сравнения типов, но если их больше, может иметь смысл сравнить сами распознаватели жестов (либо добавленные программно, либо как выходы из построителя интерфейса), как описано в этот документ: 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 30.03.2020 16:03

@Weslie, вам нужно поймать распознаватели представленного представления, а не распознаватели его суперпредставления. Попробуйте временно добавить метод делегатаgestRecognizerShouldBegin, чтобы увидеть, какие жесты на самом деле перехватываются и отлаживаются.

Vitalii 31.03.2020 12:57

в iOS 13

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

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

См. также https://stackoverflow.com/a/58676756/2419404

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

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

Сначала вы можете получить ссылку на 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 {}

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

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

Это должен быть принятый ответ. Он правильно решает проблему OP самым чистым способом. Я только что реализовал это, и это работает именно так, как вы ожидаете. Таким образом, вы также можете вернуть true или false на основе чего угодно.

nickdnk 14.06.2021 01:18

Вместо того, чтобы устанавливать делегат в prepare(for:sender:) предыдущего контроллера представления, вы можете иметь presentationController?.delegate = self в viewDidLoad. Таким образом, весь код находится в одном контроллере представления.

Allanah Fowler 29.11.2021 03:23

Другие вопросы по теме