web-dev-qa-db-ja.com

UIViewControllerContextTransitioningを使用して「From View Controller」が消える

私は1つの問題を抱えており、私はそれを以下に説明しました。

カスタム遷移にUIViewControllerContextTransitioningを使用しています。

2つのView Controller、1つ目のView Controller、2つ目のView Controllerがあります。

次に、アニメーション付きの最初のView Controllerにsecondview Controllerを追加します。私はそれを達成し、今ではsecondviewコントローラーtransparentを持っているので、secondviewコントローラーの下に最初のviewコントローラーを見ることができます。

しかし、私は最初のView Controllerを見ることができず、secondview controllerの下に黒い画面しか見ることができません。

これがコードです。

-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
    self.transitionContext = transitionContext;
    if(self.isPresenting){
        [self executePresentationAnimation:transitionContext];
    }
    else{
       [self executeDismissalAnimation:transitionContext];
    }
  }

-(void)executePresentationAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
     UIView* inView = [transitionContext containerView];
     UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

     UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

     CGRect offScreenFrame = inView.frame;
     offScreenFrame.Origin.y = inView.frame.size.height;
     toViewController.view.frame = offScreenFrame;

    toViewController.view.backgroundColor = [UIColor clearColor];
    fromViewController.view.backgroundColor = [UIColor clearColor];
    inView.backgroundColor = [UIColor  clearColor];
    [inView insertSubview:toViewController.view aboveSubview:fromViewController.view];
     // [inView addSubview:toViewController.view];
    CFTimeInterval duration = self.presentationDuration;
    CFTimeInterval halfDuration = duration/2;

    CATransform3D t1 = [self firstTransform];
    CATransform3D t2 = [self secondTransformWithView:fromViewController.view];

    [UIView animateKeyframesWithDuration:halfDuration delay:0.0 options:UIViewKeyframeAnimationOptionCalculationModeLinear animations:^{

    [UIView addKeyframeWithRelativeStartTime:0.0f relativeDuration:0.5f animations:^{
        fromViewController.view.layer.transform = t1;
    }];

    [UIView addKeyframeWithRelativeStartTime:0.5f relativeDuration:0.5f animations:^{
        fromViewController.view.layer.transform = t2;
    }];
    } completion:^(BOOL finished) {
    }];


    [UIView animateWithDuration:duration delay:(halfDuration - (0.3*halfDuration)) usingSpringWithDamping:0.7f initialSpringVelocity:6.0f options:UIViewAnimationOptionCurveEaseIn animations:^{
        toViewController.view.frame = inView.frame;
    } completion:^(BOOL finished) {
        [self.transitionContext completeTransition:YES];
    }];
}

いつ [self.transitionContext completeTransition:YES];が呼び出され、突然最初のView Controllerが消え、2番目のView Controllerの下に黒い画面が表示されます。

誰にもアイデアはありますか?ありがとう。

100
NiravPatel

私はここで同じ問題を抱えていました-iOS 8のバグのように見えます。 レーダーを提出しました

Reveal を使用して、画面が黒くなった後にビュー階層を検査しました。キーUIWindowは完全に空です-ビュー階層はまったくありません!

Reveal'd

私は少し遊んでみましたが、簡単な場合には簡単な回避策があるようです。キーウィンドウのサブビューとしてtoViewControllerのビューを再追加できます。

transitionContext.completeTransition(true)
UIApplication.sharedApplication().keyWindow!.addSubview(toViewController.view)

確認しましたが、キーウィンドウのrootViewControllerはまだ正しく設定されているため、問題ありません。既に提示されているモーダルコントローラー内からコントローラーを提示した場合にどうなるかはわかりません。そのため、より複雑な場合は、実験する必要があります。

94
Ash Furrow

この背後にある理由をもっとよく説明すべきだと思います。

