2013-05-01 11 views
5

Buduję aplikację za pomocą ReactiveCocoa. Widok z góry to menu, które można przeciągnąć, a następnie przesunąć do góry. Muszę użyć dwóch różnych urządzeń do rozpoznawania gestów - jednego do ściągania i jednego do cofania. Tylko jeden może być włączony na raz - i jest mój problem. Stan.Jak używać ReactiveCocoa z Gesture Recognizers

Używam rozszerzenia BlocksKit do skonfigurowania narzędzia do rozpoznawania gestów.

self.panHeaderDownGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithHandler:^(UIGestureRecognizer *sender, UIGestureRecognizerState state, CGPoint location) { 
    UIPanGestureRecognizer *recognizer = (UIPanGestureRecognizer *)sender; 

    CGPoint translation = [recognizer translationInView:self.view]; 

    if (state == UIGestureRecognizerStateChanged) 
    { 
     [self.downwardHeaderPanSubject sendNext:@(translation.y)]; 
    } 
    else if (state == UIGestureRecognizerStateEnded) 
    { 
     // Determine the direction the finger is moving and ensure if it was moving down, that it exceeds the minimum threshold for opening the menu. 
     BOOL movingDown = ([recognizer velocityInView:self.view].y > 0 && translation.y > kMoveDownThreshold); 

     // Animate the change 
     [UIView animateWithDuration:0.25f animations:^{ 
      if (movingDown) 
      { 
       [self.downwardHeaderPanSubject sendNext:@(kMaximumHeaderTranslationThreshold)]; 
      } 
      else 
      { 
       [self.downwardHeaderPanSubject sendNext:@(0)]; 
      } 
     } completion:^(BOOL finished) { 
      [self.menuFinishedTransitionSubject sendNext:@(movingDown)]; 
     }]; 
    } 
}]; 

W mojej metody initWithNibName:bundle:, jestem konfigurowania następujące RACSubject s.

self.headerMovementSubject = [RACSubject subject]; 
[self.headerMovementSubject subscribeNext:^(id x) { 
    @strongify(self); 

    // This is the ratio of the movement. 0 is closed and 1 is open. 
    // Values less than zero are treated as zero. 
    // Values greater than one are valid and will be extrapolated beyond the fully open menu. 
    CGFloat ratio = [x floatValue]; 

    CGRect headerFrame = CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), kHeaderHeight + ratio * kMaximumHeaderTranslationThreshold); 

    if (ratio < 0) 
    {    
     headerFrame = CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), kHeaderHeight); 
    } 

    self.headerViewController.view.frame = headerFrame; 
}]; 

// This subject is responsible for receiving translations from a gesture recognizers and turning 
// thos values into ratios. These ratios are fead into other signals. 
self.downwardHeaderPanSubject = [RACSubject subject]; 
[self.downwardHeaderPanSubject subscribeNext:^(NSNumber *translation) { 
    @strongify(self); 
    CGFloat verticalTranslation = [translation floatValue]; 

    CGFloat effectiveRatio = 0.0f; 

    if (verticalTranslation <= 0) 
    { 
     effectiveRatio = 0.0f; 
    } 
    else if (verticalTranslation <= kMaximumHeaderTranslationThreshold) 
    { 
     effectiveRatio = fabsf(verticalTranslation/kMaximumHeaderTranslationThreshold); 
    } 
    else 
    { 
     CGFloat overshoot = verticalTranslation - kMaximumHeaderTranslationThreshold; 
     CGFloat y = 2 * sqrtf(overshoot + 1) - 2; 
     effectiveRatio = 1.0f + (y/kMaximumHeaderTranslationThreshold); 
    } 

    [self.headerMovementSubject sendNext:@(effectiveRatio)]; 
}]; 

