web-dev-qa-db-ja.com

rootViewControllerをアニメーションでスワップする

RootViewControllersをアニメーションで交換するときに少し問題があります。これが私が使用しているコードです:

[UIView transitionWithView:self.window duration:0.8 options:UIViewAnimationOptionTransitionFlipFromRight animations:^{
        self.window.rootViewController = self.navigationController;
    } completion:nil];

アニメーションの直前に画面が真っ黒になり、アニメーションが発生することを除いて、それは一種の作品です。オリジナルのrootViewControllerがアニメーションの直前に削除されているようです。何か案は?

33
edc1591

transitionWithViewは、指定されたコンテナービューのサブビューをアニメーション化することを目的としています。ルートビューコントローラの変更をアニメーション化することはそれほど簡単ではありません。私は副作用なしでそれをしようとする長い時間を費やしました。見る:

ナビゲーションコントローラースタック、サブビュー、モーダルコントローラーを使用せずにビューコントローラーの変化をアニメーション化しますか?

編集:参照された回答からの抜粋を追加

[UIView transitionFromView:self.window.rootViewController.view
                    toView:viewController.view
                  duration:0.65f
                   options:transition
                completion:^(BOOL finished){
                    self.window.rootViewController = viewController;
                }];
49
XJones

私はこれがかなり古い質問であることを認識していますが、受け入れられた回答も代替案も良い解決策を提供しませんでした(アニメーションが終了すると、新しいルートビューコントローラーのナビゲーションバーが白から背景色に点滅し、非常に不快です) 。

これを修正するには、最初のビューコントローラーの最後の画面のスナップショットを作成し、2番目(宛先)のビューコントローラーの上にオーバーレイし、必要に応じてアニメーション化しますafter 2番目のビューコントローラーをルートとして設定します。

UIView *overlayView = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO];
[self.destinationViewController.view addSubview:overlayView];
self.window.rootViewController = self.destinationViewController;

[UIView animateWithDuration:0.4f delay:0.0f options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
    overlayView.alpha = 0;
} completion:^(BOOL finished) {
    [overlayView removeFromSuperview];
}];

編集: Swift 3バージョンのコード:

let overlayView = UIScreen.main.snapshotView(afterScreenUpdates: false)
destinationViewController.view.addSubview(overlayView)
window.rootViewController = destinationViewController

UIView.animate(withDuration: 0.4, delay: 0, options: .transitionCrossDissolve, animations: {
    overlayView.alpha = 0
}, completion: { finished in
    overlayView.removeFromSuperview()
})
43

私は transitionWithView:duration:options:animations:completion: を見つけて、より信頼できる結果を生成しました。

[UIView transitionWithView:window
                  duration:0.3
                   options:UIViewAnimationOptionTransitionFlipFromLeft
                animations:^{
                    [fromView removeFromSuperview];
                    [window addSubview:toView];
                    window.rootViewController = toViewController;
                }
                completion:NULL];

ルートビューコントローラーを設定するための完了ブロックまでそのままにしておくと、view(Will/Did)Appearなどのメソッドの実行中に

self.view.window.rootViewController

以前のビューコントローラに設定されたままになります。これらのメソッドの実行中に、私が行ったようにrootViewControllerへの参照を他のコードに渡す必要がない限り、これはほとんどの状況で問題になることはありません。

5
nacross

(この回答を書いている時点で)現在受け入れられている回答は、アニメーション中にナビゲーションバーなどのUI要素を正しく配置しないか、他のlayoutグリッチにつながる可能性があるため、理想的ではありません。 Marko Nikolovskiの回答 。ただし、アニメーションの目的で、現在のビューコントローラーのビューの snapshot を次のビューコントローラーのビューの上にプッシュすることは、どのような種類のビューコントローラーにも使用できないため、理想的ではありません。スナップショットは単なるstatic imageなので、次のView Controllerへの移行中にアニメーションが発生しています。

ccloggの基本的な考え方 を取るこれは、アルファ値を気にしない2つのビューコントローラ間のスムーズな遷移のための私の実装です。

/// Replaces the window's current `rootViewController` with the `newViewController` 
/// passed as a parameter. If `animated`, the window animates the replacement 
/// with a cross dissolve transition.
///
/// - Parameters:
///   - newViewController: The view controller that replaces the current view controller.
///   - animated: Specifies if the transition between the two view controllers is animated.
///   - duration: The transition's duration. (Ignored if `animated == false`)
private func swapCurrentViewController(with newViewController: UIViewController, 
                                       animated: Bool = true, 
                                       duration: TimeInterval = 1) {

    // Get a reference to the window's current `rootViewController` (the "old" one)
    let oldViewController = window?.rootViewController

    // Replace the window's `rootViewController` with the new one
    window?.rootViewController = newViewController

    if animated, let oldView = oldViewController?.view {

        // Add the old view controller's view on top of the new `rootViewController`
        newViewController.view.addSubview(oldView)

        // Remove the old view controller's view in an animated fashion
        UIView.transition(with: window!,
                          duration: duration,
                          options: .transitionCrossDissolve,
                          animations: { oldView.removeFromSuperview() },
                          completion: nil)
    }
}

ウィンドウのrootViewController before を開始してトランジションまたはアニメーションを開始することが重要です。これは、新しいView Controllerがコンテキスト(正しいレイアウトマージンなど)を知るための方法です。


????これは、何らかの理由で本当にウィンドウのrootViewControllerを置き換える必要がある場合に使用する方法です。ただし、私は、アーキテクチャの観点から、他の2つのビューコントローラ間の遷移の処理を担当するビューコントローラを作成し、それをウィンドウのrootViewControllerそしてnever change itthis answer でも同様の質問によく説明されています。

2
Mischa

私にとってうまくいった解決策は、Marko Nikolovskiの答えを少し変更したものでした。既存のルートビューコントローラーにはアニメーション化されたスピナーがあり、アニメーションがフリーズしたため、スナップショットが奇妙に見えました。最終的に、私は(現在のルートビューコントローラー内で)以下を実行できました。

NextRootViewController *nextRootVC = [self.storyboard instantiateViewControllerWithIdentifier:@"NextRootViewController"];

self.view.window.rootViewController = nextRootVC;

[nextRootVC addSubview:self.view];

[UIView animateWithDuration:0.4 delay:0.2 options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
    self.view.alpha = 0;
} completion:^(BOOL finished) {
    [self.view removeFromSuperview];
}];
2
cclogg