← Назад к вопросам

Hit test проходит рекурсивно или итеративно?

2.0 Middle🔥 101 комментариев
#UIKit и верстка

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Рекурсия или итерация в hitTest

В iOS при обработке касаний система использует рекурсивный подход для выполнения hitTest. Это фундаментальный механизм, который работает сверху вниз по иерархии представлений (UIView), начиная от корневого окна (UIWindow) и до самого глубокого дочернего представления.

Как работает рекурсивный hitTest

Основной метод hitTest(_:with:) класса UIView реализован рекурсивно. Вот его упрощённая логика:

  1. Проверка условий: Метод сначала проверяет, может ли представление участвовать в hit-тестировании:

    • isUserInteractionEnabled == true
    • isHidden == false
    • alpha > 0.01 Если любое условие не выполняется, метод возвращает nil.
  2. Проверка попадания в границы: Метод проверяет, находится ли точка касания внутри границ представления с помощью point(inside:with:):

    if !self.point(inside: point, with: event) {
        return nil
    }
    

    Если точка вне границ, возвращается nil.

  3. Рекурсивный обход дочерних представлений: Если точка внутри границ, система начинает рекурсивно проверять дочерние представления в обратном порядке Z-индекса (от последнего добавленного к первому):

    for subview in subviews.reversed() {
        let convertedPoint = subview.convert(point, from: self)
        if let hitView = subview.hitTest(convertedPoint, with: event) {
            return hitView
        }
    }
    

    Этот цикл вызывает hitTest для каждого дочернего представления, передавая преобразованную точку.

  4. Возврат результата: Если ни одно дочернее представление не вернуло результат (точка не попала в них или они не участвуют в hit-тестировании), метод возвращает self — текущее представление становится hit-представлением.

Пример рекурсивной цепочки

Рассмотрим иерархию:

UIWindow
└── ContainerView (alpha: 1.0, userInteractionEnabled: true)
    ├── Button (frame: (20, 20, 100, 50))
    └── Label (frame: (150, 20, 100, 50))

При касании в точке (40, 30):

  1. hitTest вызывается для UIWindow.
  2. UIWindow проверяет попадание в границы → точка внутри.
  3. UIWindow рекурсивно вызывает hitTest для ContainerView (его единственного дочернего).
  4. ContainerView проверяет попадание → точка внутри.
  5. ContainerView в цикле for subview in subviews.reversed():
    • Сначала проверяет Label → точка вне границ → nil.
    • Затем проверяет Button → точка внутри границ → Button вызывает hitTest для своих дочерних (их нет) → возвращает self.
  6. Результат (Button) передаётся вверх по цепочке вызовов.

Почему рекурсия, а не итерация?

  1. Естественное соответствие иерархии представлений: Рекурсия идеально отражает древовидную структуру UIView, где каждое представление может иметь дочерние.
  2. Простота реализации и понимания: Рекурсивный алгоритм более интуитивно понятен для обхода деревьев.
  3. Гибкость переопределения: Разработчики могут переопределять hitTest(_:with:) в подклассах UIView, добавляя кастомную логику (например, расширение области касания), и при этом сохраняется естественный flow.
  4. Эффективность для типичных сценариев: Глубина иерархии представлений в iOS редко превышает 10-15 уровней, поэтому риск переполнения стека минимален.

Важные нюансы

  • Оптимизация: Хотя алгоритм рекурсивный, система iOS может оптимизировать его выполнение, особенно при обработке множественных касаний или в сложных анимациях.
  • Переопределение метода: При кастомной реализации важно сохранять базовую логику, иначе можно нарушить всю цепочку обработки событий:
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // Кастомная логика, например, расширение hit-области
        let expandedRect = bounds.insetBy(dx: -10, dy: -10)
        if expandedRect.contains(point) {
            return super.hitTest(point, with: event)
        }
        return nil
    }
    
  • Производительность: Слишком глубокая иерархия представлений или сложная логика в hitTest может повлиять на отзывчивость интерфейса. В таких случаях рекомендуется упрощать структуру или кэшировать результаты.

Таким образом, hitTest в iOS действительно реализован рекурсивно, что является наиболее естественным и эффективным подходом для обхода иерархии представлений и определения целевого объекта для событий касания.

Hit test проходит рекурсивно или итеративно? | PrepBro