Отключить жест, чтобы раскрыть модальное представление формы / страницы
В iOS 13 модальные презентации, использующие форму и стиль листа, могут быть отклонены жестом панорамирования. Это проблематично на одном из моих листов формы, потому что пользователь рисует в этом поле, которое мешает жесту. Он тянет экран вниз вместо рисования вертикальной линии.
Как вы можете отключить вертикальное движение, чтобы отклонить жест в контроллере модального представления, представленном в виде листа?
настройка isModalInPresentation = true
все еще позволяет листу быть снесенным, это только не отклонит.
17 ответов
Я получил официальный совет для достижения этой цели. Если вы можете предотвратить запуск системы распознавания жестов панорамирования, это предотвратит отклонение жеста. Несколько способов сделать это:
Если ваш рисунок на холсте сделан с помощью распознавателя жестов, например, вашего собственного
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
}
}
Вот гифка, показывающая поведение:
Этот вопрос, помеченный как повторяющийся, лучше описывает мою проблему: Отключение интерактивного закрытия представленного контроллера представления в 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
немедленно.
В процессе подготовки (для: отправителя:):
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}