"来自View Controller"使用UIViewControllerContextTransitioning消失

时间:2014-06-21 05:48:55

标签: ios iphone objective-c uiview transition

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

我正在使用UIViewControllerContextTransitioning进行自定义转换。

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

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

但是我无法看到第一个视图控制器,我只能看到第二视图控制器下方的黑屏。

这是代码。

-(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];被调用时,第一个视图控制器突然消失,黑屏显示在第二个视图控制器下方。

有人有想法吗? 感谢。

17 个答案:

答案 0 :(得分:94)

我在这里遇到了同样的问题 - 看起来像是iOS 8中的一个错误。我filed a radar

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

Reveal'd

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

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

我已经检查过,关键窗口的rootViewController仍然正确设置,所以没关系。我不确定如果你从一个已经呈现的模态控制器中展示你的控制器会发生什么,所以对于更复杂的情况,你将不得不试验一下。

答案 1 :(得分:72)

我觉得应该更好地解释这背后的推理。

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

在iOS 7中,系统始终返回视图控制器&#39;转换完成后自动完成动画演示(呈现和呈现)到其原始位置的视图。对于iOS 8中的某些演示样式,情况不再发生。

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

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

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

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

当你明确地将它放在动画师的容器视图中时,你可以做几件事正确修复一个消失的呈现视图控制器视图的问题:

  1. 如果不需要为呈现视图控制器的视图设置动画,请使用viewForKey:获取动画视图,而不是直接查看控制器的视图。 viewForKey:可能会返回零或甚至完全不同的观点。

  2. 如果要为呈现视图控制器的视图设置动画,则应考虑使用UIModalPresentationFullScreen样式或继续使用UIModalPresentationCustom并使用{{1}实现自己的UIPresentationController子类} return shouldRemovePresentersView。实际上,除了后者允许您使用自定义表示控制器这一事实之外,此方法的实现是由YESUIModalPresentationFullScreen样式定义的内部表示控制器之间的主要区别。

    < / LI>
  3. 在所有其他罕见情况下,您必须将呈现视图控制器的视图返回到其原始位置,如同其他建议的答案一样。

答案 2 :(得分:68)

在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;

因此,不要调整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返回时,呈现视图控制器的视图为零,这会阻止您进行模拟过渡,使所有呈现视图动画化(例如滑落,或者倒装横向)。我在rdar:// 17961976(http://openradar.appspot.com/radar?id=5210815787433984)为iOS8提交了一个错误。另请参阅http://github.com/bcherry/TransitionBug

上的示例项目

编辑2:感谢graveley的建议,使用UIModalPresentationFullScreen修复了这个问题。也许这不是一个错误。 Apple可能打算让UIModalPresentationCustom仅修改传入模式的视图。如果要修改外发视图,是否需要保证全屏显示新视图?无论如何,您应该使用viewForKey和UIModalPresentationFullScreen。

答案 3 :(得分:24)

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

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

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

答案 4 :(得分:13)

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

总结一下:

  1. 将modalPresentationStyle设置为.Custom
  2. 子类UIPresentationController,覆盖shouldRemovePresentersView(没有)
  3. 覆盖TransitionDelegate类中的presentationControllerForPresentedViewController并返回自定义UIPresentationController
  4. 在自定义转换中

    +1,在解雇动画发生时不要添加到View。

    在这里展示:

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

答案 5 :(得分:8)

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

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

要求您的代理人提供自定义演示控制器,以便在呈现视图控制器时管理视图层次结构。

     

返回值:

     

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

     

讨论:

     

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

     

如果您没有实现此方法,或者您执行此方法   方法返回nil,系统使用默认的表示控制器   宾语。 默认的演示文稿控制器不会添加任何视图或   内容到视图层次结构。

     

可用性适用于iOS 8.0及更高版本。

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

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

还有来自WWDC的示例代码,名为&#34; LookInside:Presentation Controllers Adaptivity和Custom Animator Objects&#34;,您可以从WWDC 2014示例代码页下载。

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

initWithPresentedViewController:presented presentingViewController:presenting

在呈现之前呈现。只需交换它们即可。

答案 6 :(得分:7)

而不是[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 :(得分:7)

以下是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:]方法,以便从另一个视图控制器的解雇完成块中呈现一个新的视图控制器,以便正常工作。

我不知道这会为每个人修复它,但它可以在我的应用中运行。干杯!

答案 8 :(得分:5)

我发现这对Obj-C来说很好:

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

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

答案 9 :(得分:5)

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

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

所以这段代码适用于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];
        }
    }];
}

答案 10 :(得分:3)

如果你设置modalPresentationStyle = UIModalPresentationFullScreen,我发现这个bug(以及更多!)会消失。您当然仍然可以获得自定义过渡动画。

答案 11 :(得分:2)

在遇到这个问题之后,我感到非常困惑,因为我不久前写了几乎相同的东西,但工作正常。来到这里寻找答案,找到看起来非常h​​acky的修复,并且似乎不了解根本原因......它实际上很容易修复。

