reactive-cocoa - 用副作用 react 委托(delegate)方法的方法

标签 reactive-cocoa

只是试图围绕某些情况下的 ReactiveCocoa 方法。
我有一个段 Controller 换出 subview Controller 的情况。我需要在这里完成几件事:

  • 当移动到父 Controller 时,我必须更新 contentInsettableView因为 iOS7 没有为我处理自定义容器 View
  • 启动搜索时,我需要淡化导航栏,并更新 contentInset带动画
  • 当搜索结束时,我需要淡化 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;
    }
    
    可以对其进行重构以提取一些插入的内容,但在本练习中保持平整。
    将在下面发布我的“非常不知道我在做什么”的方法作为答案。
    部分回答
    好的,所以我试图将信息流提取到相关信号中。
    基本上我需要知道:
  • 我正在搜索
  • 我的 contentInset 的当前值(顶部)在这种情况下

  • 所以我的方法是
  • 为我当前是否正在搜索 self.currentlySearchingSignal 创建一个 RACSubject .
  • top我的 tableView.contentInset 的值(value)成信号
  • sendNext:@(YES)currentlySearchingSignalsearchBarShouldBeginEditing被调用(以及何时返回 YES)
  • sendNext:@(NO)currentlySearchingSignalsearchBarShouldEndEditing被调用(以及何时返回 YES)
  • ...

  • 好吧,我被困住了。我知道我需要以某种方式组合/订阅这些,但试图以非状态方式考虑它。
  • 当添加到父 VC 并且当我的 contentInset.top尚未正确设置( topLayoutGuide ),我需要在没有动画的情况下设置它。
  • 搜索和我的 contentInset.top 时设置不正确(状态栏框架)我需要执行动画(然后在我的动画完成之前不要再次更新)
  • 当不搜索和我的 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;
            }
        }];
         
    }
    

    最佳答案

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

    我没用过ReactiveCocoaLayout ,但我怀疑您可能会发现除了 RAC 之外,还可以使用 RCL 来改进其中的一些代码。我相信其他人会提供更多详细信息。

    我建议的第一件事是阅读 -rac_signalForSelector: .它对于在委托(delegate)回调和 RAC 信号之间架起桥梁非常有值(value)。

    例如,以下是获取代表所需回调的信号的方式:

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

    现在,假设您有一个更新 View 的方法:
    - (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: 的信号.这些信号中的每一个都映射到适当的父 View Controller ,并与指示是否执行动画的 bool 值配对。这对值被打包成 RACTuple .

    接下来,使用 -reduceEach: , (UIViewController *, BOOL)元组映射到 (UIEdgeInsets, BOOL)元组。此映射从父 View Controller 计算边缘插入,但不会更改 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,这两种方法都不是另一种方法的明显赢家。然而,指南推荐avoiding explicit subscription .

    关于reactive-cocoa - 用副作用 react 委托(delegate)方法的方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19170122/

    相关文章:

    ios - 在不知道 RACTuple 参数数量的情况下减少 RACTuple 的值

    ios - ReactiveCocoa 5, ReactiveSwift 网络子请求处理 & 最佳实践

    ios - RxSwift 网络状态可观察

    ios - ReactiveCocoa `rac_textSignal` map 完成事件

    ios - KVO 不适用于 NSManagedObject 的自定义属性

    ios - 编写 CLLocationManagerDelegate 的最佳 Reactive-Cocoa 方法,它很少获取位置

    iphone - 如何在 ReactiveSwift 中中断 SafeSignal?

    objective-c - 你会如何编写以 "Reactive Cocoa"方式获取集合?

    swift - 保留 Signal 或 SignalProducer?

    swift - 如果信号已经发生,如何观察信号并立即接收到 `next` 事件?