Ответ
Для определения целевого UIView при касании iOS использует процесс hit-testing, который координируется методом hitTest(_:with:) у UIWindow и UIView.
Алгоритм hit-testing (поиска цели):
- Система начинает с корневого окна (
UIWindow) и передает ему точку касания. - Для каждой view рекурсивно вызывается
hitTest(_:with:), который работает так:- A. Проверка возможности быть целью: Вызывает
point(inside:with:), чтобы проверить, находится ли точка вboundsview. Также автоматически проверяет:isUserInteractionEnabled == trueisHidden == falsealpha > 0.01- Если условие не выполняется — возвращает
nil.
- B. Рекурсивный опрос subviews: Если точка внутри, метод проходит по массиву
subviewsв обратном порядке (от самого верхнего к нижнему). Для каждогоsubview:- Конвертирует точку в систему координат subview.
- Рекурсивно вызывает у subview
hitTest(_:with:). - Если subview возвращает не-
nil(нашел свою цель), этот результат и возвращается.
- C. Возврат себя: Если точка внутри view, но ни один из subviews не вернул цель, то эта view и становится целевой. Метод возвращает
self.
- A. Проверка возможности быть целью: Вызывает
Упрощенная реализация (иллюстрирующая логику):
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// 1. Быстрая проверка возможности быть целью
guard self.point(inside: point, with: event),
isUserInteractionEnabled,
!isHidden,
alpha > 0.01 else {
return nil
}
// 2. Рекурсивный опрос subviews (сверху вниз)
for subview in subviews.reversed() {
let convertedPoint = subview.convert(point, from: self)
if let candidate = subview.hitTest(convertedPoint, with: event) {
return candidate // Нашли цель в иерархии subview
}
}
// 3. Эта view — конечная цель
return self
}
Практическое применение:
- Увеличение области касания: Переопределить
point(inside:with:), чтобы возвращатьtrueдля области большеbounds. - Игнорирование касаний: Вернуть
nilвhitTest(_:with:), чтобы view "пропускала" события сквозь себя. - Перенаправление событий: Вернуть другую view в качестве результата
hitTest(_:with:).
Ответ 18+ 🔞
Ну ты смотри, какие страсти в айфоне творятся, когда ты пальцем по экрану шаркаешь! А там, блядь, целая операция под кодовым названием hit-testing идёт, сука! Это как в тире: система ищет, в какую вьюху ты пальцем ткнул, чтобы ей событие впендюрить.
Алгоритм этого самого поиска цели (hit-testing):
- Начинается всё с главного окна (
UIWindow), корня всего сущего. Ему и передают координаты твоего тычка. - А дальше пошла рекурсия, мать её! Для каждой вьюхи дергается метод
hitTest(_:with:), который работает по чёткому плану:- А. Проверка на профпригодность: Сначала дергается
point(inside:with:), чтобы понять, вообще в границы этой вьюхи точка попадает? Заодно автоматом глядит:isUserInteractionEnabled == true(чтоб не выключенная)isHidden == false(чтоб не спрятанная)alpha > 0.01(чтоб хоть чуть видимая)- Если хоть одно условие не прошёл — всё, пизда, возвращает
nil. Не кандидат.
- Б. Опрос подчинённых: Если точка внутри, начинается самое интересное. Метод лезет в массив
subviewsи проходит по нему ЗАДОМ НАПЕРЁД (от верхних слоёв к нижним). Для каждой дочерней вьюхи:- Пересчитывает точку в её координаты.
- И, охуеть, рекурсивно вызывает у этой детки её собственный
hitTest(_:with:). - Если какая-то subview нашла в себе цель и вернула не
nil— всё, поиск окончен, возвращается эта цель. Дальше не ищем, блядь!
- В. Самозванство: Если точка внутри этой вьюхи, но все её subviews развели руками (вернули
nil), значит цель — она сама, красавица! Метод возвращаетself.
- А. Проверка на профпригодность: Сначала дергается
Вот как это примерно внутри выглядит (упрощённо, для понятности):
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// 1. Быстрая проверка: а ты вообще кто такой, чтоб тебя трогали?
guard self.point(inside: point, with: event),
isUserInteractionEnabled,
!isHidden,
alpha > 0.01 else {
return nil // Отказ, иди нахуй
}
// 2. Опрашиваем детей, начиная с самых верхних (тех, что позже добавили)
for subview in subviews.reversed() {
let convertedPoint = subview.convert(point, from: self)
if let candidate = subview.hitTest(convertedPoint, with: event) {
return candidate // Всё, нашли виноватого, дальше не ищем!
}
}
// 3. Дети не при делах, значит это я и есть тот самый красавчик
return self
}
А где это, блядь, применить-то можно?
- Сделать кнопку больше, чем она есть: Переопределить
point(inside:with:), чтобы она возвращалаtrueдля области шире её видимых границ. Хитрая жопа! - Сделать вьюху-невидимку: Вернуть
nilв своёмhitTest(_:with:), и все события будут пролетать сквозь неё, как сквозь дуршлаг. Идеально для декоративных хуёвин. - Подставить другого: Вернуть в
hitTest(_:with:)не себя, а другую вьюху. Вот так просто, блядь, перенаправить событие! Главное — не запутаться, а то получится пиздопроебибна.