Obsługa zdarzeń dla iOS - jak hitTest:withEvent: i pointInside:withEvent: są powiązane?
Chociaż większość dokumentów apple jest bardzo dobrze napisana, myślę, że "Event Handling Guide for iOS " jest wyjątkiem. Ciężko mi zrozumieć, co tam opisano.
Dokument mówi:
W hit-testing, okno wywołuje
hitTest:withEvent:
w górnym widoku hierarchii widoku; metoda ta działa rekurencyjnie wywołującpointInside:withEvent:
na każdym widoku w hierarchii widoku, który zwraca YES, przechodząc w dół hierarchii, aż znajdzie subview, w którym / align = "left" / Widok ten staje się widokiem hit-test.
Czy więc jest tak, że tylko hitTest:withEvent:
najwyższego widoku jest wywoływany przez system, który wywołuje pointInside:withEvent:
wszystkich podwidywań, a jeśli zwrot z określonego subview jest tak, to wywołuje pointInside:withEvent:
podklasy tego subview?
6 answers
To wydaje się dość podstawowe pytanie. Ale Zgadzam się z Tobą dokument nie jest tak jasny jak inne dokumenty, więc oto moja odpowiedź.
Implementacja hitTest:withEvent:
W UIResponder wykonuje następujące czynności:
- wywołuje
pointInside:withEvent:
zself
- jeśli Zwrot jest nie,
hitTest:withEvent:
zwracanil
. koniec historii. - jeśli Zwrot jest tak, wysyła
hitTest:withEvent:
Wiadomości do swoich podwidywań. zaczyna się od widoku najwyższego poziomu i przechodzi do innych widoków aż do widoku podrzędnego zwraca a nie -nil
obiekt, lub wszystkie podglądy otrzymują wiadomość. - jeśli subview zwróci obiekt nie -
nil
za pierwszym razem, pierwszyhitTest:withEvent:
zwróci ten obiekt. koniec historii. - jeśli żaden subview nie zwraca obiektu nie-
nil
, pierwszyhitTest:withEvent:
zwracaself
Ten proces powtarza się rekurencyjnie, więc zwykle widok liścia hierarchii widoków jest zwracany w końcu.
Jednakże, możesz zastąpić hitTest:withEvent
, aby zrobić coś inaczej. W wielu przypadkach nadrzędne pointInside:withEvent:
jest prostszy i nadal zapewnia wystarczającą ilość opcji, aby dostosować obsługę zdarzeń w aplikacji.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-02-10 19:07:56
Myślę, że mylisz podklasowanie z hierarchią widoków. To, co mówi doktor, jest następujące. Powiedzmy, że masz taką hierarchię widoków. Według hierarchii nie mówię o hierarchii klas, ale o widokach w hierarchii widoków, w następujący sposób:
+----------------------------+
|A |
|+--------+ +------------+ |
||B | |C | |
|| | |+----------+| |
|+--------+ ||D || |
| |+----------+| |
| +------------+ |
+----------------------------+
Powiedz, że włożysz palec do środka D
. Oto co się stanie:
-
{[2] } jest wywoływany na
A
, najwyższym widoku hierarchii widoków. -
pointInside:withEvent:
jest wywoływana rekurencyjnie w każdym widoku.-
pointInside:withEvent:
jest wywoływanaA
, oraz zwracaYES
-
pointInside:withEvent:
jest wywołane naB
i zwracaNO
-
pointInside:withEvent:
jest wywołane na {[12] } i zwracaYES
-
pointInside:withEvent:
jest wywołane na {[1] } i zwracaYES
-
- w widokach, które zwróciły
YES
, spojrzy w dół hierarchii, aby zobaczyć subview, w którym miało miejsce dotknięcie. W tym przypadku odA
,C
iD
, będzieD
. -
D
będzie Widok hit-test
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-14 22:30:17
Jest to bardzo pomocne w testowaniu trafień w systemie iOS.]}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
return nil;
}
if ([self pointInside:point withEvent:event]) {
for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
CGPoint convertedPoint = [subview convertPoint:point fromView:self];
UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
if (hitTestView) {
return hitTestView;
}
}
return self;
}
return nil;
}
Edytuj Swift 4:
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if self.point(inside: point, with: event) {
return super.hitTest(point, with: event)
}
guard isUserInteractionEnabled, !isHidden, alpha > 0 else {
return nil
}
for subview in subviews.reversed() {
let convertedPoint = subview.convert(point, from: self)
if let hitView = subview.hitTest(convertedPoint, with: event) {
return hitView
}
}
return nil
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-12 14:10:36
Dzięki za odpowiedzi, pomogli mi rozwiązać sytuację z" overlay " widoki.
+----------------------------+
|A +--------+ |
| |B +------------------+ |
| | |C X | |
| | +------------------+ |
| | | |
| +--------+ |
| |
+----------------------------+
Załóżmy X
- dotyk użytkownika. pointInside:withEvent:
on B
zwraca NO
, więc hitTest:withEvent:
zwraca A
. Napisałem kategorię na UIView
, aby poradzić sobie z problemem, gdy musisz otrzymać dotyk na górze najbardziej widoczny widok.
- (UIView *)overlapHitTest:(CGPoint)point withEvent:(UIEvent *)event {
// 1
if (!self.userInteractionEnabled || [self isHidden] || self.alpha == 0)
return nil;
// 2
UIView *hitView = self;
if (![self pointInside:point withEvent:event]) {
if (self.clipsToBounds) return nil;
else hitView = nil;
}
// 3
for (UIView *subview in [self.subviewsreverseObjectEnumerator]) {
CGPoint insideSubview = [self convertPoint:point toView:subview];
UIView *sview = [subview overlapHitTest:insideSubview withEvent:event];
if (sview) return sview;
}
// 4
return hitView;
}
-
Nie należy wysyłać zdarzeń dotykowych dla widoków ukrytych lub przezroczystych, ani widoków z
- Jeśli dotyk jest w środku
self
,self
będą rozpatrywane jako potencjalny wynik. - Sprawdź rekurencyjnie wszystkie podviews dla hit. Jeśli w ogóle, zwróć go.
- Else zwraca self lub nil w zależności od wyniku z kroku 2.
userInteractionEnabled
ustawionym na NO
;
Uwaga, [self.subviewsreverseObjectEnumerator]
potrzebne do śledzenia hierarchii widoku od góry do dołu. I sprawdź clipsToBounds
, aby upewnić się, że nie testuje zamaskowanych podviewów.
Użycie:
- Importuj kategorię w podklasowanym widoku.
- Zastąp {[7] } tym
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return [self overlapHitTest:point withEvent:event];
}
Oficjalny poradnik Apple ' a zawiera kilka dobre ilustracje też.
Mam nadzieję, że to komuś pomoże.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-06-26 20:19:33
To pokazuje jak ten fragment!
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (self.hidden || !self.userInteractionEnabled || self.alpha < 0.01)
{
return nil;
}
if (![self pointInside:point withEvent:event])
{
return nil;
}
__block UIView *hitView = self;
[self.subViews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
CGPoint thePoint = [self convertPoint:point toView:obj];
UIView *theSubHitView = [obj hitTest:thePoint withEvent:event];
if (theSubHitView != nil)
{
hitView = theSubHitView;
*stop = YES;
}
}];
return hitView;
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-06-18 08:00:51
Fragment @ lion działa jak czar. Przeportowałem go do swift 2.1 i użyłem go jako rozszerzenia do UIView. Wrzucam to tutaj na wypadek, gdyby ktoś tego potrzebował.
extension UIView {
func overlapHitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
// 1
if !self.userInteractionEnabled || self.hidden || self.alpha == 0 {
return nil
}
//2
var hitView: UIView? = self
if !self.pointInside(point, withEvent: event) {
if self.clipsToBounds {
return nil
} else {
hitView = nil
}
}
//3
for subview in self.subviews.reverse() {
let insideSubview = self.convertPoint(point, toView: subview)
if let sview = subview.overlapHitTest(insideSubview, withEvent: event) {
return sview
}
}
return hitView
}
}
Aby go użyć, wystarczy zastąpić hitTest: point: withEvent w interfejsie użytkownika w następujący sposób:
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
let uiview = super.hitTest(point, withEvent: event)
print("hittest",uiview)
return overlapHitTest(point, withEvent: event)
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-13 18:19:54