Ответ
Hit-Testing — это процесс определения, какое представление (UIView) должно получить событие касания. Он запускается при каждом новом касании (touch event) системой UIApplication.
Алгоритм (метод hitTest(_:with:)):
Процесс рекурсивный, начинается с корневого окна (UIWindow) и идет от верхних вью к нижним.
- Проверяются условия для текущей вью:
isUserInteractionEnabled == trueisHidden == falsealpha > 0.01- Точка касания находится внутри границ вью (
bounds).
- Если условия не выполнены – вью и всё её поддерево игнорируются.
- Если условия выполнены, алгоритм проходит по массиву
subviewsв обратном порядке (от последней добавленной, которая визуально выше, к первой). - Для каждой сабвью рекурсивно вызывается
hitTest. - Если сабвью возвращает не-
nil(нашла подходящую вью), этот результат возвращается. - Если ни одна сабвью не вернула результат, возвращается сама текущая вью.
Пример переопределения для расширения области касания:
class CustomButton: UIButton {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// 1. Проверяем стандартные условия
guard self.isUserInteractionEnabled,
!self.isHidden,
self.alpha > 0.01 else {
return nil
}
// 2. Расширяем область проверки на 20 пунктов во все стороны
let expandedBounds = bounds.insetBy(dx: -20, dy: -20)
// 3. Если точка в расширенной области, возвращаем self (эту кнопку)
if expandedBounds.contains(point) {
return self
}
// 4. Иначе передаем обработку стандартному алгоритму
return super.hitTest(point, with: event)
}
}
Итог: Hit-Testing гарантирует, что событие касания получает самый глубокий (верхний в иерархии) UIView, готовый его обработать.
Ответ 18+ 🔞
Слушай, а вот эта ваша хит-тестинговая магия — это ж просто пиздец какой-то цирк с конями, ей-богу. Сидит себе система, как хитрая жопа, и думает: «Ну-ка, чувак, куда ты там ткнул своим жирным пальцем?» И начинает этот ебёный детектив.
Представь: каждый раз, как ты тыкаешь в экран, UIApplication такой: «Опа, новое касание, блядь! Ну-ка, UIWindow, давай, работай, сука!» И пошла писать губерния.
Алгоритм, он же метод hitTest(_:with:) — это рекурсивный пиздец.
Начинается всё с корневого окна и идёт сверху вниз, то есть от тех вьюх, которые визуально наверху, к тем, что под ними.
- Сначала вьюху на месте преступления допрашивают по трём пунктам:
isUserInteractionEnabled == true— чтоб с ней вообще можно было взаимодействовать, а не как с пнём.isHidden == false— чтоб она не была спрятана, как маньяк в кустах.alpha > 0.01— чтоб её хоть чуть видно было, а не как призрак.- И главное — точка касания должна быть внутри её границ (
bounds). Не попал — иди нахуй.
- Если хоть одно условие провалено — вся эта вьюха и её дети-сабвьюхи отправляются в игнор. Пиздуй отсюда.
- Если же всё чики-пуки, начинается самое интересное. Алгоритм лезет в массив
subviewsи проходит по нему в обратном порядке. То есть смотрит сначала на ту, которую добавили последней и которая, блядь, сейчас сверху всех лежит. - Для каждой такой сабвьюхи он рекурсивно вызывает этот же самый
hitTest. И так, сука, углубляется, пока не упрётся. - Если какая-то сабвьюха нашла себя и возвращает не-
nil— всё, квест завершён, результат всплывает наверх. - Если все дети молчат как рыбы — ну что ж, тогда возвращается сама текущая вьюха. «Значит, ткнули именно в меня, блядь».
Вот тебе пример, как можно наёбнуть систему и расширить зону клика, если кнопка мелкая, как хуй с горы:
class CustomButton: UIButton {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// 1. Сначала стандартные проверки, без них нихуя не получится
guard self.isUserInteractionEnabled,
!self.isHidden,
self.alpha > 0.01 else {
return nil
}
// 2. Расширяем область проверки на 20 пунктов во все стороны, чтоб легче было попасть
let expandedBounds = bounds.insetBy(dx: -20, dy: -20)
// 3. Если ткнули в эту расширенную зону — о, сука, попал! Возвращаем саму кнопку.
if expandedBounds.contains(point) {
return self
}
// 4. Если не попали — ну что ж, пусть родительский алгоритм разбирается
return super.hitTest(point, with: event)
}
}
Итог, блядь: Весь этот хит-тестинг существует для одного — чтобы событие касания получил самый глубокий (и визуально верхний) UIView, который готов его принять. Всё честно, как в хорошем борделе: кто первый встал, того и тапки.