// This subject is responsible for mapping this value to other signals and state (ugh). 
self.menuFinishedTransitionSubject = [RACReplaySubject subject]; 
[self.menuFinishedTransitionSubject subscribeNext:^(NSNumber *menuIsOpenNumber) { 
    @strongify(self); 

    BOOL menuIsOpen = menuIsOpenNumber.boolValue; 

    self.panHeaderDownGestureRecognizer.enabled = !menuIsOpen; 
    self.panHeaderUpGestureRecognizer.enabled = menuIsOpen; 
    self.otherViewController.view.userInteractionEnabled = !menuIsOpen; 

    if (menuIsOpen) 
    { 
     [self.headerViewController flashScrollBars]; 
    } 
}]; 

Tu się dużo dzieje. Problem pogłębia fakt, że mam prawie podwojoną liczbę tematów, które tu wymieniłem (także dla rozpoznawania gestów panoramowania), oraz inny zestaw rozpoznań do podobnej interakcji ze stopką . To dużo tematów.

Moje pytanie składa się z dwóch części:

  1. Czy istnieje lepszy sposób, aby utworzyć rodzaj łączenia chcę? Ponownie używam niektórych tematów w moim geście push-up, który wygląda bardzo podobnie. Mam dużo RACSubjects i wydaje się być chytry.
  2. Urządzenie służy zasadniczo do zarządzania stanem urządzeń do rozpoznawania gestów. Próbowałem powiązać ich właściwość enabled bez żadnego szczęścia. Masz tu jakieś rady?

Odpowiedz

5

Skupmy się na jednoznacznych subskrypcjach, ponieważ są to zwykle nisko wiszące owoce do przepisywania imperatywnego kodu.

Po pierwsze, na podstawie wyświetlonego kodu wygląda na to, że headerMovementSubject jest podawane tylko wartości od downwardHeaderPanSubject (i nigdzie indziej). To łatwy kandydata na piśmie jako transformacja Zamiast:

RACSignal *headerFrameSignal = [[self.downwardHeaderPanSubject 
    map:^(NSNumber *translation) { 
     CGFloat verticalTranslation = [translation floatValue]; 
     CGFloat effectiveRatio = 0.0f; 

     // Calculate effectiveRatio. 

     return @(effectiveRatio); 
    }] 
    map:^(NSNumber *effectiveRatio) { 
     // Calculate headerFrame. 

     return @(headerFrame); 
    }]; 

Następnie, zamiast manipulować self.headerViewController.view.frame jako efekt uboczny, możemy użyć wiążąca:

RAC(self.headerViewController.view.frame) = headerFrameSignal; 

Możemy robić podobne rzeczy z logicznych w menuFinishedTransitionSubject:

RAC(self.panHeaderDownGestureRecognizer.enabled) = [self.menuFinishedTransitionSubject not]; 
RAC(self.panHeaderUpGestureRecognizer.enabled) = self.menuFinishedTransitionSubject; 
RAC(self.otherViewController.view.userInteractionEnabled) = [self.menuFinishedTransitionSubject not]; 

Niestety -flashScrollBars nadal musi być powoływane jako efekt uboczny, ale możemy w LEA st podnieść filtrowanie z bloku:

[[self.menuFinishedTransitionSubject 
    filter:^(NSNumber *menuIsOpen) { 
     return menuIsOpen.boolValue; 
    }] 
    subscribeNext:^(id _) { 
     @strongify(self); 

     [self.headerViewController flashScrollBars]; 
    }]; 

Jeśli chcesz uzyskać naprawdę fantazyjne, dużo logiki gest Recognizer może być reprezentowany z przekształceń strumienia zamiast, a animacja może być realizowane z ReactiveCocoaLayout, ale to przerobić własne.

+0

Jeśli 'headerMovementSubject' był podawany z rozpoznawczego gestu ruchu (i odpowiadających sygnałów), jak by to działało? Czy nadal potrzebowałbym jawnej właściwości przedmiotu? –

+0

Przedmioty są często zapachem kodu, ponieważ są zmiennymi w zasadzie zmiennymi. Zamiast tego skupiłbym się na łączeniu podmiotów rozpoznających gesty przesuwania i przesunięcia w górę (np. Za pomocą '+ CombineLatest:') w coś, co może być wykorzystane w powyższych transformacjach. –

+0

Gotcha.Myślę, że widzę, jak to by działało. Dzięki! –