2012-07-14 15 views
20

Chcę, aby zarówno mój UIScrollView, jak i jego widoki, otrzymywały wszystkie zdarzenia dotykowe wewnątrz widoku podrzędnego. Każdy może odpowiedzieć na swój sposób.Zezwalaj UIScrollView i jego subviews na reakcję na dotyk

Ewentualnie, jeśli gesty tap zostały przekazane do subviews, wszystko byłoby dobrze.

Wiele osób boryka się z tym problemem. Oto kilka z wielu pokrewnych pytania:

How does UIScrollView steal touches from its subviews
How to steal touches from UIScrollView?
How to Cancel Scrolling in UIScrollView

Nawiasem mówiąc, jeśli zastępują hitTest: withEvent: w widoku przewijania, widzę akcenty jak długo userInteractionEnabled TAK . Ale to naprawdę nie rozwiązuje mojego problemu, ponieważ:

1) W tym momencie nie wiem, czy to kran, czy nie.
2) Czasami trzeba ustawić userInteractionEnabled na NIE.

EDYCJA: Aby wyjaśnić, tak, chcę traktować zaczepy inaczej niż patelnie. Taps powinny być obsługiwane przez subviews. Patelnie można obsługiwać w sposób przewijany w zwykły sposób.

+4

Ustawienie userInteractionEnabled NO i oczekując dane interakcji użytkownika wydaje się wszechświata rozbicia paradoksu. – borrrden

+0

Czy chcesz rozróżnić dotyk od zwoju? – shannoga

Odpowiedz

38

pierwszy, zrzeczenie się. Jeśli ustawisz userInteractionEnabled na NO na UIScrollView, żadne wydarzenia dotykowe nie zostaną przekazane do subviews. O ile mi wiadomo, nie ma możliwości obejścia tego z jednym wyjątkiem: przechwytywanie zdarzeń dotyku w superview of UIScrollView, a konkretnie przekazywanie tych zdarzeń do subviews UIScrollView. Szczerze mówiąc, nie wiem, dlaczego chciałbyś to zrobić. Jeśli chcesz wyłączyć określoną funkcję UIScrollView (np. ... no, przewijanie), możesz to zrobić wystarczająco łatwo, nie wyłączając UserInteraction.

Jeśli rozumiem Twoje pytanie, potrzebujesz zdarzeń związanych z dotknięciem do przetworzenia przez UIScrollView i przekazanych do subviews? W każdym razie (niezależnie od gestu), myślę, że to, czego szukasz, to metoda protokołu gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: w protokole UIGestureRecognizerDelegate. W swoich subviewsach, niezależnie od tego, jaki gest rozpoznajesz, ustaw delegata (prawdopodobnie niezależnie od tego, co klasa ustawia na pierwszym miejscu) w geście rozpoznawania gestów. Zastąp powyższą metodę i zwróć YES. Teraz ten gest zostanie rozpoznany razem z innymi rozpoznawaczami, które mogą "skraść" ten gest (w twoim przypadku kran). Korzystając z tej metody, możesz nawet dostroić swój kod, aby wysyłać określone rodzaje gestów do podstron lub wysłać gest tylko w określonych sytuacjach. Daje ci dużo kontroli. Tylko pamiętaj, aby przeczytać o metodę, zwłaszcza ta część:

Metoda ta jest wywoływana, gdy rozpoznanie gestu przez obu gestureRecognizer lub otherGestureRecognizer będzie blokować inny gest rozpoznawania od uznając jego gest. Zwróć uwagę, że zwracanie YES na poziomie gwarantuje równoczesne rozpoznawanie; zwracanie NIE, z drugiej strony, nie gwarantuje równoczesnego rozpoznawania , ponieważ delegat innego gestu użytkownika może zwrócić TAK.

Oczywiście, istnieje zastrzeżenie: Dotyczy to tylko rozpoznawania gestów. Możesz więc nadal mieć problemy, jeśli próbujesz użyć touchesBegan:, touchesEnded itd., Aby przetworzyć dotknięcia. Możesz oczywiście użyć hitTest:, aby wysyłać surowe wydarzenia dotykowe do subskrybentów, ale dlaczego? Dlaczego przetwarzać zdarzenia za pomocą tych metod w UIView, kiedy można dołączyć do widoku UIGestureRecognizer i uzyskać całą tę funkcjonalność za darmo? Jeśli potrzebujesz detali przetworzonych w taki sposób, że żaden standard nie może dostarczyć , podklasę UIGestureRecognizer i przetworzyć tamte dotknięcia. W ten sposób uzyskasz całą funkcjonalność urządzenia UIGestureRecognizer wraz z własnym niestandardowym przetwarzaniem dotyku. Naprawdę uważam, że Apple ma zamiar zastąpić większość (jeśli nie wszystkie) niestandardowego kodu przetwarzania dotykowego, którego deweloperzy używają pod numerem UIView. Pozwala na ponowne użycie kodu i dużo łatwiej jest sobie z nim radzić, gdy łagodzi to, jaki kod przetwarza dane zdarzenie dotykowe.

+1

Awesome, thanks! Jeśli ktoś inny to czyta, możesz uzyskać podobny efekt za pomocą metody UIScrollView touchesShouldCancelInContentView –

+0

"Jeśli chcesz wyłączyć określoną funkcję UIScrollView (np. ... no, przewijanie), możesz to zrobić wystarczająco łatwo bez wyłączania UserInteraction "->" myScrollView.scrollEnabled = false " – RGML

0

Jeśli chcesz odróżnić dotyk od zwoju, możesz sprawdzić, czy dotknięcia zostały przesunięte. Jeśli jest to dotknięcie, wówczas touchHasBeenMoved nie zostanie wywołany, możesz więc założyć, że to jest dotyk.

W tym miejscu można ustawić wartość logiczną, aby wskazać, czy movnent accoured i ustawić tę wartość logiczną jako warunek w innych metod.

jestem na drodze, ale jeśli to jest to, czego potrzebujesz będę w stanie wyjaśnić lepiej później.

3

Nie wiem, czy to może pomóc, ale miałem podobny problem, gdzie chciałem Scrollview do obsługi dwukrotne stukanie ale do przodu jeden kran do subviews. Oto kod używany w CustomScrollView

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { 

    UITouch* touch = [touches anyObject]; 
    // Coordinates 
    CGPoint point = [touch locationInView:[self.subviews objectAtIndex:0]]; 

    // One tap, forward 
    if(touch.tapCount == 1){ 
     // for each subview 
     for(UIView* overlayView in self.subviews){ 
      // Forward to my subclasss only 
      if([overlayView isKindOfClass:[OverlayView class]]){ 
       // translate coordinate 
       CGPoint newPoint = [touch locationInView:overlayView]; 
       //NSLog(@"%@",NSStringFromCGPoint(newPoint)); 

       BOOL isInside = [overlayView pointInside:newPoint withEvent:event]; 
       //if subview is hit 
       if(isInside){ 
        Forwarding 
        [overlayView touchesEnded:touches withEvent:event]; 
        break; 
       } 
      } 
     } 

    } 
    // double tap : handle zoom 
    else if(touch.tapCount == 2){ 

     if(self.zoomScale == self.maximumZoomScale){ 
      [self setZoomScale:[self minimumZoomScale] animated:YES]; 
     } else { 
      CGRect zoomRect = [self zoomRectForScrollView:self withScale:self.maximumZoomScale withCenter:point];    

      [self zoomToRect:zoomRect animated:YES]; 
     } 

     [self setNeedsDisplay]; 

    } 
} 

oczywiście skuteczne Kod powinien być zmieniony, ale w tym momencie powinieneś mieć wszystkie informacje, których potrzebujesz, aby zdecydować, czy masz do przekazania zdarzenia. Może być konieczne zaimplementowanie tego w innej metodzie, jak touchesMoved: withEvent :.

Mam nadzieję, że to pomoże.

0

hackish sposób, aby osiągnąć swój cel - nie 100% dokładny - ma podklasy UIWindow i zastąpić - (void) sendEvent: (*) UIEvent zdarzenie;

Szybki przykład:

w SecondResponderWindow.h nagłówku

//SecondResponderWindow.h 

@protocol SecondResponderWindowDelegate 
- (void)userTouchBegan:(id)tapPoint onView:(UIView*)aView; 
- (void)userTouchMoved:(id)tapPoint onView:(UIView*)aView; 
- (void)userTouchEnded:(id)tapPoint onView:(UIView*)aView; 
@end 

@interface SecondResponderWindow : UIWindow 
@property (nonatomic, retain) UIView *viewToObserve; 
@property (nonatomic, assign) id <SecondResponderWindowDelegate> controllerThatObserves; 
@end 

w SecondResponderWindow.m

//SecondResponderWindow.m 

- (void)forwardTouchBegan:(id)touch onView:(UIView*)aView { 
    [controllerThatObserves userTouchBegan:touch onView:aView]; 
} 
- (void)forwardTouchMoved:(id)touch onView:(UIView*)aView { 
    [controllerThatObserves userTouchMoved:touch onView:aView]; 
} 
- (void)forwardTouchEnded:(id)touch onView:(UIView*)aView { 
    [controllerThatObserves userTouchEnded:touch onView:aView]; 
} 

- (void)sendEvent:(UIEvent *)event { 
    [super sendEvent:event]; 

    if (viewToObserve == nil || controllerThatObserves == nil) return; 

    NSSet *touches = [event allTouches]; 
    UITouch *touch = [touches anyObject]; 
    if ([touch.view isDescendantOfView:viewToObserve] == NO) return; 

    CGPoint tapPoint = [touch locationInView:viewToObserve]; 
    NSValue *pointValue = [NSValue valueWithCGPoint:tapPoint]; 

    if (touch.phase == UITouchPhaseBegan) 
     [self forwardTouchBegan:pointValue onView:touch.view]; 
    else if (touch.phase == UITouchPhaseMoved) 
     [self forwardTouchMoved:pointValue onView:touch.view]; 
    else if (touch.phase == UITouchPhaseEnded) 
     [self forwardTouchEnded:pointValue onView:touch.view]; 
    else if (touch.phase == UITouchPhaseCancelled) 
     [self forwardTouchEnded:pointValue onView:touch.view]; 
} 

To nie jest w 100% zgodny z tym, czego się spodziewaliśmy - bo drugiego odpowiadającego view nie obsługuje zdarzenia dotyku natywnie poprzez -touchDidBegin: i tak, i musi zaimplementować SecondResponderWindowDelegate. Jednak ten hack pozwala na obsługę zdarzeń dotykowych na dodatkowych responderach.

Metoda ta jest inspirowana i rozszerzony z MITHIN Kumara TapDetectingWindow

2

Miałem ten sam problem, ale z przewijaniem, które znajdowało się wewnątrz UIPageViewController, więc musiało być traktowane nieco inaczej.

Zmieniając właściwość cancelsTouchesInView na wartość false dla każdego identyfikatora na UIScrollView, byłem w stanie odbierać dotknięcia przycisków wewnątrz UIPageViewController.

Zrobiłem więc dodając ten kod do viewDidLoad:

guard let recognizers = self.pageViewController.view.subviews[0].gestureRecognizers else { 
    print("No gesture recognizers on scrollview.") 
    return 
} 

for recognizer in recognizers { 
    recognizer.cancelsTouchesInView = false 
} 
Powiązane problemy