Responder Chain — это механизм iOS, который последовательно передаёт события касания, нажатия клавиш и жестов через иерархию объектов UIResponder, пока один из них не обработает событие. Цепочка начинается с объекта, обнаружившего событие, и движется вверх по иерархии: от view к её superview, затем к view controller, window и, наконец, к UIApplication. По данным Apple Developer Documentation (2026), этот паттерн позволяет разделить ответственность за обработку событий между компонентами интерфейса, обеспечивая гибкость без жёсткой привязки к конкретному обработчику.
Главное
Responder Chain — это последовательность объектов UIResponder, через которые iOS передаёт события ввода, такие как касания, нажатия клавиш и акселерометр. Каждый объект в этой цепочке имеет возможность обработать событие или передать его следующему responder-объекту через свойство next.
Механизм основан на иерархии view: когда происходит касание, iOS сначала определяет, какая view была нажата (через hit-testing), и создаёт цепочку, начиная с этой view и поднимаясь до UIApplication. UIApplication является последним звеном цепочки — если событие доходит до него и не обрабатывается, оно просто отбрасывается.
По данным Apple, этот паттерн критически важен для инкапсуляции логики обработки событий. Разработчик может переопределить поведение конкретной view, не затрагивая остальные элементы иерархии. Например, UITextField становится first responder при фокусе и получает события клавиатуры, не требуя изменений в родительском UIViewController.
Обработка событий через Responder Chain происходит в два этапа: сначала iOS определяет, какая view получила событие (hit-testing), затем запускает цепочку ответчиков для его обработки. Если объект не реализует соответствующий метод, событие передаётся дальше.
Цепочка формируется динамически на основе текущего first responder и иерархии view. Стандартный порядок выглядит так: first responder → его view → superview → UIViewController → root view → UIWindow → UIApplication. Если любой из этих объектов реализует, например, touchesBegan, событие обрабатывается на этом уровне и не передаётся дальше.
import UIKit
class CustomView: UIView {
override func touchesBegan(
_ touches: Set<UITouch>,
with event: UIEvent?
) {
// Touch handled at this view level
print("CustomView handled touch")
// Forward event along the chain
super.touchesBegan(touches, with: event)
}
}
Ключевая особенность — вызов super.touchesBegan не является обязательным. Если его не вызвать, событие будет обработано только на текущем уровне и не пойдёт дальше по Responder Chain. Это даёт разработчику полный контроль над тем, какие объекты участвуют в обработке.
Когда объект получает событие, но не обрабатывает его (не переопределяет метод), iOS автоматически передаёт событие следующему responder-объекту через свойство next. Это свойство формирует односвязный список, который и называется Responder Chain.
UIViewController находится между своей view и UIWindow: если view не обрабатывает событие, контроллер получает шанс это сделать. Это особенно полезно для общей логики — например, обработки жеста, который должен работать на всей сцене, независимо от того, какая view под пальцем пользователя.
Hit-Testing — это процесс, с помощью которого iOS определяет, какая view находится под точкой касания. Метод hitTest:withEvent: обходит иерархию view от UIWindow вниз, проверяя, какая из дочерних view содержит точку касания и не скрыта.
Алгоритм работает рекурсивно: для каждого уровня iOS проверяет view в обратном порядке добавления (сначала верхние). Если view не скрыта, не прозрачна и точка попадает в её bounds, для неё запускается hitTest рекурсивно для всех subviews. Самая глубокая view, удовлетворяющая всем условиям, становится hit-test view — первым объектом в Responder Chain.
override func hitTest(
_ point: CGPoint,
with event: UIEvent?
) -> UIView? {
if isUserInteractionEnabled &&
isHidden == false &&
alpha > 0.01 &&
point(inside: point, with: event) {
return super.hitTest(point, with: event)
}
return nil
}
Разработчики могут переопределить hitTest, чтобы изменить стандартное поведение. Например, расширить область касания для маленькой кнопки или перенаправить событие на другую view, которая физически не находится под пальцем. Это мощный инструмент для создания кастомных интерактивных элементов.
UIResponder — базовый класс для всех объектов, которые могут обрабатывать события в iOS. UIView, UIViewController, UIApplication и UIWindow наследуются от UIResponder. Класс предоставляет набор методов, которые можно переопределить для обработки различных типов событий.
Основные группы методов включают touchesBegan, touchesMoved, touchesEnded, touchesCancelled для касаний; pressesBegan, pressesEnded для физических кнопок; и motionBegan, motionEnded для событий акселерометра. Каждый метод получает набор объектов UITouch или UIPress и ссылку на UIEvent, содержащий дополнительные метаданные о событии.
| Метод UIResponder | Назначение |
|---|---|
| touchesBegan | Вызывается при начале касания |
| touchesMoved | Вызывается при перемещении пальца |
| touchesEnded | Вызывается при отпускании пальца |
| touchesCancelled | Вызывается при прерывании (звонок, свайп шторки) |
| pressesBegan | Вызывается при нажатии физической кнопки |
Важно понимать, что iOS вызывает эти методы только для first responder и последующих объектов в цепочке. Если ни один объект не переопределил метод, событие не генерирует ошибку — оно просто игнорируется. Для отладки обработки событий используйте Symbolic Breakpoint на UIResponder touchEvent.
Стандартные UIKit компоненты активно используют Responder Chain для своей работы. UITextField становится first responder при получении фокуса, что автоматически открывает клавиатуру. UIButton обрабатывает касания через UIControl механизм, который также опирается на responder chain.
UITableView и UICollectionView используют цепочку ответчиков для обработки выделения ячеек и жестов прокрутки. Если пользователь касается ячейки, событие сначала получает сама ячейка, затем UITableView, и только потом UIViewController. UIGestureRecognizer имеет более высокий приоритет, чем touchesBegan — если на view добавлен распознаватель жестов, он получит событие первым.
По данным Apple, правильное использование Responder Chain критически важно для доступности приложения. VoiceOver и другие ассистивные технологии используют цепочку ответчиков для навигации между элементами интерфейса. Если цепочка разорвана, пользователи с ограниченными возможностями не смогут взаимодействовать с приложением.
UIMenuController для отображения контекстного меню также использует Responder Chain. Когда пользователь вызывает меню, система ищет first responder, который реализует методы canPerformAction и соответствующие action-методы. Меню отображается только для тех действий, которые поддерживает текущий responder.
Это позволяет, например, показать команды Cut, Copy, Paste только когда UITextField в фокусе, и скрыть их при работе с UILabel. Разработчик может добавить кастомные действия в контекстное меню, реализовав их в подклассе UIResponder и вернув true из canPerformAction.
Создание кастомного responder объекта даёт разработчику полный контроль над обработкой событий. Для этого нужно создать подкласс UIResponder (или UIView/UIViewController) и переопределить необходимые методы обработки событий.
Часто кастомные responder объекты используются для обработки специфических жестов, которые не покрываются стандартными UIGestureRecognizer. Например, распознавание рисования фигур, сложных multi-touch комбинаций или проприетарных паттернов ввода. Кастомный responder может агрегировать события от нескольких пальцев и принимать решение на основе их комбинации.
class DrawingResponder: UIResponder {
private var activeTouches: [UITouch: CGPoint] = [:]
override func touchesBegan(
_ touches: Set<UITouch>,
with event: UIEvent?
) {
for touch in touches {
activeTouches[touch] = touch.location(in: self)
}
}
override func touchesMoved(
_ touches: Set<UITouch>,
with event: UIEvent?
) {
for touch in touches {
let currentPoint = touch.location(in: self)
activeTouches[touch] = currentPoint
drawLine(from: activeTouches[touch]!, to: currentPoint)
}
}
}
При создании кастомного responder важно правильно настроить цепочку next. Если ваш объект не является частью стандартной иерархии UIKit, необходимо явно указать, какой объект будет его next responder. Это гарантирует, что необработанные события продолжат движение по Responder Chain.
Часто задаваемые вопросы
Responder Chain — это иерархическая цепочка объектов UIResponder, через которую iOS последовательно передаёт события касания, нажатий и жестов. Если объект не обрабатывает событие, оно передаётся следующему responder-объекту по цепочке до UIApplication.
Изменить порядок можно, переопределив свойство next у вашего UIResponder объекта. Вернув другой объект вместо стандартного, вы перенаправляете необработанные события на него. Это полезно для нестандартных иерархий, например, когда кастомный контейнер управляет несколькими дочерними контроллерами.
Hit-Testing определяет, какая view находится под точкой касания (исходный получатель), а Responder Chain определяет, как событие передаётся между объектами после hit-test. Hit-test находит первый объект, responder chain обеспечивает дальнейшую маршрутизацию, если этот объект не обрабатывает событие.
Чтобы прервать цепочку, достаточно обработать событие в вашем UIResponder и не вызывать super. Например, переопределив touchesBegan и не вызывая super.touchesBegan, вы предотвращаете передачу события дальше. Событие будет обработано на текущем уровне и не достигнет следующих звеньев цепочки.
Наиболее частые причины: isUserInteractionEnabled установлен в false, view скрыта (isHidden = true), alpha меньше 0.01, или view находится за пределами bounds родительского контейнера. Также проверьте, что на view или её superview нет UIGestureRecognizer, который перехватывает события до touchesBegan.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также