【问题标题】:How to use ReactiveCocoa with Gesture Recognizers如何将 ReactiveCocoa 与手势识别器一起使用
【发布时间】:2013-05-01 14:50:19
【问题描述】:

我正在使用 ReactiveCocoa 构建一个应用程序。顶视图是一个可以下拉然后向上推的菜单。我必须使用两种不同的手势识别器——一种用于下拉,一种用于向上推。一次只能启用一个——这是我的问题。状态。

我正在使用 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)];
        }];
    }
}];

在我的initWithNibName:bundle: 方法中,我设置了以下RACSubjects。

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

这里发生了很多事情。由于我的主题数量几乎是此处列出的主题数量的两倍(用于平移手势识别器的主题),加上另一组用于与页脚的类似交互。这是很多科目。

我的问题分为两部分:

  1. 有没有更好的方法来设置我想要的那种链接?我也在我的俯卧撑手势中重新使用了一些主题,看起来非常相似。我有很多 RACSubjects,看起来很笨拙。
  2. menuFinishedTransitionSubject 主要用于管理手势识别器的状态。我尝试绑定他们的enabled 属性,但没有任何运气。这里有什么建议吗?

【问题讨论】:

    标签: ios objective-c reactive-programming reactive-cocoa


    【解决方案1】:

    让我们关注显式订阅,因为这些通常是重写命令式代码的唾手可得的成果。

    首先,根据显示的代码,看起来headerMovementSubject 只是来自downwardHeaderPanSubject 的值(并且没有其他地方)。这是一个很容易写成转换的候选者:

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

    那么,我们可以使用绑定来代替操纵self.headerViewController.view.frame作为副作用:

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

    我们可以用menuFinishedTransitionSubject中的布尔值做类似的事情:

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

    不幸的是,-flashScrollBars 仍然需要作为副作用被调用,但我们至少可以将过滤从块中移除:

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

    如果你想变得更花哨,很多手势识别器逻辑可以用流转换来表示,动画可以用ReactiveCocoaLayout 来实现,但那是它自己的重写。

    【讨论】:

    • 如果headerMovementSubject 是从向上移动的手势识别器(和相应的信号)提供的,那将如何工作?我还需要明确的主题属性吗?
    • 主题通常是代码异味,因为它们基本上是可变变量。相反,我会专注于将下移和上移手势识别器主题(例如,使用+combineLatest:)组合成可以输入上述转换的东西。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多