表示するView Controllerのビューを元の場所(ビュー階層)から取り出して、アニメーターが提供するcontainerView内に配置しますが、アニメーションが終了した後に戻さないため、ビューは消えます。そのため、View Controllerのビューは、スーパービュー(containerView)とともにウィンドウから完全に削除されます。

IOS 7では、システムは、トランジションが自動的にアニメーション化を完了した後、常にプレゼンテーションに関係するView Controllerのビュー(プレゼンテーションおよびプレゼンテーション)を元の場所に戻しました。これは、iOS 8の一部のプレゼンテーションスタイルでは発生しなくなりました。

ルールは非常に単純です:アニメータは、View Controllerのビューがtransitionの終了によって完全に非表示になる(View階層から削除される)場合にのみ、表示するView Controllerのビューを操作する必要があります。つまり、最初のプレゼンテーションアニメーションが終了すると、表示されるView Controllerのビューだけが表示され、表示されるView Controllerのビューは表示されないことを意味します。たとえば、表示されたView Controllerのビューの不透明度を50%に設定し、UIModalPresentationFullScreenを使用すると、表示された下にView Controllerのビューを表示することはできませんが、UIModalPresentationOverFullscreenを使用すると、(UIPresentationControllerのshouldRemovePresentersViewメソッドはそれ)。

アニメーターが常に表示中のView Controllerのビューを操作できるようにしないのはなぜですか?まず、プレゼンテーションライフサイクル全体でアニメーションが終了した後、表示するView Controllerのビューが表示されたままになっている場合、アニメーション化する必要はまったくありません。 2番目に、そのView Controllerの所有権がPresentation Controllerに転送される場合、プレゼンテーションコントローラーは、たとえば向きが変わったときに必要なときにそのView Controllerのビューをレイアウトする方法をほとんど知らないでしょうが、提示するView Controllerの元の所有者はそうします。

IOS 8では、アニメーターが操作するビューを取得するためにviewForKey:メソッドが導入されました。まず、アニメーターがビューに触れてはならない場合は常にnilを返すことにより、上記の規則に従うことが役立ちます。第二に、アニメータがアニメートするためのdifferentビューを返す場合があります。フォームシートに似たプレゼンテーションを実装するとします。この場合、表示されるView Controllerのビューの周囲に影または装飾を追加する必要があります。アニメーターは代わりにその装飾をアニメーション化し、表示されたView Controllerのビューは装飾の子になります。

viewControllerForKey:はなくなりません。ViewControllerへの直接アクセスが必要な場合でも引き続き使用できますが、アニメータはアニメートする必要があるビューについて仮定するべきではありません。

できることはいくつかあります 正しく アニメータのコンテナビュー内に明示的に配置すると、表示されるView Controllerのビューが消える問題を修正します。

  1. 表示するView Controllerのビューをアニメーション化する必要がない場合は、View Controllerのビューに直接手を差し伸べるのではなく、viewForKey:を使用してアニメーション化するビューを取得します。 viewForKey:は、nilまたはまったく異なるビューを返す場合があります。

  2. 表示するView Controllerのビューをアニメーション化する場合は、UIModalPresentationFullScreenスタイルの使用を検討するか、UIModalPresentationCustomの使用を続行し、shouldRemovePresentersViewが_YESを返すUIPresentationControllerの独自のサブクラスを実装する必要があります。実際、このメソッドの実装は、カスタムプレゼンテーションコントローラーを使用できるという事実とは別に、UIModalPresentationFullScreenスタイルとUIModalPresentationCustomスタイルで定義される内部プレゼンテーションコントローラーの主な違いです。

  3. 他のすべてのまれなケースでは、他の回答が示唆するように、表示するView Controllerのビューを元の場所に戻す必要があります。

73
egdmitry

IOS 8では、viewForKey:によって返されるView Controllerの.viewプロパティの代わりに、viewControllerForKey:によって返されるビューを操作する必要があります。これはベータ版のドキュメントでは特に明確ではありませんが、UIViewControllerTransitioning.hのソースを見ると、viewControllerForKey:の上にこのコメントが表示されます。

