Что такое Hit Test в UIKit?

Ответ

Hit Test — это механизм UIKit для определения представления (UIView), которое должно получить и обработать событие касания (touch event). Система рекурсивно проходит иерархию вью, начиная с корневого окна (UIWindow), чтобы найти самое глубокое (frontmost) представление, находящееся под точкой касания.

Алгоритм работы (в упрощенном виде):

  1. Проверяет, находится ли точка внутри bounds вью (bounds.contains(point)).
  2. Проверяет свойства, влияющие на взаимодействие: isUserInteractionEnabled, isHidden и alpha > 0.01.
  3. Для прошедших проверку вью рекурсивно вызывает hitTest(_:with:) для их подвью (порядок обхода — с конца массива subviews, т.е. от верхних к нижним).
  4. Если точка не попала ни в одну подвью, возвращает саму эту вью как результат.

Зачем переопределять hitTest? Для изменения области отклика вью или перенаправления событий.

Пример: Расширение кликабельной области кнопки за её видимые bounds.

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    // 1. Определяем расширенную область (например, +20pt со всех сторон)
    let expandedBounds = bounds.insetBy(dx: -20, dy: -20)

    // 2. Если точка в расширенной области и вью может получать события...
    if expandedBounds.contains(point) && !isHidden && isUserInteractionEnabled && alpha > 0.01 {
        // 3. ...возвращаем эту кнопку как цель для события
        return self
    }
    // 4. Иначе используем стандартное поведение
    return super.hitTest(point, with: event)
}

Важно: Не забывайте вызывать super.hitTest в default-кейсе, чтобы не сломать стандартное поведение для других областей.

Ответ 18+ 🔞

А, ну ты держись, сейчас я тебе про хит-тест так расскажу, что ты офигеешь. Это ж, блядь, как в поликлинике очередь — система ищет, кому в морду дать пощёчину, то есть, прости, тач-ивент отправить.

Представь: тыкнули пальцем в экран. А система, такая, ёпта, начинает сверху вниз, с главного окна, спускаться и спрашивать каждую вьюху: «Слышь, ты, квадратик, это тебя ткнули?». А та проверяет: я, сука, не спрятана ли (isHidden), меня, блядь, не выключили ли (isUserInteractionEnabled), и я не прозрачная, как совесть политика (alpha > 0.01). И главное — попала ли точка в мои границы? Если да — она начинает допрос своих детей, причём с самых верхних, самых заносчивых (это subviews.last), чтобы найти самого глубокого ублюдка под пальцем.

А зачем это всё, блядь, переопределять? Ну, например, когда у тебя кнопка размером с кнопку, а тыкать в неё должны с размаху, как слон в посудной лавке. Расширить её невидимую зону, чтобы даже криворукий пользователь попал.

Смотри, как это делается, на примере кнопки, которая хочет внимания, как сука на сковородке:

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    // 1. Делаем ей невидимые буферы, плюс 20 точек со всех сторон. Щас не попасть будет — только кривой.
    let expandedBounds = bounds.insetBy(dx: -20, dy: -20)

    // 2. Если тычок пришёлся в эту раздутую область, и вьюха в принципе жива и готова...
    if expandedBounds.contains(point) && !isHidden && isUserInteractionEnabled && alpha > 0.01 {
        // 3. ...то о, бля, это она и есть — цаца, лови событие!
        return self
    }
    // 4. А если мимо — ну, иди нахуй, пусть родитель разбирается.
    return super.hitTest(point, with: event)
}

Запомни, ебушки-воробушки: всегда в конце, когда уже ничего не подошло, возвращай super.hitTest. А то так своё умное поведение напишешь, что вся иерархия событий накроется медным тазом, и будет тебе пиздец, а не интерфейс.