7

特定の状況に対する ReactiveCocoa のアプローチに頭を悩ませようとしています。

セグメント コントローラーが子ビュー コントローラーを交換する状況があります。ここでいくつかのことを達成する必要があります。

  1. 親コントローラーに移動したら、iOS7 がカスタム コンテナー ビューでそれを処理しないため、contentInsetのを更新する必要があります。tableView
  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. この場合の my contentInset(top)の現在の値

だから私のアプローチは

  1. 現在検索しているかどうかの RACSubject を作成しますself.currentlySearchingSignal
  2. myのtop値をtableView.contentInsetシグナルに変換する
  3. sendNext:@(YES)currentlySearchingSignalいつ呼び出されるsearchBarShouldBeginEditingか (そしていつ YES が返されるか)
  4. sendNext:@(NO)currentlySearchingSignalいつ呼び出されるsearchBarShouldEndEditingか (そしていつ YES が返されるか)
  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];
}];

次に、より複雑な部分について説明します。この信号合成は、 の信号と の+merge信号の 2 つの信号から始まります。これらの各シグナルは、適切な親ビュー コントローラーにマップされ、アニメーションを実行するかどうかを示すブール値とペアになります。この値のペアは にパックされます。-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];

IMO、どちらのアプローチも明確な勝者ではありません。ただし、ガイドラインでは、明示的なサブスクリプションを避けることを推奨しています。

于 2013-10-05T19:43:23.287 に答える