// Currently only two keys are defined by the
// system - UITransitionContextToViewControllerKey, and
// UITransitionContextFromViewControllerKey.
// Animators should not directly manipulate a view controller's views and should
// use viewForKey: to get views instead.
- (UIViewController *)viewControllerForKey:(NSString *)key;

したがって、toViewController.viewのフレームなどを調整する代わりに、[transitionContext viewForKey:UITransitionContextToViewKey]の戻り値を使用します。

アプリがiOS7またはXcode 5、あるいはその両方をサポートする必要がある場合、次のようにUIViewControllerで単純なカテゴリメソッドを使用できます。

- (UIView *)viewForTransitionContext:(id<UIViewControllerContextTransitioning>)transitionContext
{
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
    if ([transitionContext respondsToSelector:@selector(viewForKey:)]) {
        NSString *key = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey] == self ? UITransitionContextFromViewKey : UITransitionContextToViewKey;
        return [transitionContext viewForKey:key];
    } else {
        return self.view;
    }
#else
    return self.view;
#endif
}

次に、通常どおりtoViewControllerfromViewControllerを取得しますが、[toViewController viewForTransitionContext:transitionContext]を使用してビューを取得します。

編集:バグがあるようです。viewForKeyから返されたときに表示するView Controllerのビューがnilであるため、表示するビューをアニメートするモーダルトランジション(スライドオフ、フリップ-水平)。 iOS8のバグをrdar:// 17961976に提出しました( http://openradar.appspot.com/radar?id=5210815787433984 )。 http://github.com/bcherry/TransitionBug のサンプルプロジェクトも参照してください。

Edit 2:提案に対するgraveleyのおかげで、UIModalPresentationFullScreenを使用すると問題が修正されます。おそらくこれはバグではありません。 AppleはUIModalPresentationCustomが着信モーダルのビューのみを変更することを意図している可能性があります。発信ビューを変更する場合、新しいビューの全画面表示を保証する必要がありますか? viewForKeyとUIModalPresentationFullScreenを使用します。

69
bcherry

modalPresentationStyleをUIModalPresentationCustomに設定しないと、問題が修正されました。

つまり、UIModalPresentationCustomを指定する代わりに、UIModalPresentationFullScreenのデフォルトのままにしておくと、表示が消える問題が修正されました。 UIViewControllerTransitioningDelegateプロトコルは、これをデフォルトのままにした場合でも引き続き従うようです。正しく思い出すと、昔々UIModalPresentationCustomが必要でした。

これまでのところ、非対話型アニメーションに対してのみこれを試しています。

24
graveley

Lefterisの関連するスレッドで、この非常に有用な答えを見つけました。 https://stackoverflow.com/a/27165723/370917

まとめると:

  1. modalPresentationStyleを.Customに設定します
  2. サブクラスUIPresentationController、shouldRemovePresentersViewをオーバーライドします(NOを使用)
  3. transitionDelegateクラスのpresentationControllerForPresentedViewControllerをオーバーライドし、カスタムUIPresentationControllerを返します。

カスタムトランジションで+1。解雇アニメーションが発生しているときにtoViewを追加しないでください。

ここに示されています:

https://www.dropbox.com/s/7rpkyamv9k9j18v/CustomModalTransition.zip?dl= ハッキングなし!それは魔法のようなものです! :)

14

IOS 8では、UIViewControllerTransitioningDelegateでUIPresentationControllerを作成し、以下のメソッドを実装する必要があります。

- (UIPresentationController *)presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source;

View Controllerを提示するときにView階層を管理するために使用するカスタムプレゼンテーションコントローラーをデリゲートに要求します。

戻り値:

モーダルプレゼンテーションを管理するためのカスタムプレゼンテーションコントローラー。

討論:

