ios “从视图控制器”消失使用 UIViewControllerContextTransitioning

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24338700/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 01:02:03  来源:igfitidea点击:

"From View Controller" disappears using UIViewControllerContextTransitioning

iosiphoneobjective-cuiviewtransition

提问by NiravPatel

I got one problem and i have described it below.

我遇到了一个问题,我在下面对其进行了描述。

I am using UIViewControllerContextTransitioningfor custom transitions.

UIViewControllerContextTransitioning用于自定义过渡。

I have 2 view controllers, first view controller and second view controller.

我有 2 个视图控制器,第一个视图控制器和第二个视图控制器。

Now I want to add second view controller on first view controller with an animation. I have achieved it, now the second view controller is transparent, so we can see first view controller below second view controller.

现在我想用动画在第一个视图控制器上添加第二个视图控制器。我已经实现了,现在第二个视图控制器是透明的,所以我们可以在第二个视图控制器下面看到第一个视图控制器。

But I am not able to see first view controller, and I can see only black screen below second view 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];
    }];
}

When [self.transitionContext completeTransition:YES];called, suddenly the first view controller disappears and black screen displays below second view controller.

[self.transitionContext completeTransition:YES];调用时,突然的第一视图控制器消失,下面第二视图控制器黑色屏幕。

Does any one have idea ? Thanks.

有没有人有想法?谢谢。

采纳答案by Ash Furrow

I was having the same problem here – looks like a bug in iOS 8. I've filed a radar.

我在这里遇到了同样的问题——看起来像是 iOS 8 中的一个错误。我已经提交了一个雷达

I used Revealto inspect the view hierarchy after the screen goes black. The key UIWindowis completely empty – no view hierarchy at all!

在屏幕变黑后,我使用Reveal检查视图层次结构。键UIWindow是完全空的——根本没有视图层次结构!

Reveal'd

揭示了

I played around a bit and it looks like there is an easy workaround, for simple cases. You can just re-add the toViewController's view as a subview of the key window's:

我玩了一会儿,看起来有一个简单的解决方法,适用于简单的情况。您可以将toViewController的视图重新添加为关键窗口的子视图:

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

I've checked and the key window's rootViewControlleris still correctly set, so that's fine. I'm not sure what would happen if you presented your controller from within an already presented modal controller, so for more complex cases, you'll have to experiment around.

我已经检查过,关键窗口的rootViewController设置仍然正确,所以没关系。我不确定如果您从已经呈现的模态控制器中呈现您的控制器会发生什么,因此对于更复杂的情况,您必须进行试验。

回答by egdmitry

I feel like the reasoning behind this should be explained better.

我觉得这背后的原因应该更好地解释。

The view disappears because you take out the presenting view controller's view out of its original location (view hierarchy), put it inside the containerView that your animator provides but never returns it back after the animation has finished. So that view controller's view is removed with its superview (containerView) from the window completely.

视图消失是因为您将呈现视图控制器的视图从其原始位置(视图层次结构)中取出,将其放入动画师提供的 containerView 中,但在动画完成后永远不会将其返回。因此,视图控制器的视图与其父视图(containerView)一起从窗口中完全删除。

In iOS 7 the system always returned view controllers' views that are involved in the presentation (presenting and presented) to their original places after the transition has finished animating automatically. That no longer happens for some presentation styles in iOS 8.

在 iOS 7 中,系统总是在过渡自动完成动画后将参与演示(呈现和呈现)的视图控制器的视图返回到它们的原始位置。对于 iOS 8 中的某些演示样式,这种情况不再发生。

The rule is very simple: the animator should only manipulate the presenting view controller's view if that view controller's view is going to be hidden (removed from the view hierarchy) completely by the end of transition. In other words it means that after the initial presentation animation finishes only the presented view controller's view will be visible and not the presenting view controller's view. For example if you set presented view controller's view's opacity to 50% and use UIModalPresentationFullScreen you will not be able to see presenting view controller's view underneath the presented but if you use UIModalPresentationOverFullscreen - you will (UIPresentationController's shouldRemovePresentersViewmethod is responsible for specifying that).

规则非常简单:如果视图控制器的视图将在过渡结束时完全隐藏(从视图层次结构中删除),动画师应该只操作呈现视图控制器的视图。换句话说,这意味着在初始呈现动画完成后,只有呈现的视图控制器的视图是可见的,而不是呈现的视图控制器的视图。例如,如果您将呈现的视图控制器的视图的不透明度设置为 50% 并使用 UIModalPresentationFullScreen,您将无法在所呈现的下方看到呈现视图控制器的视图,但如果您使用 UIModalPresentationOverFullscreen - 您将(UIPresentationController 的shouldRemovePresentersView方法负责指定)。

