UIViewController - 自定义解除转换的问题

Ben*_*ohn 26 uiviewcontroller uikit uiviewanimationtransition ios

摘要

我有一个内容UIViewController,它使用自定义转换呈现设置UIViewController.演示文稿是presentViewController:animated:completion:.

当我稍后解除设置时dismissViewControllerAnimated:completion:,呈现控制器突然跳回设置控制器演示之前的初始位置.

我在设备上有一个解决方法,但不是模拟器.但是,我想知道我做错了什么而不是黑客入侵让它消失的小屋.我还打算让这个动画互动,我怀疑这个问题会在我这样做时放大.

定制过渡 - 打开引擎盖

期望的效果是呈现控制器在屏幕上向下滑动,并且看到呈现的控制器位于其后面,从其抬起以填充屏幕.在呈现控制器的使用寿命期间,呈现控制器的顶部保持在屏幕上.它停留在屏幕的底部,但位于显示的控制器上方.

您可以想象在汽车(前部控制器)上抬起发动机罩以查看后面的发动机(显示的设置),但发动机罩在底部保持可见,以获得一些背景信息.

我计划对其进行改进,以便呈现控制器真正看起来以3d方式提升视角,但我还没有那么远.

当设置被取消时,原始呈现控制器(发动机罩)应向上滑回屏幕,并且呈现的控制器(设置)稍微下沉(关闭发动机罩).

这是切换屏幕上和屏幕外设置的方法(它只是由UIButton调用).你会注意到呈现视图控制器将自己设置为<UIViewControllerTransitioningDelegate>.

-(void) toggleSettingsViewController
{
  const BOOL settingsAreShowing = [self presentedViewController] != nil;
  if(!settingsAreShowing)
  {
    UIViewController *const settingsController = [[self storyboard] instantiateViewControllerWithIdentifier: @"STSettingsViewController"];
    [settingsController setTransitioningDelegate: self];
    [settingsController setModalPresentationStyle: UIModalPresentationCustom];
    [self presentViewController: settingsController animated: YES completion: nil];
  }
  else
  {
    [self dismissViewControllerAnimated: YES completion: nil];
  }
}
Run Code Online (Sandbox Code Playgroud)

要实现<UIViewControllerAnimatedTransitioning>呈现视图控制器,也只需返回自身<UIViewControllerAnimatedTransitioning>

-(id<UIViewControllerAnimatedTransitioning>) animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
  return self;
}

-(id<UIViewControllerAnimatedTransitioning>) animationControllerForDismissedController:(UIViewController *)dismissed
{
  // Test Point 1.
  return self;
}
Run Code Online (Sandbox Code Playgroud)

最后,呈现视图控制器将收到animateTransition::

-(void) animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
  UIViewController *const fromController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
  UIViewController *const toController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

  const BOOL isUnwinding = [toController presentedViewController] == fromController;
  const BOOL isPresenting = !isUnwinding;

  UIViewController * presentingController = isPresenting ? fromController : toController;
  UIViewController * presentedController = isPresenting ? toController : fromController;

  if(isPresenting)
  {
    // Add the presented controller (settings) to the view hierarchy _behind_ the presenting controller.
    [[transitionContext containerView] insertSubview: [presentedController view] belowSubview: [presentingController view]];

    // Set up the initial position of the presented settings controller. Scale it down so it seems in the distance. Alpha it down so it is dark and shadowed.
    presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);
    presentedController.view.alpha = 0.7;

    [UIView animateWithDuration: [self transitionDuration: transitionContext] animations:^{
      // Lift up the presented controller.
      presentedController.view.transform = CGAffineTransformMakeScale(1.0, 1.0);

      // Brighten the presented controller (out of shadow).
      presentedController.view.alpha = 1;

      // Push the presenting controller down the screen – 3d effect to be added later.
      presentingController.view.layer.transform = CATransform3DMakeTranslation(0,400,0);
     } completion: ^(BOOL finished){
       [transitionContext completeTransition: ![transitionContext transitionWasCancelled]];
     }];
  }
  else
  {
    // Test Point 2.

    // !!!This line should not be needed!!!
    // It resets the presenting controller to where it ought to be anyway.
    presentingController.view.layer.transform = CATransform3DMakeTranslation(0,400,0);

    [UIView animateWithDuration: [self transitionDuration: transitionContext] animations:^{
      // Bring the presenting controller back to its original position.
      presentingController.view.layer.transform = CATransform3DIdentity;

      // Lower the presented controller again and put it back in to shade.
      presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);
      presentedController.view.alpha = 0.4;
    } completion:^(BOOL finished) {
      [transitionContext completeTransition: ![transitionContext transitionWasCancelled]];
    }];
  }
}

-(NSTimeInterval) transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
  return 0.5;
}
Run Code Online (Sandbox Code Playgroud)

问题

在上面的代码中,我已经指出!!!不应该需要这条线!.

