В чем разница между методами point(inside:with:) и hitTest(_:with:) в UIKit?

«В чем разница между методами point(inside:with:) и hitTest(_:with:) в UIKit?» — вопрос из категории UIKit, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Эти методы работают в тандеме для определения получателя touch-событий в иерархии UIView.

point(inside:with:)

  • Назначение: Проверяет, находится ли заданная точка в пределах границ (bounds) текущего view.
  • Возвращает: Bool (true, если точка внутри).
  • Логика: Простая геометрическая проверка. Не учитывает иерархию subviews.
  • Типичное использование: Переопределяется для расширения или изменения области отклика view без изменения его видимых границ.
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        // Увеличиваем область отклика на 10 пунктов во все стороны
        let expandedBounds = bounds.insetBy(dx: -10, dy: -10)
        return expandedBounds.contains(point)
    }

hitTest(_:with:)

  • Назначение: Определяет самое глубокое (frontmost) view в иерархии, которое должно получить touch-событие для заданной точки.
  • Возвращает: UIView? (найденное view или nil).
  • Алгоритм работы (рекурсивный):
    1. Проверяет, может ли view вообще получать события: isUserInteractionEnabled, !isHidden, alpha > 0.01.
    2. Вызывает point(inside:with:) для себя. Если точка вне границ — возвращает nil.
    3. Если точка внутри, проходит по subviews в обратном порядке (от самого верхнего к нижнему), вызывая для каждого hitTest.
    4. Если один из subviews вернул не-nil view, возвращает его. Если нет — возвращает себя.
  • Типичное использование: Переопределяется для перенаправления событий или создания нестандартного поведения (например, игнорирование touches в определенной области).
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let hitView = super.hitTest(point, with: event)
        // Если событие должно попасть в прозрачную кнопку-оверлей, возвращаем её
        return (hitView == self) ? overlayButton : hitView
    }

Взаимосвязь: hitTest использует point(inside:with:) как один из шагов в своем алгоритме. Переопределение point(inside:with:) напрямую влияет на результат работы hitTest для этого view.