Добавить объявление

Решение проблем при конфликтах жестов в SwiftUI

Жесты в SwiftUI выглядят просто: повесил .gesture на вью и оно реагирует. Но как только на экране появляются вложенные элементы, системные свайпы или несколько жестов на одной вью - начинается хаос. Вместо красивой анимации вы получаете неправильное поведение. Сегодня разберем три механизма, которые ставят жесты под контроль.

Маска жеста - кто вообще имеет право слышать касание:


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

Тут вступает GestureMask. Это параметр модификатора .gesture(including:), который определяет, насколько глубоко в иерархии отслеживается ваш жест:

  • .all - жест срабатывает от касания в любой точке: в самой вью, в ее детях, даже в пустых областях. Идеально для сквозного перетаскивания.

  • .gesture - жест активен только на той вью, где его добавили. Дети и родители не реагируют. Это поведение по умолчанию.

  • .subviews - жест срабатывает, только если касание началось в дочерних вью. Неожиданный сценарий, но иногда полезен.

  • .none - все жесты глубже по иерархии отключаются.

На практике .all и .none встречаются чаще всего. .all удобен для джойстиков и перетаскиваемых контейнеров, .none - когда вы временно блокируете все управление во время анимации или загрузки. Два других варианта - скорее для узких сценариев.

Кто главный на сцене - приоритеты внутри одной вью:


Бывает, что на одной вью висят два жеста. Например, тап и долгое нажатие. По умолчанию SwiftUI пытается распознать оба жеста и вы получаете двойную реакцию.

РешениеhighPriorityGesture(_:including:). Этот модификатор говорит: «Этот жест важнее всех остальных на этой вью». Как только он начал распознаваться, остальные отменяются.

Типичный кейс: кнопка, которая и так реагирует на тап, но вы хотите, чтобы долгое нажатие перехватывало управление, не дожидаясь тапа. Вешаете тап через обычный .gesture, а долгое нажатие - через .highPriorityGesture. Теперь даже при коротком зажатии тап не вызовется.

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

Как переспорить системные жесты - свайп назад и другие навязчивые привычки iOS:


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

Модификатор .defersSystemGestures(on:) решает эту проблему. Он говорит системе: «Подожди, сначала пусть моя вью решит, что делать с этим свайпом». Если ваша вью не воспользовалась жестом (например, палец сместился меньше порога), тогда система запускает свой.

Важный нюанс: вы не блокируете системный жест полностью. Вы просто даете своей вью приоритет на время распознавания. Пользователь все равно сможет вызвать системный свайп, если целенаправленно потянет от края.

Передавайте в параметр массив сторон, например [.leading, .trailing]. Чаще всего нужен только .leading (левый край). И не ждите чудес - если ваша вью не обрабатывает жест вообще, система запустит свой довольно быстро. Этот модификатор полезен для игр, кастомных слайдеров и интерактивных карт. Для обычных кнопок он не нужен.

Вывод:


GestureMask, highPriorityGesture и defersSystemGestures - нужны только тогда, когда стандартное поведение SwiftUI не справляется. Пока на экране один жест на одну кнопку, все работает и без них. Но как только появляются вложенные вью и конкурирующие жесты, начинается хаос. Здесь главное не переборщить. Не надо лепить .all и высокие приоритеты везде подряд, иначе жесты начнут срабатывать там, где не должны, а пользователь решит, что приложение глючит.

Начинайте с простого: жест прямо на нужную вью. Не сработало - добавьте маску. Два жеста конфликтуют - назначьте главный через highPriorityGesture. Мешает системный жест назад - только тогда подключайте defersSystemGestures. И тестируйте на реальном устройстве, а не в симуляторе.
08.06.2026 21 548