有些回答提及将modalPresentationStyle更改为.overFullScreen。这是正确的,.overCurrentContext也可以。这是预期的,以及Apple文件的行为。但为什么这对每个人都有用呢?为什么所有hacky代码,以及它与其他东西的组合,以及你不应该做的疯狂的东西?

原来,您需要设置演示文稿样式在视图载入之前。不是之后。在init中执行,或者从之前的控制器执行此操作,或者您喜欢 - 只要它在视图加载之前。

答案 12 :(得分:1)

使用新的UIModalPresentationOverCurrentContext为我修复了它。我在iOS 7上的原始转换只是模糊背后的视图背景模糊。

答案 13 :(得分:1)

我也遇到了这个问题。我想要创建一个半透明背景的自定义过渡,我仍然可以看到我来自的视图控制器,但我只有黑色背景。我发现Mark Aron在这个帖子中的答案帮助了我,但它是用Objective C编写的,所以这里是我已经针对iOS 9和iOS 10测试过的答案的Swift 3版本:

  1. 创建UIPresentationController的子类。将shouldRemovePresentersView覆盖为false,如下所示:

    class ModalPresentationController: UIPresentationController {
    
    override var shouldRemovePresentersView: Bool {
    return false
    }
    
    override func containerViewWillLayoutSubviews() {
    presentedView?.frame = frameOfPresentedViewInContainerView
    }
    }
    
  2. 在您实例化新视图控制器并设置其转移委托的位置,指示您希望它显示自定义模式演示文稿样式,如下所示:

    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)
    
    }
    }
    
  4. 另外需要注意的是,您不应该尝试在presentAnimator类中引用fromView。这将是nil,您将在运行时收到错误。除此之外,如果你实现类似的东西,你将获得自己的动画自定义过渡和半透明背景(如果你做的话)。

答案 14 :(得分:0)

好的,伙计们,我认为我解决了一种情况,即在iOS 13及更高版本中构建应用程序时,“有效的动画师”无法正常工作。

环境 Xcode 11.1,iOS 13.1

问题

我想做的事情很简单:我有一个集合视图,当点击一个单元格时,它将选择一个详细视图。我不想让枯燥的默认样式“模态呈现”,而是让它变得更有趣,所以我为视图控制器转换编写了一个动画器。

我通过将集合VC拖放到详细信息VC中来设置IB中的segue。 segue的样式为“模态呈现”,演示设置为“全屏”。

当显示详细视图时,一切都会按预期进行。但是,当我关闭细节视图并返回到集合视图时,我只能看到动画的细节视图,而集合视图就消失了。我在这里和那里戳戳,有一些发现

1。在从函数“ animateTransition()”调用下一行之后,收集视图继续并显示

transitionContext.completeTransition(true)

2。只要详细视图不能完全覆盖收集视图,当从详细视图回退时,收集视图就不会消失

解决方案

说实话,我对动画过渡的工作原理知之甚少。因此,我只能关注这篇文章,并the other one,尝试每个答案。不幸的是,他们都不适合我。最终,我到达了一个唯一可以调整的地方,就是IB中segue的表示方式(我应该在一开始就要做)。当我将演示文稿设置为“全屏显示”时,奇迹发生了,我的问题也解决了。详细信息视图可以全屏显示,并带有动画;当关闭该视图时,我可以同时看到集合视图作为背景和动画详细视图。

然后在路上再发现一个

3。要引用“ toView”和“ fromView”,以下两种方法都可以工作

间接方式:

transitionContext.viewController(forKey: .to)?.view
transitionContext.viewController(forKey: .from)?.view

直接方式:

transitionContext.view(forKey: .to)
transitionContext.view(forKey: .from)

但是,当我将segue样式切换为“ Over Full Screen”时,直接为“ toView”和“ fromView”返回“ nil”并且只能间接地起作用,another post中也提到了此问题,因此我认为值得在这里发表我的小发现。

希望这对将来的人会有所帮助。

答案 15 :(得分:0)

解雇内容视图控制器时,我遇到了同样的问题。

我的应用程序具有此父视图控制器,以模态方式显示子视图控制器(显示vc)。然后,当点击childVC中的子视图时,它将显示另一个vc(我将其称为内容视图控制器(表示为vc))

我的问题是,在关闭contentVC(现在是呈现的vc)时,它应该转到子VC(现在是呈现的VC),但是一旦我的自定义转换完成,childVC就会突然消失,显示父VC。

我为解决此问题所做的是

  1. 将parentVC呈现的childVC的site.tt从默认的.modalPresentationStyle更改为.automatic
  2. 然后将contentVC的.fullscreen也更改为.modalPresentationStyle

这解决了问题。但它不会将您的子级VC显示为parentVC上的卡片样式表(使用.fullscreen或自动时),这是iOS 13中的新增功能。

很想知道是否有任何解决方案可以在由父母提出时保留childVC的卡片样式表。

答案 16 :(得分:-2)

将视图控制器添加为另一个视图控制器的子视图。

[self addChildViewController:childViewController];                 

检查并告诉我。