Ответ
В UIKit hit-test выполняется рекурсивно. Алгоритм начинается с корневого view (обычно UIWindow) и движется вниз по иерархии.
Процесс:
- Проверяется, находится ли точка касания в пределах
boundsтекущего view. - Если да, метод рекурсивно вызывается для его subviews в обратном порядке (последний добавленный проверяется первым).
- Если точка не попадает ни в один subview, возвращается сам view.
- Если 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 контролировать свою логику попадания (например, через переопределение этого метода).