发生的事情是在测试点1测试点2之间,呈现视图控制器的屏幕位置被重置为默认的全屏界限.因此,它不是在屏幕的底部准备好再次平滑地重新启动动画,而是突然跳到屏幕上以确定它是否也能平滑地制作动画!

我尝试了各种方法在屏幕上动画显示视图控制器:

  • 我改变了它的视图框架.
  • 我已经改变了它的视图转换.
  • 我已经改变了它的视图层的3D变换.

在所有情况下,在测试点1,当要求转换代表时,呈现控制器按照我的预期设置.但是,在所有情况下,在测试点2处,呈现视图控制器已丢失正确的位置并已被"清除"以具有我想要将其设置为动画的正常全屏位置.

在上面的工作中,我明确地将呈现视图控制器重新定位到它应该在动画开始时的位置!!!这条线不应该被需要!.这似乎适用于具有当前版本iOS 7的设备.但是,在模拟器上,控制器在清除位置可见至少一帧.

我怀疑自己做错了什么,而且我会在解决另一个问题时遇到麻烦.

有什么想法发生了什么?谢谢!

Joh*_*alo 39

使用自定义过渡动画解雇模态呈现的视图控制器的一些潜在问题:

  • 将呈现的("to")视图添加到容器中,然后将呈现的视图放在前面.不要添加呈现视图,因为您可能会将其从当前的超级视图中删除.
  • 在关闭时,UIKit 在调用animateTransition之前将呈现视图的alpha设置为0 .因此,您需要将其设置为1.0或者在当前完成时的任何设置,然后再解除您的解雇动画.
  • 同样地,对于呈现的视图的变换.在关闭时,它会在调用animateTransition之前重置为identity.

鉴于这一切,我认为这应该有效:

-(void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController *fromController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIView *containerView = transitionContext.containerView;

    const BOOL isUnwinding = [toController presentedViewController] == fromController;
    const BOOL isPresenting = !isUnwinding;

    UIViewController *presentingController = isPresenting ? fromController : toController;
    UIViewController *presentedController = isPresenting ? toController : fromController;

    [containerView addSubview:presentingController.view];
    [containerView bringSubviewToFront:presentingController.view];

    if(isPresenting)
    {
        // Set up the initial position of the presented settings controller. Scale it down so it seems in the distance. Alpha it down so it is dark and shadowed.
        presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);
        presentedController.view.alpha = 0.7;

        [UIView animateWithDuration: [self transitionDuration: transitionContext] animations:^{
            // Lift up the presented controller.
            presentedController.view.transform = CGAffineTransformMakeScale(1.0, 1.0);

            // Brighten the presented controller (out of shadow).
            presentedController.view.alpha = 1;

            // Push the presenting controller down the screen – 3d effect to be added later.
            presentingController.view.layer.transform = CATransform3DMakeTranslation(0,400,0);
        } completion: ^(BOOL finished){
            [transitionContext completeTransition: ![transitionContext transitionWasCancelled]];
        }];
    }
    else
    {
        presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);
        presentedController.view.alpha = 0.7;

        [UIView animateWithDuration: [self transitionDuration: transitionContext] animations:^{
            // Bring the presenting controller back to its original position.
            presentingController.view.layer.transform = CATransform3DIdentity;

            // Lower the presented controller again and put it back in to shade.
            presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);
            presentedController.view.alpha = 0.4;
        } completion:^(BOOL finished) {
            [transitionContext completeTransition: ![transitionContext transitionWasCancelled]];
        }];
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 伙计们,对于那些在animateTransition之后有黑/白屏幕的人:(解雇).我终于找到了原因.不要注释掉__vc.modalPresentationStyle = UIModalPresentationCustom__.问题出在那一行:__ [transitionContext.containerView addSubview:fromViewController.view] __.不要再次重新添加'from'(UITransitionContextFromViewControllerKey)视图,因为它已经在视图层次结构中.它在iOS7上很好,但在iOS8中没有. (22认同)
  • 只需改进SoftDesigner的答案,您所要做的就是**[transitionContext.containerView addSubview:toViewController.view]**.解雇时无需再次添加.在呈现/解除时,无需添加fromViewController.view.它适用于iOS 7+ (7认同)
  • @Awesomeness这个bug仍然是开放的(一个好兆头),但我看不到任何活动.雷达#是17476279,标题是"使用自定义转换结果以白屏显示",如果有帮助的话. (4认同)
  • 我发现将要呈现的视图控制器的modalPresentationStyle设置为UIModalPresentationCustom导致解雇时的白色视图(仅限iOS 8).如果您遇到此问题,请尝试注释掉viewController.modalPresentationStyle = UIModalPresentationCustom; (3认同)
  • 问题#1未记录在""当前活动视图控制器的视图可能已经是此容器视图的子视图,但可能需要添加属于所呈现的视图控制器的视图." 我觉得这是一个很好的做法.我怀疑另外两个_are_ UIKit错误,我已经提交了rdar:// 17295478,但没有回复任何消息.我想知道这与iOS 8 SDK有什么不同? (2认同)