Why not allow the animator manipulate the presenting view controller's view at all times? First of all, if the presenting view controller's view is going to stay visible after the animation finishes during the whole presentation life cycle there is no need to animate it at all — it just stays where it is. Second, if the ownership for that view controller is transferred to the presentation controller, the presentation controller will most likely not know how to layout that view controller's view when needed for example when the orientation changes, but the original owner of the presenting view controller does.

为什么不允许动画师一直操纵呈现视图控制器的视图?首先,如果在整个演示生命周期中动画完成后演示视图控制器的视图将保持可见,则根本不需要对其进行动画处理——它只是停留在原处。其次,如果该视图控制器的所有权转移到表示控制器,则表示控制器很可能不知道如何在需要时布局该视图控制器的视图,例如当方向改变时,但表示视图控制器的原始所有者会.

In iOS 8 viewForKey:method was introduced to get views that the animator manipulates. First, it helps to follow the rule described above by returning nil whenever the animator should not touch the view. Second, it may return a differentview for the animator to animate. Imagine that you are implementing a presentation similar to form sheet. In this case you would want to add some shadow or decoration around the presented view controller's view. The animator will animate that decoration instead and the presented view controller's view will be a child of the decoration.

在 iOS 8viewForKey:中引入了方法来获取动画师操作的视图。首先,只要动画师不应该触摸视图,就可以通过返回 nil 来帮助遵循上述规则。其次,它可能会返回一个不同的视图供动画师制作动画。想象一下,您正在实现一个类似于表单的演示文稿。在这种情况下,您可能希望在呈现的视图控制器视图周围添加一些阴影或装饰。动画师将为该装饰设置动画,并且呈现的视图控制器的视图将是该装饰的子项。

viewControllerForKey:doesn't go away, it can still be used if a direct access to view controllers is needed but the animator should not make any assumptions about the views it needs to animate.

viewControllerForKey:不会消失,如果需要直接访问视图控制器,它仍然可以使用,但动画师不应对其需要动画的视图做出任何假设。

There are several things you can do to correctlyfix an issue with a disappearing presenting view controller's view when you explicitly place it inside the animator's container view:

当您显式地将其放置在动画师的容器视图中时,您可以采取多种措施来正确解决显示视图控制器视图消失的问题:

  1. If do not need to animate the presenting view controller's view, use viewForKey:to get views to animate instead of reaching out to view controller's views directly. viewForKey:may return nil or even completely different views.

  2. If you want to animate the presenting view controllers's view you should consider using UIModalPresentationFullScreenstyle or continue using UIModalPresentationCustomand implement your own subclass of UIPresentationController with shouldRemovePresentersViewreturning YES. In fact, the implementation of this method is the main difference between internal presentation controllers defined by UIModalPresentationFullScreenand UIModalPresentationCustomstyles apart from the fact that the latter allows you to use custom presentation controllers.

  3. In all other rare cases you will have to return the presenting view controller's view to its original location as other answers suggested.

  1. 如果不需要为呈现视图控制器的视图设置动画,请使用viewForKey:获取视图进行动画处理,而不是直接接触视图控制器的视图。viewForKey:可能返回 nil 甚至完全不同的视图。

  2. 如果您想为呈现视图控制器的视图设置动画,您应该考虑使用UIModalPresentationFullScreenstyle 或继续使用UIModalPresentationCustom并实现您自己的 UIPresentationController 子类并shouldRemovePresentersView返回YES。事实上,这个方法的实现是由UIModalPresentationFullScreenUIModalPresentationCustom样式定义的内部呈现控制器之间的主要区别,除了后者允许您使用自定义呈现控制器。

  3. 在所有其他极少数情况下,您必须按照其他答案的建议将呈现视图控制器的视图返回到其原始位置。

回答by bcherry

In iOS 8, you must manipulate the views returned by viewForKey:instead of the .viewproperty of the view controllers returned by viewControllerForKey:. This isn't particularly clear from the beta documentation, but if you look into the source for UIViewControllerTransitioning.h you'll see this comment above viewControllerForKey::

在 iOS 8 中,您必须操作由 返回的视图,viewForKey:而不是由.view返回的视图控制器的属性viewControllerForKey:。这在 beta 文档中并不是特别清楚,但是如果您查看 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;

So instead of adjusting frames etc of toViewController.view, use the return value of [transitionContext viewForKey:UITransitionContextToViewKey].

