В этом уроке рассмотрим различные особенности работы с методами hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? и point(inside point: CGPoint, with event: UIEvent?) -> Bool

На эту тему встречается множество вопросов как теоретических, так и практических.

<aside> 💡 Вопросы и задания по этой теме встречались на интервью в компании Ivi, Mail и VK

</aside>

Для начала определимся с тем, что такое hit-testing и какую проблему он решает?

Hit-testing — это процесс определения того, пересекается ли точка, например, точка касания, с заданным графическим объектом, представленным на экране, например, UIView. iOS использует эту технику, чтобы определить, какой UIView находится на переднем плане под пальцем пользователя, и может ли UIView обработать касание. Он реализует это путем поиска в иерархии представлений с использованием алгоритма обхода в глубину (DFS) с обратным предварительным порядком (Reverse pre-order).

Задание 1

Реализуйте метод hitTest. Как он мог бы выглядеть в стандартной библиотеке UIKit?

func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { 
		//TODO: ???
}

Решение 1

func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard isUserInteractionEnabled, !isHidden, alpha > 0 else {
		    return nil
    }

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

       Как работает hitTest. Источник

   Как работает hitTest. [Источник](<https://smnh.me/hit-testing-in-ios>)
  1. Сперва проверяем, что вью может обрабатывать события касания:
  2. Далее проверяем, что точка лежит внутри вью, используя point(inside:, with:)
  3. В обратном порядке рекурсивно проверяем, существует ли в иерархии дочерняя вью, способная обработать нажатие.
  4. Если в иерархии представления нет такой вью, то возвращаем текущую вью.
  5. Если метод point(inside:, with:) вернул false, то возвращаем nil