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ąc pointInside: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?

 130
Author: BlueFish, 2011-02-10

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: z self
  • jeśli Zwrot jest nie, hitTest:withEvent: zwraca nil. 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, pierwszy hitTest:withEvent: zwróci ten obiekt. koniec historii.
  • jeśli żaden subview nie zwraca obiektu nie-nil, pierwszy hitTest:withEvent: zwraca self

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.

 160
Author: MHC,
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:

  1. {[2] } jest wywoływany na A, najwyższym widoku hierarchii widoków.
  2. pointInside:withEvent: jest wywoływana rekurencyjnie w każdym widoku.
    1. pointInside:withEvent: jest wywoływana A, oraz zwraca YES
    2. pointInside:withEvent: jest wywołane na B i zwraca NO
    3. pointInside:withEvent: jest wywołane na {[12] } i zwraca YES
    4. pointInside:withEvent: jest wywołane na {[1] } i zwraca YES
  3. 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 od A, C i D, będzie D.
  4. D będzie Widok hit-test
 282
Author: pgb,
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.]}

Tutaj wpisz opis obrazka

- (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
}
 40
Author: onmyway133,
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 userInteractionEnabled ustawionym na NO;
  1. Jeśli dotyk jest w środku self, self będą rozpatrywane jako potencjalny wynik.
  2. Sprawdź rekurencyjnie wszystkie podviews dla hit. Jeśli w ogóle, zwróć go.
  3. Else zwraca self lub nil w zależności od wyniku z kroku 2.

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:

  1. Importuj kategorię w podklasowanym widoku.
  2. 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.
 21
Author: Lion,
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;
}
 3
Author: hippo,
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)
}
 0
Author: mortadelo,
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