因此,不要调整 的帧等,而是toViewController.view使用 的返回值[transitionContext viewForKey:UITransitionContextToViewKey]

If your app needs to support iOS7 and/or Xcode 5, then you can use a simple category method on UIViewController like the following:

如果您的应用程序需要支持 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
}

Then, get your toViewControllerand fromViewControlleras usual, but get the views using [toViewController viewForTransitionContext:transitionContext].

然后,像往常一样获取您的toViewControllerfromViewController,但使用[toViewController viewForTransitionContext:transitionContext].

Edit: There appears to be a bug, where the presenting view controller's view is nil when returned from viewForKey, which prevents you from making modal transitions that animate the presenting view at all (such as sliding off, or flip-horizontal). I filed a bug for iOS8 at rdar://17961976 (http://openradar.appspot.com/radar?id=5210815787433984). Also see the sample project at http://github.com/bcherry/TransitionBug

编辑:似乎有一个错误,当从 返回时viewForKey,呈现视图控制器的视图为零,这会阻止您进行使呈现视图完全动画化的模态转换(例如滑出或水平翻转)。我在 rdar://17961976 ( http://openradar.appspot.com/radar?id=5210815787433984)为 iOS8 提交了一个错误。另请参阅http://github.com/bcherry/TransitionBug 上的示例项目

Edit 2: Thanks to graveley for the suggestion, using UIModalPresentationFullScreen fixes the issue. Perhaps this is not a bug. Apple may intend that UIModalPresentationCustom only modifies the view of the incoming modal. If you want to modify the outgoing view, you need to guarantee full screen presentation of the new view? In any case, you should use viewForKeyand UIModalPresentationFullScreen.

编辑 2:感谢graveley 的建议,使用 UIModalPresentationFullScreen 解决了这个问题。也许这不是一个错误。Apple 可能打算 UIModalPresentationCustom 只修改传入模态的视图。如果要修改传出视图,需要保证新视图的全屏呈现吗?在任何情况下,您都应该使用viewForKeyUIModalPresentationFullScreen。

回答by graveley

Not setting modalPresentationStyleto UIModalPresentationCustom fixed the issue for me.

不设置modalPresentationStyle为 UIModalPresentationCustom 为我解决了这个问题。

In other words, leaving at the default of UIModalPresentationFullScreen instead of specifying UIModalPresentationCustom fixed the disappearing view issue. Note the UIViewControllerTransitioningDelegate protocol still seems to be followed even when leaving this at the default. If i recall correctly, once upon a time UIModalPresentationCustom was a requirement.

换句话说,保留 UIModalPresentationFullScreen 的默认值而不是指定 UIModalPresentationCustom 修复了消失的视图问题。注意 UIViewControllerTransitioningDelegate 协议似乎仍然被遵循,即使将其保留为默认值。如果我没记错的话,曾几何时 UIModalPresentationCustom 是一个要求。

Works so far, have only tried this for non-interactive animations.

到目前为止,仅在非交互式动画中尝试过此操作。

回答by Mark Aron Szulyovszky

I've found this extremely useful answer in a related thread by Lefteris: https://stackoverflow.com/a/27165723/3709173

我在 Lefteris 的相关线程中找到了这个非常有用的答案:https: //stackoverflow.com/a/27165723/3709173

To sum it up:

把它们加起来:

  1. set modalPresentationStyle to .Custom
  2. subclass UIPresentationController, override shouldRemovePresentersView (with NO)
  3. override presentationControllerForPresentedViewController in your TransitionDelegate class and return your custom UIPresentationController
  1. 将 modalPresentationStyle 设置为 .Custom
  2. 子类 UIPresentationController,覆盖 shouldRemovePresentersView(无)
  3. 在 TransitionDelegate 类中覆盖presentationControllerForPresentedViewController 并返回自定义的UIPresentationController

+1 in your custom transition, don't add toView when the dismissal animation is happening.

+1 在您的自定义过渡中,不要在解除动画发生时添加 toView。

Demonstrated here:

在这里演示:

https://www.dropbox.com/s/7rpkyamv9k9j18v/CustomModalTransition.zip?dl=0without any hacks! it's like magic! :)

https://www.dropbox.com/s/7rpkyamv9k9j18v/CustomModalTransition.zip?dl=0没有任何黑客!这就像魔术!:)

回答by Paulo Faria

In iOS 8, you need create a UIPresentationController and implement the method below, in the UIViewControllerTransitioningDelegate.

在 iOS 8 中,您需要创建一个 UIPresentationController 并在 UIViewControllerTransitioningDelegate 中实现以下方法。

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

