Какие методы используются в responder chain для обработки нажатия кнопки?
Что проверяют
Различаете ли вы две половины обработки касания: поиск view сверху вниз и передачу события снизу вверх. Кнопка тут ловушка: UIButton работает через target-action, а цепочка респондеров нужна ей лишь в особых случаях. Следом спрашивают, почему кнопка не нажимается или как увеличить ей область касания.
Как отвечать
Путь касания по шагам:
- hit-testing: окно вызывает
hitTest(_:with:), который рекурсивно проверяетpoint(inside:with:)и спускается к самому глубокому view под пальцем. View сisUserInteractionEnabled == false, скрытые или сalphaменьше 0.01 пропускаются вместе с поддеревом; - жесты: распознаватели на этом view и его предках получают касания раньше самого view. При
cancelsTouchesInView, по умолчаниюtrue, распознанный жест отменяет касание для view; - касания у респондера:
touchesBegan,touchesMoved,touchesEnded,touchesCancelledизUIResponder. По умолчанию событие уходит вnext: superview, корневой view контроллера, контроллер, окно,UIApplication; - кнопка:
UIControlотслеживает касание черезbeginTracking,continueTracking,endTracking,cancelTrackingи на.touchUpInsideвызываетsendAction(_:to:for:), которое уходит вUIApplication.sendAction(_:to:from:for:). Обработчик вешают черезaddTarget(_:action:for:)или, начиная с iOS 14, черезUIAction.
Если target равен nil, UIKit ищет получателя по цепочке через target(forAction:withSender:), который опирается на canPerformAction(_:withSender:). Так работают команды меню и copy(_:).
Где спотыкаются
Говорят, что hitTest передаёт событие по цепочке. Hit-testing идёт сверху вниз, цепочка респондеров снизу вверх, это разные механизмы.
Переопределяют touchesBegan в контроллере, чтобы ловить нажатия кнопки. Кнопка сама обработала касание, до контроллера оно не дойдёт. Кастомный контрол правильнее делать наследником UIControl.
Не понимают, почему кнопка на UIImageView не реагирует. У UIImageView и UILabel isUserInteractionEnabled по умолчанию false. Та же история с кнопкой за границами родителя: point(inside:with:) родителя вернёт false, даже если clipsToBounds выключен.