简体   繁体   English

如何在手势识别器中使用ReactiveCocoa

[英]How to use ReactiveCocoa with Gesture Recognizers

I'm building an application using ReactiveCocoa. 我正在使用ReactiveCocoa构建应用程序。 The top view is a menu which can be pulled down then pushed back up. 顶视图是一个菜单,可以将其下拉然后再向上推。 I have to use two different gesture recognizers – one for pulling down and one for pushing back up. 我必须使用两种不同的手势识别器–一种用于下拉,另一种用于向上推。 Only one can be enabled at a time – and there's my problem. 一次只能启用一个-这是我的问题。 State. 州。

I'm using the BlocksKit extension to set up the gesture recognizer. 我正在使用BlocksKit扩展程序来设置手势识别器。

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)];
        }];
    }
}];

In my initWithNibName:bundle: method, I'm setting up the following RACSubject s. 在我的initWithNibName:bundle:方法中,我正在设置以下RACSubject

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];
    }
}];

There's a lot going on here. 这里有很多事情。 The problem is exacerbated by the fact that I've got nearly double the number of subjects as I've listed here (ones for the pan-up gesture recognizer, too), plus another set of recognizers for similar interaction with the footer. 我在这里列出的主题数几乎增加了一倍(泛指手势识别器也是如此), 另外还有一组与页脚进行类似交互的识别器,使问题更加严重。 That's a lot of subjects. 有很多主题。

My question is in two parts: 我的问题分为两个部分:

  1. Is there a better way to set up the kind of chaining I want? 有没有更好的方法来设置我想要的链接? I'm re-using some of the subjects in my push-up gesture, as well, which looks really similar. 我也以俯卧撑姿势重复使用某些主题,看起来确实很相似。 I've got a lot of RACSubjects and it seems janky. 我有很多RACSubjects ,看起来很简陋。
  2. The menuFinishedTransitionSubject is essentially used for managing the state of the gesture recognizers. menuFinishedTransitionSubject本质上用于管理手势识别器的状态。 I tried binding their enabled property without any luck. 我试图绑定他们的enabled属性没有任何运气。 Any advice here? 有什么建议吗?

Let's focus on the explicit subscriptions, because those are generally the low-hanging fruit for rewriting imperative code. 让我们关注显式订阅,因为这些通常是重写命令性代码的低挂水果。

First of all, based on the code shown, it looks like headerMovementSubject is only fed values from downwardHeaderPanSubject (and nowhere else). 首先,根据显示的代码,它看起来像headerMovementSubject只美联储值downwardHeaderPanSubject (和其他地方)。 That's an easy candidate for writing as a transformation instead: 可以很容易地将其作为转换来编写:

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);
    }];

Then, instead of manipulating self.headerViewController.view.frame as a side effect, we can use a binding: 然后,我们可以使用绑定来代替操纵self.headerViewController.view.frame的副作用。

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

We can do similar things with the booleans in menuFinishedTransitionSubject : 我们可以使用menuFinishedTransitionSubject的布尔值执行类似的操作:

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

Unfortunately, -flashScrollBars still needs to be invoked as a side effect, but we can at least lift the filtering out of the block: 不幸的是, -flashScrollBars仍然需要作为副作用来调用,但是我们至少可以将过滤移出该块:

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

        [self.headerViewController flashScrollBars];
    }];

If you want to get really fancy, a lot of the gesture recognizer logic can be represented with stream transformations instead, and the animation could be implemented with ReactiveCocoaLayout , but that's a rewrite of its own. 如果您真的想花哨的话,可以使用流转换来表示很多手势识别器逻辑,并且可以使用ReactiveCocoaLayout来实现动画,但这只是对它的重写。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM