Как исключить UIView или UIViewController из цепочки ответов (Responder Chain) в UIKit?

Ответ

Responder Chain — это цепочка объектов (UIResponder), по которой распространяются события (касания, встряхивания, команды меню и т.д.). Чтобы исключить участника:

1. Переопределение свойства next Можно «пропустить» себя, вернув следующего ответчика дальше по цепочке.

class CustomView: UIView {
    override var next: UIResponder? {
        // Пропускаем родительский UIView и передаём событие дальше
        return super.next?.next
    }
}

2. Запрет стать First Responder Объект, который не может стать first responder, не будет получать события, предназначенные для него (например, ввод текста).

override var canBecomeFirstResponder: Bool {
    return false
}

3. Принудительный отказ от статуса First Responder Если объект в данный момент является first responder, можно заставить его уступить этот статус.

myTextField.resignFirstResponder()
// Или для любого UIResponder:
_ = myCustomView.resignFirstResponder()

4. Игнорирование конкретных событий Более точечный подход — переопределить методы обработки событий, чтобы они не передавались дальше.

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    // Не вызываем super — событие не пойдёт дальше по Responder Chain.
    // Выполняем только своё действие.
    handleTouchInternally()
}

Практический пример: Допустим, у вас есть контейнерная вью (ContainerView), которая должна игнорировать все касания, передавая их сразу дочерним элементам.

class PassthroughView: UIView {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let hitView = super.hitTest(point, with: event)
        // Если касание пришлось на саму PassthroughView, игнорируем её.
        return hitView == self ? nil : hitView
    }
}

Важно: Изменение стандартного поведения Responder Chain может нарушить ожидаемую логику работы контролов (кнопок, текстовых полей) и доступность. Действуйте осознанно.

Ответ 18+ 🔞

Ну слушай, вот есть у тебя эта цепочка ответчиков, Responder Chain, по которой события как по макаронам ползут. А иногда надо, чтобы какой-то ушлёпок из этой цепочки выпал, как пробка из ванны, и не мешался. Как это провернуть?

1. Подмена следующего звена Можно просто нагло соврать системе о том, кто следующий в цепочке. Переопредели свойство next и пропусти, например, своего непосредственного родителя, отдав следующего за ним. Хитро, блядь.

class CustomView: UIView {
    override var next: UIResponder? {
        // Пропускаем своего прямого предка и отдаём его предка. Ёпта, обошёл как лохушку.
        return super.next?.next
    }
}

2. Запрет на повышение Если объект не может стать первым ответчиком (first responder), то его и событиями, которые для этого статуса предназначены, хуячить не будут. Например, текстовый ввод мимо пройдёт.

override var canBecomeFirstResponder: Bool {
    return false // Нет, блядь, не хочу и не буду. Отстаньте.
}

3. Принудительное понижение А если этот пидор уже уселся в кресло первого ответчика и не слезает, можно его оттуда пинком под жопу снять.

myTextField.resignFirstResponder()
// Или так, для любого ответчика, чтоб наверняка:
_ = myCustomView.resignFirstResponder() // Иди нахуй со своего трона.

4. Точечное игнорирование Самый тонкий подход, блядь. Не ломаем всю цепь, а просто в нужном месте говорим: «Это моё, дальше не пущу». Переопределяешь метод обработки события и не вызываешь super.

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    // super не зовём — значит, событие здесь и сдохнет, в рот меня чих-пых.
    // Делаем что-то своё внутри.
    handleTouchInternally()
}

Пример из жизни, чтобы понятнее было: Допустим, есть у тебя контейнерная вьюха, которая должна быть прозрачной для касаний — как будто её нет, чтобы тапы сразу детям доставались.

class PassthroughView: UIView {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let hitView = super.hitTest(point, with: event)
        // Если касание попало прямо в эту вьюху (а не в её детей) — говорим, что никого не нашли.
        return hitView == self ? nil : hitView // Сам в пролёте, события идут сквозь.
    }
}

Но предупреждаю, ёпта: Ковыряться в стандартном поведении Responder Chain — это как жонглировать бензопилой. Можно нечаянно отпилить себе доступность всякую или сломать логику кнопок. Делай это с умом, а не просто потому, что можешь.