[英]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: 我的问题分为两个部分:
RACSubjects
and it seems janky. 我有很多RACSubjects
,看起来很简陋。 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.