Jak system określa, w którym widoku nastąpiło dotknięcie?
sobes.tech AI
Odpowiedź od AI
System używa dwóch kluczowych mechanizmów: testu trafienia (hit-testing) i hierarchii widoków.
-
Hit-testing: Framework UIKit używa metody
hitTest(_:with:)głównego widoku okna (UIWindow), aby określić, który widok znajduje się pod punktem dotyku. Ta metoda rekurencyjnie przechodzi przez hierarchię widoków, zaczynając od głównego. -
Hierarchia widoków:
- Metoda
hitTest(_:with:)najpierw sprawdza, czy punkt dotyku znajduje się w granicach bieżącego widoku. Jeśli nie, zwracanil. - Jeśli tak, metoda przegląda widoki potomne w odwrotnej kolejności (od ostatnio dodanego do pierwszego). Dla każdego widoku potomnego wywołuje własną metodę
hitTest(_:with:)z tym samym punktem dotyku (przekształconym na współrzędne widoku potomnego). - Pierwszy widok potomny, którego
hitTest(_:with:)zwraca wynik nie-nil(czyli, on lub któryś z jego potomków zawiera punkt), jest uważany za "trafiony" i ten wynik jest zwracany do góry w hierarchii. - Jeśli żaden widok potomny nie zawiera punktu, a bieżący widok go zawiera i nie ma ustawionego
userInteractionEnabled = false, system uznaje, że dotknięcie miało miejsce w bieżącym widoku, i metoda zwracaself. - Jeśli
userInteractionEnabledbieżącego widoku jestfalse, lubhiddenjesttrue, lubalphajest mniejsze niż 0.01, metoda zwracanil, nawet jeśli punkt jest w granicach.
- Metoda
Proces kontynuuje się w dół hierarchii, aż znajdzie się najniższy widok zawierający punkt dotyku. Ten widok staje się hit-test view lub hit-test result, i to jemu (lub jego najbliższemu odpowiedniemu przodkowi) zostanie delegowana dalsza obsługa zdarzenia (touchesBegan, touchesMoved itd.).
Oto jak nadpisać hitTest lub point(inside:with:) dla niestandardowego zachowania:
import UIKit
class CustomView: UIView {
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// Sprawdzamy, czy punkt znajduje się wewnątrz naszej widoku.
if !self.point(inside: point, with: event) {
return nil
}
// Przechodzimy przez widoki potomne w odwrotnej kolejności.
for subview in subviews.reversed() {
// Konwertujemy punkt do współrzędnych widoku potomnego.
let convertedPoint = subview.convert(point, from: self)
// Rekurencyjnie wywołujemy hitTest na widoku potomnym.
if let hitTestView = subview.hitTest(convertedPoint, with: event) {
// Jeśli widok potomny lub jego potomkowie zawierają punkt, zwracamy ten wynik.
return hitTestView
}
}
// Jeśli żaden widok potomny nie obsłużył dotknięcia,
// a ten widok jest interaktywny i zawiera punkt, zwracamy self.
return self
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
// Podstawowa implementacja sprawdza, czy punkt jest w granicach i czy widok jest interaktywny.
// return super.point(inside: point, with: event)
// Można nadpisać dla niestandardowych kształtów lub marginesów, np. powiększyć obszar dotyku.
let extendedBounds = bounds.insetBy(dx: -20, dy: -20) // Powiększamy obszar o 20pts z każdej strony
return extendedBounds.contains(point)
}
}
Metoda point(inside:with:) jest wywoływana z hitTest(_:with:) w celu szybkiego sprawdzenia, czy punkt znajduje się w obszarze widoku, zanim zostaną sprawdzone widoki potomne.
Ostatecznie znajduje się najniższy widok w hierarchii, który zawiera punkt dotyku i ma ustawione userInteractionEnabled na true, hidden na false i alpha większe niż 0.01.