Ответ
В 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 контролировать свою логику попадания (например, через переопределение этого метода).
Ответ 18+ 🔞
А, ну это же классика, блядь! UIKit и его хит-тест, ёпта. Сидит себе система, как хитрая жопа, и решает, куда ты пальцем ткнул. А под капотом-то — рекурсия, мать её!
Представь: у тебя окно, в нём вьюшка, в ней ещё три, а в одной из них кнопка спрятана. Ты тыкаешь в экран, а система начинает, как маньяк, сверху вниз шариться: «Тут? Не-а. А тут? Во, кажется, попал! А у тебя внутри кто живёт?».
Как оно, сука, работает, по шагам:
- Попадание в границы? Сначала смотрит: а в меня ли вообще тыкнули? Если точка за пределами
bounds— сразу отвал, возвращаетnil. Нечего тут шастать. - А живой ли я? Проверяет, не скрыт ли вью (
isHidden), не выключен ли (isUserInteractionEnabled) и не стал ли он призраком (alpha <= 0.01). Если что-то не так — всё, игнорируется, как пустое место. Прозрачнее 1% — и ты уже призрак, блядь! - Рекурсия — наше всё! Если сам-то вью живой и точка в нём, он начинает допрос своих детей —
subviews. Идёт он по ним задом наперёд! Кто последним добавлен (тот, кто обычно сверху визуально), тот первым и проверяется. Логично же, сука: что сверху, то и ближе к пальцу. - Финал. Если ни один ребёнок не признался, что в него попали, значит, тыкнули прямо в этого родителя. Он и возвращается со словами: «Да, это я, чё надо?».
Вот смотри, как это в коде выглядит, реальная логика:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// 1. Я вообще живой и кликабельный?
guard isUserInteractionEnabled, !isHidden, alpha > 0.01 else {
return nil // Нет, иди нахуй.
}
// 2. В меня ли тыкнули?
guard self.point(inside: point, with: event) else {
return nil // Мимо, промазал.
}
// 3. Опрашиваю детей, начиная с того, кто сверху (reversed)
for subview in subviews.reversed() {
// Перевожу точку в его систему координат
let convertedPoint = subview.convert(point, from: self)
// Рекурсивно спрашиваю: "Эй, пацан, это ты?"
if let hitView = subview.hitTest(convertedPoint, with: event) {
return hitView // Он говорит "да" — значит, он и есть цель.
}
}
// 4. Дети молчат — значит, попали в меня самого.
return self
}
А нахуя рекурсия-то? Да потому что иерархия вьюшек — это же дерево, ёпта! Каждая ветка (вью) должна сама решать, как на неё кликать. Можешь переопределить этот метод и сделать, например, кликабельную область больше, чем bounds. Или наоборот, дыру в середине создать. Вся мощь — в этом рекурсивном спуске, где каждый может сказать своё «муму», блядь!