7

只是试图围绕某些情况下的 ReactiveCocoa 方法。

我有一个段控制器换出子视图控制器的情况。我需要在这里完成几件事:

  1. 当移动到父控制器时,我必须更新 的contentInsettableView因为 iOS7 不使用自定义容器视图为我处理它
  2. 启动搜索时,我需要淡化导航栏,并更新contentInset动画
  3. 搜索结束时,我需要淡入navigationBar并重置contentInset动画

这是以命令式样式完成此操作的当前代码:

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

可以对其进行重构以提取一些插入的内容,但在本练习中保持平整。

将在下面发布我的“非常不知道我在做什么”的方法作为答案。

部分回答

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

基本上我需要知道:

  1. 我现在在搜索吗
  2. contentInset在这种情况下,我的(顶部)的当前值

所以我的方法是

  1. 为我当前是否正在搜索创建一个 RACSubject self.currentlySearchingSignal
  2. topmy 的值tableView.contentInset变成一个信号
  3. sendNext:@(YES)何时被调用(以及何时返回 YES currentlySearchingSignalsearchBarShouldBeginEditing
  4. sendNext:@(NO)何时被调用(以及何时返回 YES currentlySearchingSignalsearchBarShouldEndEditing
  5. ...

好吧,我被困住了。我知道我需要以某种方式组合/订阅这些,但试图以非状态方式考虑它。

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

试图解决它

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

- (void)viewDidLoad
{
    [super viewDidLoad];
    @weakify(self);
    self.currentlyInSearchMode = [RACSubject subject];
    self.contentInsetTop = RACObserve(self.tableView, contentInset);
    RACSignal *parentViewControllerSignal = RACObserve(self, parentViewController);
    
    // setup the insets when added to parent and not correctly set yet
    [[[RACSignal combineLatest:@[self.contentInsetTop, parentViewControllerSignal]] filter:^BOOL(RACTuple *value) {
        return !((NSValue *)value.first).UIEdgeInsetsValue.top == ((UIViewController *)value.second).topLayoutGuide.length;
    }]doNext:^(id x) {
        @strongify(self);
        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;
        }
    }];
     
}
4

1 回答 1

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

现在,对于更复杂的部分。这个信号组合从+mergeing 两个信号开始,信号 for-didMoveToParentViewController:和信号 for 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];

IMO,这两种方法都不是另一种方法的明显赢家。然而,指南建议避免显式订阅

于 2013-10-05T19:43:23.287 回答