Asks your delegate for the custom presentation controller to use for managing the view hierarchy when presenting a view controller.

Return Value:

The custom presentation controller for managing the modal presentation.

Discussion:

When you present a view controller using the UIModalPresentationCustom presentation style, the system calls this method and asks for the presentation controller that manages your custom style. If you implement this method, use it to create and return the custom presentation controller object that you want to use to manage the presentation process.

If you do not implement this method, or if your implementation of this method returns nil, the system uses a default presentation controller object. The default presentation controller does not add any views or content to the view hierarchy.

Availability Available in iOS 8.0 and later.

在呈现视图控制器时,向您的委托询问用于管理视图层次结构的自定义呈现控制器。

返回值:

用于管理模式演示的自定义演示控制器。

讨论:

当您使用 UIModalPresentationCustom 呈现样式呈现视图控制器时,系统会调用此方法并请求管理您的自定义样式的呈现控制器。如果您实现此方法,请使用它来创建和返回要用于管理演示过程的自定义演示控制器对象。

如果你没有实现这个方法,或者你的这个方法的实现返回 nil,系统将使用一个默认的表示控制器对象。默认表示控制器不会向视图层次结构添加任何视图或内容。

可用性 在 iOS 8.0 及更高版本中可用。

For more information watch the WWDC 2014 video:

有关更多信息,请观看 WWDC 2014 视频:

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

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

There's also a sample code from the WWDC called "LookInside: Presentation Controllers Adaptivity and Custom Animator Objects", which you can download from the WWDC 2014 sample code page.

还有一个来自 WWDC 的示例代码,名为“LookInside:Presentation Controllers Adaptivity and Custom Animator Objects”,您可以从 WWDC 2014 示例代码页下载。

You may need to change the sample code a little bit. The UIPresentationController init method changed to:

您可能需要稍微更改示例代码。UIPresentationController init 方法更改为:

initWithPresentedViewController:presented presentingViewController:presenting

Before it was presenting and then presented. Just swap them and it should work.

在它呈现之前,然后呈现。只需交换它们,它应该可以工作。

回答by vichudson1

Here is an Objective C version of Ash's fix.

这是 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]

I had to swap the order and call the [transitionContext completeTransition:] method after adding the view back in to get presenting a new view controller from the dismissal completion block of another view controller to work right.

我不得不交换顺序并在重新添加视图后调用 [transitionContext completeTransition:] 方法,以便从另一个视图控制器的解除完成块中呈现一个新的视图控制器才能正常工作。

I don't know that this'll fix it for everyone but it works in my app. Cheers!

我不知道这会为每个人修复它,但它适用于我的应用程序。干杯!

回答by CarlosGz

instead of [inView insertSubview:toViewController.view aboveSubview:fromViewController.view]; just add: [inView addSubview:toViewController.view];

而不是 [inView insertSubview:toViewController.view aboveSubview:fromViewController.view]; 只需添加:[inView addSubview:toViewController.view];

if (self.presenting) {

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

} else {
    // your code
}

You can see an example here: linkand it works on iOS 7 and iOS 8

你可以在这里看到一个例子:链接,它适用于 iOS 7 和 iOS 8

回答by Reedy

I found this worked fine for Obj-C:

我发现这适用于 Obj-C:

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

Seems to work fine on both ios7 and ios8.

似乎在 ios7 和 ios8 上都可以正常工作。

回答by Chris

I found that viewForKey:UITransitionContextToViewKeyreturns nil on ios8. So if it's nil, I grab the view from the 'to' view controller.

我发现viewForKey:UITransitionContextToViewKey在 ios8上返回 nil。所以如果它是零,我从“到”视图控制器中获取视图。

However, this seems to result in the 'to' view not being moved from the container to the window when completeTransition:YESis called. So if viewForKey:UITransitionContextToViewKeyreturns nil, I fall over to toVC.view, and keep track of the fact that it returned nil, and after the completion I move it to the container's initial superview (which happens to be the window).

但是,这似乎导致“to”视图在completeTransition:YES被调用时不会从容器移动到窗口。因此,如果viewForKey:UITransitionContextToViewKey返回 nil,我会转向toVC.view,并跟踪它返回 nil 的事实,并在完成后将其移动到容器的初始超级视图(恰好是窗口)。

So this code works on iOS7 as well as iOS8, and shouldwork on iOS9 too even if they fix it or not.

所以这段代码在 iOS7 和 iOS8 上有效,即使他们修复与否,也应该在 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];
        }
    }];
}