Как работает hit-test в UIKit: рекурсивно или итеративно?

«Как работает hit-test в UIKit: рекурсивно или итеративно?» — вопрос из категории UIKit, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В UIKit hit-test выполняется рекурсивно. Алгоритм начинается с корневого view (обычно UIWindow) и движется вниз по иерархии.

Процесс:

  1. Проверяется, находится ли точка касания в пределах bounds текущего view.
  2. Если да, метод рекурсивно вызывается для его subviews в обратном порядке (последний добавленный проверяется первым).
  3. Если точка не попадает ни в один subview, возвращается сам view.
  4. Если view скрыто (isHidden), отключено для взаимодействия (isUserInteractionEnabled) или имеет почти нулевую прозрачность (alpha <= 0.01), оно игнорируется.

Пример реализации hitTest(_:with:):

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    // 1. Проверка возможности взаимодействия
    guard isUserInteractionEnabled, !isHidden, alpha > 0.01 else {
        return nil
    }
    // 2. Проверка попадания точки в bounds
    guard self.point(inside: point, with: event) else {
        return nil
    }
    // 3. Рекурсивный обход subviews (в обратном порядке)
    for subview in subviews.reversed() {
        let convertedPoint = subview.convert(point, from: self)
        if let hitView = subview.hitTest(convertedPoint, with: event) {
            return hitView
        }
    }
    // 4. Точка внутри этого view, но не внутри его subviews
    return self
}

Почему рекурсивно? Это естественно отражает древовидную структуру view-иерархии и позволяет каждому view контролировать свою логику попадания (например, через переопределение этого метода).