← Назад к вопросам

Что обрабатывает HIt Test?

2.0 Middle🔥 111 комментариев
#UIKit и верстка

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Hit Test — определение UI элемента, получившего touch

Что такое Hit Test

Hit test (проверка попадания) — это процесс, при котором iOS определяет, какой UIView получит touch event в ответ на касание пользователя.

Когда пользователь касается экрана, система проходит через иерархию view и ищет самый глубокий view, который находится в этой точке.

Как работает Hit Test

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    // 1. Проверяем, находится ли точка внутри нашего view
    guard self.bounds.contains(point) else {
        return nil  // Touch не попал в этот view
    }
    
    // 2. Проходим через все сублейеры
    for subview in subviews.reversed() {  // Обратный порядок!
        let convertedPoint = convert(point, to: subview)
        if let hitView = subview.hitTest(convertedPoint, with: event) {
            return hitView  // Нашли самый глубокий view
        }
    }
    
    // 3. Если no subviews hit, возвращаем сам себя
    return self
}

Пример: порядок обхода

Window
├── ViewController.view
│   ├── Button A (добавлен первый)
│   ├── Button B (добавлен вторым)
│   └── Button C (добавлен третьим)

Если касание попадает на перекрытие Button B и Button C, то Button C получит touch (был добавлен последним, на вершине).

Hit test проходит в обратном порядке (от последнего к первому).

Переопределение Hit Test

Кейс 1: Расширить область попадания

class LargeButton: UIButton {
    let hitAreaInsets = UIEdgeInsets(top: -10, left: -10, bottom: -10, right: -10)
    
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        let expandedBounds = bounds.inset(by: hitAreaInsets)
        guard expandedBounds.contains(point) else {
            return nil
        }
        return super.hitTest(point, with: event)
    }
}

Кейс 2: Отключить интерактивность

class NonInteractiveView: UIView {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // Всегда пропускаем touches
        return nil
    }
}

Кейс 3: Перенаправить touch другому view

class PassthroughView: UIView {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // Пропускаем себя, передаём супервью
        return super.hitTest(point, with: event)
    }
}

Практический пример: кастомный overlay

class ModalOverlay: UIView {
    weak var contentView: UIView?
    
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // Разрешаем touches только для контентного view
        guard let contentView = contentView else {
            return nil  // Overlay не интерактивен
        }
        
        let convertedPoint = convert(point, to: contentView)
        if contentView.bounds.contains(convertedPoint) {
            return contentView.hitTest(convertedPoint, with: event)
        }
        
        // Touch вне contentView — пропускаем
        return nil
    }
}

Связь с Point Inside

override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
    // Проверяет, находится ли точка внутри view
    // Используется внутри hitTest
    return bounds.contains(point)
}

UIControl и Hit Test

class CustomButton: UIControl {
    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        if isEnabled && !isHidden && alpha > 0.01 {
            return super.hitTest(point, with: event)
        }
        return nil  // Disabled кнопка не получает touches
    }
}

Отладка Hit Test

Способ 1: Логирование

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    print("Hit test для \(self) в точке \(point)")
    return super.hitTest(point, with: event)
}

Способ 2: Визуальная отладка в Xcode

  • Debug → View Hierarchy
  • Показывает структуру view
  • Подсвечивает элементы при hover

Ключевые свойства

let view = UIView()

// Влияют на hit test
view.isHidden = true    // Скрытый view не получит touch
view.alpha = 0          // Прозрачный view не получит touch
view.isUserInteractionEnabled = false  // Отключить touches
view.bounds = .zero     // Пустой view не получит touch

Порядок Z-axis (layer order)

// Порядок добавления определяет Z порядок
view.addSubview(buttonA)  // z = 0
view.addSubview(buttonB)  // z = 1
view.addSubview(buttonC)  // z = 2 (вверху)

// Если все три перекрываются, touch пойдёт в buttonC

Ключевые правила

✅ Hit test проходит в обратном порядке (от вершины вниз) ✅ Переопределяй hitTest когда нужна кастомная логика ✅ Помни про isUserInteractionEnabled, alpha, isHidden ✅ Расширяй области попадания для small buttons ✅ Используй hitTest для сложных жестов ❌ Не забывай вызывать super.hitTest ❌ Не игнорируй isHidden — это влияет на hit test

Hit test — критический механизм для правильной обработки пользовательских касаний. Правильное понимание этого процесса важно для создания responsive интерфейсов.