Доставка событий на iOS: часть 1

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

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

Touch Handling

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

Проверка попадания

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

Вот где в игру вступает хит-тест.

В процессе проверки попадания используются следующие методы: hitTest: withEvent: и pointInside: withEvent:. hitTest: withEvent: использует pointInside: withEvent:, чтобы определить, находится ли точка для проверки попадания в его пределах. Если он находится за пределами границ, он возвращает nil и пропускает всю эту ветвь иерархии представлений.

Если проверяемая точка находится в пределах границ, она вызывает pointInside: withEvent: для каждого подпредставления. Для вложенного представления, которое возвращает YES из pointInside: withEvent:, вызывается hitTest: withEvent:. Конечный результат исходного вызова hitTest: withEvent: - это результат одного из вложенных представлений или себя в случае, когда все его вложенные представления возвращают ноль.

Возьмем следующую иерархию представлений:

Предположим, пользователь нажал в представлении E. Процесс начинается в представлении A с hitTest: withEvent:. pointInside: withEvent: возвращает YES для представления A, поэтому затем он вызывает pointInside: withEvent: для представления B и представления C. Представление B возвращает НЕТ. Представление C возвращает YES, поэтому для него вызывается hitTest: withEvent:. Представление C следует тому же процессу, что и представление D и представление E. Представление D возвращает NO для pointInside: withEvent:. Представление E возвращает YES, а затем возвращает себя при hitTest: withEvent:, потому что у него нет подвидов.

Предполагая, что представление D является подвидом представления C, что произойдет, если пользователь нажмет на представление D, где оно выходит за пределы представления C? (Это может произойти, если для параметра clipsToBounds установлено значение NO.) Представление A запускает описанный выше процесс. И представление B, и представление C возвращают NO для pointInside: withEvent:, поэтому представление A в конечном итоге получает касание.

Теперь у нас есть представление из hitTest: withEvent:. Это представление называется просмотром «проверки попадания». Теперь он связан с касанием, и ему будет предоставлена ​​первая возможность после любых жестов (подробнее об этом позже) отреагировать на события касания, пока касание активно.

Что произойдет, если у него нет специальной реализации для данного касания? Это зависит. Если представлением управляет контроллер представления, контроллеру представления предоставляется возможность ответить. Если этот контроллер представления не отвечает, супервизору представления проверки попадания дается возможность ответить. Этот процесс повторяется вплоть до так называемой «цепочки респондентов».

Цепочка респондента

Responder Chain - это реализация шаблона проектирования цепочки ответственности. Каждый участник в цепочке респондента наследует от UIResponder. UIResponder содержит дискретные методы для обработки различных типов событий. Помимо событий касания, UIResponder объявляет методы для обработки представлений ввода, событий движения, событий нажатия и событий удаленного управления.

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

Жесты

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

Жесты всегда получают первую возможность обработать событие касания. По умолчанию жест сначала касается касания, а затем просмотра. Основное отличие заключается в том, что когда состояние жеста достигает «распознано», прикосновения к представлению отменяются. Вы можете управлять тем, как эти события касания доставляются (или не доставляются) в представление, используя эти свойства в UIGestureRecognizer:, cancelsTouchesInView, delaysTouchesBegan и delaysTouchesEnded. Используйте эти свойства с умом, так как они потенциально могут создать иллюзию зависания представления.

Заключение

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

Чтобы получить больше информации о дизайне и разработке, подпишитесь на BPXL Craft и следите за Black Pixel в Twitter.