UIModalPresentationCustomプレゼンテーションスタイルを使用してView Controllerを表示すると、システムはこのメソッドを呼び出し、カスタムスタイルを管理するプレゼンテーションコントローラを要求します。このメソッドを実装する場合は、このメソッドを使用して、プレゼンテーションプロセスの管理に使用するカスタムプレゼンテーションコントローラーオブジェクトを作成して返します。

このメソッドを実装しない場合、またはこのメソッドの実装がnilを返す場合、システムはデフォルトのプレゼンテーションコントローラーオブジェクトを使用します。 デフォルトのプレゼンテーションコントローラーは、ビュー階層にビューやコンテンツを追加しません。

可用性iOS 8.0以降で使用可能。

詳細については、WWDC 2014ビデオをご覧ください。

https://developer.Apple.com/videos/wwdc/2014/?include=228

「LookInside:Presentation Controllers Adaptivity and Custom Animator Objects」と呼ばれるWWDCのサンプルコードもあります。これは、WWDC 2014サンプルコードページからダウンロードできます。

サンプルコードを少し変更する必要がある場合があります。 UIPresentationControllerのinitメソッドが次のように変更されました。

initWithPresentedViewController:presented presentingViewController:presenting

それが提示され、その後提示される前。それらを交換するだけで動作するはずです。

8
Paulo Faria

Ashの修正のObjective Cバージョンを以下に示します。

// my attempt at obj-c version of Ash's fix
UIView *theToView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;
[[[UIApplication sharedApplication] keyWindow] addSubview:theToView];
[transitionContext completeTransition:YES]

ビューを追加した後、順序を入れ替えて[transitionContext completeTransition:]メソッドを呼び出して、別のView Controllerの終了完了ブロックから新しいView Controllerを表示し、正しく機能する必要がありました。

これがすべての人のために修正されることはわかりませんが、私のアプリでは機能します。乾杯!

7
vichudson1

代わりに[inView insertSubview:toViewController.view aboveSubview:fromViewController.view];追加するだけです:[inView addSubview:toViewController.view];

if (self.presenting) {

    [transitionContext.containerView addSubview:toViewController.view];
    // your code

} else {
    // your code
}

ここに例を見ることができます: link そして、それはiOS 7とiOS 8で動作します

7
CarlosGz

viewForKey:UITransitionContextToViewKeyがios8でnilを返すことがわかりました。したがって、nilの場合、「to」ビューコントローラーからビューを取得します。

ただし、これにより、completeTransition:YESが呼び出されたときに 'to'ビューがコンテナからウィンドウに移動されないようです。 viewForKey:UITransitionContextToViewKeyがnilを返す場合、toVC.viewに戻り、nilを返したという事実を追跡し、完了後にコンテナの最初のスーパービュー(たまたまウィンドウ)。

そのため、このコードはiOS8だけでなくiOS7でも動作します。また、shouldはiOS9でも動作します。

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    // Get the 'from' and 'to' views/controllers.
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    BOOL hasViewForKey = [transitionContext respondsToSelector:@selector(viewForKey:)]; // viewForKey is iOS8+.
    UIView *fromView = hasViewForKey ?
        [transitionContext viewForKey:UITransitionContextFromViewKey] :
        fromVC.view;
    UIView *toView = hasViewForKey ?
        [transitionContext viewForKey:UITransitionContextToViewKey] :
        toVC.view;

    // iOS8 has a bug where viewForKey:to is nil: http://stackoverflow.com/a/24589312/59198
    // The workaround is: A) get the 'toView' from 'toVC'; B) manually add the 'toView' to the container's
    // superview (eg the root window) after the completeTransition call.
    BOOL toViewNilBug = !toView;
    if (!toView) { // Workaround by getting it from the view.
        toView = toVC.view;
    }
    UIView *container = [transitionContext containerView];
    UIView *containerSuper = container.superview; // Used for the iOS8 bug workaround.

    // Perform the transition.
    toView.frame = container.bounds;
    [container insertSubview:toView belowSubview:fromView];
    [UIView animateWithDuration:kDuration delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
        fromView.frame = CGRectOffset(container.bounds, 0, CGRectGetHeight(container.bounds));
    } completion:^(BOOL finished) {
        [transitionContext completeTransition:YES];

        if (toViewNilBug) {
            [containerSuper addSubview:toView];
        }
    }];
}
5
Chris

