Sobes.tech
Middle

Как системата определя в коя изглед е направено докосването?

sobes.tech AI

Отговор от AI

Системата използва два ключови механизма: hit-testing (тест за удар) и йерархията на изгледите.

  1. Hit-testing (Тест за удар): Framework-ът UIKit използва метода hitTest(_:with:) на кореновия изглед (UIWindow), за да определи кой изглед е под точката на докосване. Този метод рекурсивно преминава през йерархията на изгледите, започвайки от корена.

  2. Йерархия на изгледите:

    • Методът hitTest(_:with:) първо проверява дали точката на докосване е вътре в границите на текущия изглед. Ако не, връща nil.
    • Ако е вътре, методът преглежда децата на изгледа в обратен ред (от последно добавеното към първото). За всяко дете се извиква неговият собствен hitTest(_:with:) с същата точка на докосване (преобразувана към координатите на детето).
    • Първото дете, което връща резултат различен от nil (т.е. съдържа точката), се счита за "засечено" и този резултат се връща нагоре по йерархията.
    • Ако никое дете не съдържа точката, а текущият изглед я съдържа и userInteractionEnabled е true, тогава системата счита, че докосването е станало в текущия изглед, и методът връща self.
    • Ако userInteractionEnabled на текущия изглед е false, или hidden е true, или alpha е по-малко от 0.01, методът връща nil, дори ако точката е вътре в границите.

Процесът продължава надолу по йерархията, докато не бъде намерен най-долният изглед, съдържащ точката на докосване. Този изглед става hit-test view или hit-test result, и именно на него (или на неговия най-близък предшественик) ще бъде делегирано управлението на събитието.

Ето как може да се препише hitTest или point(inside:with:) за персонализирано поведение:

import UIKit

class CustomView: UIView {

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // Проверяваме дали точката е вътре в нашия изглед.
        if !self.point(inside: point, with: event) {
            return nil
        }

        // Обхождаме децата в обратен ред.
        for subview in subviews.reversed() {
            let convertedPoint = subview.convert(point, from: self)
            if let hitTestView = subview.hitTest(convertedPoint, with: event) {
                return hitTestView
            }
        }
        return self
    }

    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        // Стандартната проверка е дали точката е вътре в bounds и дали изгледът е активен.
        let extendedBounds = bounds.insetBy(dx: -20, dy: -20) // Разширява границите с 20 точки
        return extendedBounds.contains(point)
    }
}