用副作用重新组织委托方法的方法

时间:2013-10-03 22:16:45

标签: reactive-cocoa

在某些情况下,只想尝试将ReactiveCocoa方法包围起来。

我有一种情况,即分段控制器交换子视图控制器。我需要在这里完成一些事情:

  1. 当移动到父控制器时,我必须更新contentInset的{​​{1}},因为iOS7不会使用自定义容器视图处理它
  2. 启动搜索时,我需要淡化导航栏,并使用动画更新tableView
  3. 搜索结束时,我需要淡入contentInset并重置动画中的navigationBar
  4. 以下是以命令式方式实现此目的的当前代码:

    contentInset

    可以重构一些插入的东西,但要保持这个练习。

    要发布我的'非常小的想法,我正在做什么'的方法作为下面的答案。

    部分答案

    好的,我正试图将信息流提取到相关信号中。

    基本上我需要知道:

    1. 我目前正在搜索
    2. 本案例- (void)didMoveToParentViewController:(UIViewController *)parent { [super didMoveToParentViewController:parent]; if (parent) { CGFloat top = parent.topLayoutGuide.length; CGFloat bottom = parent.bottomLayoutGuide.length; if (self.tableView.contentInset.top != top) { UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0); self.tableView.contentInset = newInsets; self.tableView.scrollIndicatorInsets = newInsets; } } } - (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar { [UIView animateWithDuration:.25 animations:^{ self.navigationController.navigationBar.alpha=0; self.tableView.contentInset = UIEdgeInsetsMake([UIApplication sharedApplication].statusBarFrame.size.height, 0, 0, 0); }]; return YES; } - (BOOL)searchBarShouldEndEditing:(UISearchBar *)searchBar { [UIView animateWithDuration:.25 animations:^{ self.navigationController.navigationBar.alpha=1; CGFloat top = self.parentViewController.topLayoutGuide.length; CGFloat bottom = self.parentViewController.bottomLayoutGuide.length; if (self.tableView.contentInset.top != top) { UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0); self.tableView.contentInset = newInsets; self.tableView.scrollIndicatorInsets = newInsets; } }]; return YES; } (顶部)的当前值
    3. 所以我的方法是

      1. 创建一个RACS主题,以确定我当前是否正在搜索contentInset
      2. self.currentlySearchingSignal的{​​{1}}值转换为信号
      3. 调用top
      4. tableView.contentInsetsendNext:@(YES)(当它返回YES时)
      5. 调用currentlySearchingSignal
      6. searchBarShouldBeginEditingsendNext:@(NO)(当它返回YES时)
      7. ...
      8. 好的,我被卡住了。我知道我需要以某种方式组合/订阅这些,但试图以非状态的方式来考虑它。

        1. 当添加到父VC并且我的currentlySearchingSignal尚未正确设置(searchBarShouldEndEditing)时,我需要在没有动画的情况下进行设置。
        2. 搜索时我的contentInset.top未正确设置(状态栏框)我需要执行动画(然后在动画完成之前不再更新)
        3. 当没有搜索时我的topLayoutGuide设置不正确(contentInset.top)我需要执行动画(并且在动画完成之前不再更新)

        4. 试图解决它

          这是我的开始。试图解决#1,但它还没有工作。

          contentInset.top

1 个答案:

答案 0 :(得分:12)

这是我从GitHub问题中复制的答案:

我还没有使用ReactiveCocoaLayout,但我怀疑你可能会发现除了RAC之外,还可以通过使用RCL来改进这些代码。我确定其他人会提供更多相关细节。

我建议的第一件事是阅读-rac_signalForSelector:。它对于委托回调和RAC信号之间的桥接非常有价值。

例如,您可以获得代表所需回调的信号:

RACSignal *movedToParentController = [[self
    rac_signalForSelector:@selector(didMoveToParentViewController:)]
    filter:^(RACTuple *arguments) {
        return arguments.first != nil; // Ignores when parent is `nil`
    }];