私はこれがObj-Cでうまくいくことがわかりました:

    [transitionContext completeTransition:YES];
    if(![[UIApplication sharedApplication].keyWindow.subviews containsObject:toViewController.view]) {
        [[UIApplication sharedApplication].keyWindow addSubview:toViewController.view];
    }

Ios7とios8の両方で正常に動作するようです。

5
Reedy

modalPresentationStyle = UIModalPresentationFullScreenを設定すると、このバグ(およびその他多数)が消えることがわかりました。もちろん、カスタムトランジションアニメーションを取得できます。

3
Chris

この問題に遭遇した後、私は非常に混乱していました。なぜなら、私はほとんど同じ前にうまく機能する何かを書いていたからです。かなりハックに見え、根本的な原因を理解していないように見える修正を見つけるための答えを探しにここに来ました...実際に修正するのは非常に簡単です。

一部の回答では、modalPresentationStyle.overFullScreenに変更することに言及しています。これは正しいです。.overCurrentContextも機能します。これは予想されるものであり、動作はAppleドキュメントです。しかし、なぜこれがすべての人に機能しないのでしょうか?やってる?

結局、プレゼンテーションのスタイルを設定する必要がありますビューの読み込み前。後ではありません。ビューをロードする前であれば、initで実行するか、前のコントローラーから実行します。

2
Jordan Smith

新しいUIModalPresentationOverCurrentContextを使用すると、修正されました。 iOS 7での私の最初の移行は、モーダルの下のビューの背景をぼかすことだけでした。

1
malhal

私もこの問題で立ち往生しました。私は、半透明の背景を持つカスタムトランジションを作成しようとしていました。そこには、元のビューコントローラがまだ表示されていましたが、黒の背景しかありませんでした。このスレッドのマーク・アロンの答えが私を助けたことがわかりましたが、Objective Cで書かれているので、Swift iOS 9とiOS 10でテストしたその答えの3バージョンです:

  1. UIPresentationControllerのサブクラスを作成します。次のようにshouldRemovePresentersViewをfalseにオーバーライドします。

    class ModalPresentationController: UIPresentationController {
    
    override var shouldRemovePresentersView: Bool {
    return false
    }
    
    override func containerViewWillLayoutSubviews() {
    presentedView?.frame = frameOfPresentedViewInContainerView
    }
    }
    
  2. 新しいView Controllerをインスタンス化し、そのトランジションデリゲートを設定する場所で、次のようにカスタムモーダルプレゼンテーションスタイルを表示することを示します。

    let newVC = mainStoryboard.instantiateViewController(withIdentifier: "newVC") as! NewViewController 
    
    newVC.transitioningDelegate = self
    
    newVC.modalPresentationStyle = UIModalPresentationStyle.custom
    
    newVC.modalPresentationCapturesStatusBarAppearance = true //optional
    
    present(newVC, animated: true, completion: nil)
    
  3. UIViewControllerTransitioningDelegateのpresentationControllerメソッドをオーバーライドして、カスタムUIPresentationControllerを返します。私は現在のクラスの拡張として私のものを持っていました:

    extension CurrentViewController: UIViewControllerTransitioningDelegate {
    
    //this is where you implement animationController(forPresented) and animationController(forDismissed) methods
    
    func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
    
    return ModalPresentationController(presentedViewController: presented, presenting: source)
    
    }
    }
    

もう1つ注意すべき点は、presentAnimatorクラスでfromViewを参照しないでください。これはnilになり、実行時にエラーが発生します。それ以外に、物事のようなものを実装する場合、アニメーションと半透明の背景を作成すると、カスタムの遷移が得られます。

1
gwinyai