Responder Chain — це механізм iOS, який послідовно передає події дотику, натискання клавіш і жестів через ієрархію об'єктів UIResponder, поки один із них не обробить подію. Ланцюжок починається з об'єкта, який виявив подію, і рухається вгору по ієрархії: від view до її superview, потім до view controller, window і, нарешті, до UIApplication. Згідно з Apple Developer Documentation (2026), цей шаблон дозволяє розділити відповідальність за обробку подій між компонентами інтерфейсу, забезпечуючи гнучкість без жорсткого прив'язування до конкретного обробника.
Головне
Responder Chain — це послідовність об'єктів UIResponder, через які iOS передає вхідні події, такі як дотики, натискання клавіш та дані акселерометра. Кожен об'єкт у цьому ланцюжку має можливість обробити подію або передати її наступному об'єкту-відповідачу через властивість 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?
) {
// Дотик оброблено на цьому рівні view
print("CustomView обробив дотик")
// Переслати подію по ланцюжку
super.touchesBegan(touches, with: event)
}
}
Ключова особливість — виклик super.touchesBegan не є обов'язковим. Якщо його не викликати, подія буде оброблена тільки на поточному рівні та не піде далі по Responder Chain. Це дає розробнику повний контроль над тим, які об'єкти беруть участь в обробці.
Коли об'єкт отримує подію, але не обробляє її (не перевизначає метод), iOS автоматично передає подію наступному об'єкту-відповідачу через властивість 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.
Створення кастомного об'єкта-відповідача дає розробнику повний контроль над обробкою подій. Для цього потрібно створити підклас UIResponder (або UIView/UIViewController) та перевизначити необхідні методи обробки подій.
Часто кастомні об'єкти-відповідачі використовуються для обробки специфічних жестів, які не покриваються стандартними UIGestureRecognizer. Наприклад, розпізнавання малювання фігур, складних multi-touch комбінацій або пропрієтарних шаблонів введення. Кастомний відповідач може агрегувати події від кількох пальців та приймати рішення на основі їх комбінації.
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)
}
}
}
При створенні кастомного відповідача важливо правильно налаштувати ланцюжок next. Якщо ваш об'єкт не є частиною стандартної ієрархії UIKit, необхідно явно вказати, який об'єкт буде його next responder. Це гарантує, що необроблені події продовжать рух по Responder Chain.
Часті запитання
Responder Chain — це ієрархічний ланцюжок об'єктів UIResponder, через який iOS послідовно передає події дотику, натискань та жестів. Якщо об'єкт не обробляє подію, вона передається наступному об'єкту-відповідачу по ланцюжку до 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також