RACSignal *beginSearch = [self rac_signalForSelector:@selector(searchBarShouldBeginEditing:)];
RACSignal *endSearch = [self rac_signalForSelector:@selector(searchBarShouldEndEditing:)];

现在,让我们假设你有一个更新视图的方法:

- (void)updateViewInsets:(UIEdgeInsets)insets navigationBarAlpha:(CGFloat)alpha animated:(BOOL)animated {
    void (^updates)(void) = ^{
        if (self.tableView.contentInset.top != insets.top) {
            self.tableView.contentInset = insets;
            self.tableView.scrollIndicatorInsets = insets;
        }
        self.navigationController.navigationBar.alpha = alpha;
    };

    animated ? [UIView animateWithDuration:0.25 animations:updates] : updates();
}

现在,您可以使用start将一些内容组合在一起。

首先,因为-searchBarShouldBeginEditing:是最短的:

[beginSearch subscribeNext:^(id _) {
    UIEdgeInsets insets = UIEdgeInsetsMake(UIApplication.sharedApplication.statusBarFrame.size.height, 0, 0, 0);
    [self updateViewInsets:insets navigationBarAlpha:0 animated:YES];
}];

现在,对于更复杂的一块。该信号组合以+merge两个信号开始,即-didMoveToParentViewController:的信号和searchBarShouldEndEditing:的信号。这些信号中的每一个都映射到适当的父视图控制器,并与指示是否执行动画的布尔值配对。这对值打包成RACTuple

接下来,使用-reduceEach:(UIViewController *, BOOL)元组将映射到(UIEdgeInsets, BOOL)元组。此映射计算来自父视图控制器的边缘插入,但不会更改animated标记。

最后,订阅了这个信号组合,其中调用了辅助方法。

[[[RACSignal
    merge:@[
        [movedToParentController reduceEach:^(UIViewController *parent) {
            return RACTuplePack(parent, @NO); // Unanimated
        }],
        [endSearch reduceEach:^(id _) {
            return RACTuplePack(self.parentViewController, @YES); // Animated
        }]
    ]]
    reduceEach:^(UIViewController *parent, NSNumber *animated) {
        CGFloat top = parent.topLayoutGuide.length;
        CGFloat bottom = parent.bottomLayoutGuide.length;
        UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
        return RACTuplePack(([NSValue valueWithUIEdgeInsets:newInsets]), animated);
    }]
    subscribeNext:^(RACTuple *tuple) {
        RACTupleUnpack(NSValue *insets, NSNumber *animated) = tuple;
        [self updateViewInsets:insets.UIEdgeInsetsValue navigationBarAlpha:1 animated:animated.boolValue];
    }];

您可以通过RAC找到经常采用的替代方法。实验越多,你发现什么有效,什么不起作用,细微差别等等。

PS。适当的@weakify / @strongify留作练习。

追问

另一种方法是使用-rac_liftSelector:。以下是它如何用于您提供的代码。它与上面的代码非常相似,除了你将animated标志提取到它自己的信号中,而不是将它嵌套到计算插入的信号中。

RACSignal *insets = [RACSignal
    merge:@[
        [movedToParentController reduceEach:^(UIViewController *parent) {
            return parent;
        }],
        [endSearch reduceEach:^(id _) {
            return self.parentViewController;
        }]
    ]]
    map:^(UIViewController *parent) {
        CGFloat top = parent.topLayoutGuide.length;
        CGFloat bottom = parent.bottomLayoutGuide.length;
        UIEdgeInsets newInsets = UIEdgeInsetsMake(top, 0, bottom, 0);
        return [NSValue valueWithUIEdgeInsets:newInsets];
    }];

RACSignal *animated = [RACSignal merge:@[
    [movedToParentController mapReplace:@NO],
    [endSearch mapReplace:@YES],
];

RACSignal *alpha = [RACSignal return:@1];

[self rac_liftSelector:@selector(updateViewInsets:navigationBarAlpha:animated:) withSignals:insets, alpha, animated, nil];

国际海事组织,这两种做法都不是明显胜过对方。但是,指南建议avoiding explicit subscription