Sobes.tech
Middle

Kako sistem određuje u koju je vizuelnu komponentu dodir došao?

sobes.tech АИ

Одговор од АИ

Sistem koristi dva ključna mehanizma: hit-testiranje (testiranje dodira) i hijerarhiju prikaza.

  1. Hit-testiranje (Hit-testing): Framework UIKit koristi metodu hitTest(_:with:) korenskog prikaza (UIWindow) za određivanje koji prikaz se nalazi ispod tačke dodira. Ova metoda rekurzivno prolazi kroz hijerarhiju prikaza, počevši od korena.

  2. Hijerarhija prikaza:

    • Metoda hitTest(_:with:) prvo proverava da li je tačka dodira unutar granica trenutnog prikaza. Ako nije, vraća nil.
    • Ako jeste, metoda pretražuje decu prikaza u obrnutom redosledu (od poslednje dodate do prve). Za svako dete se poziva njegova sopstvena hitTest(_:with:) metoda sa istom tačkom dodira (preko transformacije u koordinate deteta).
    • Prvo dete za koje hitTest(_:with:) vrati rezultat različit od nil (tj. ono ili neki od njegovih potomaka sadrži tačku) smatra se "uhvaćenim" i taj rezultat se vraća na viši nivo.
    • Ako nijedno dete ne sadrži tačku, a trenutni prikaz je sadržava i userInteractionEnabled je true, tada sistem smatra da je dodir dogodio u trenutnom prikazu, i metoda vraća self.
    • Ako je userInteractionEnabled trenutnog prikaza false, ili hidden je true, ili alpha manji od 0.01, metoda vraća nil, čak i ako je tačka unutar granica.

Proces se nastavlja niz hijerarhiju dok se ne pronađe najniži prikaz koji sadrži tačku dodira. Taj prikaz postaje hit-test view ili hit-test rezultat, i upravo njemu (ili njegovom najbližem predku) će biti poverena dalja obrada događaja (touchesBegan, touchesMoved itd.).

Evo kako se može prepisati hitTest ili point(inside:with:) za prilagođeno ponašanje hit-testiranja:

import UIKit

class CustomView: UIView {

    override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
        // Proveravamo da li je tačka unutar našeg prikaza.
        if !self.point(inside: point, with: event) {
            return nil
        }

        // Prolazimo kroz decu u obrnutom redosledu.
        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 {
        // Osnovna implementacija proverava da li je tačka unutar granica i da li je prikaz interaktivan.
        let extendedBounds = bounds.insetBy(dx: -20, dy: -20) // Povećava oblast za 20pt sa svih strana
        return extendedBounds.contains(point)
    }
}