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

时间:2014-06-06 15:46:22

标签: ios uiviewcontroller uikit uiviewanimationtransition

摘要

我有一个内容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];
  }
}

要实现<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;
}

最后,呈现视图控制器将收到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;
}

问题

在上面的代码中,我已经指出 !!!不应该需要此行!!!

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

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

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

在所有情况下,在测试点1 ,当要求转换委托时,呈现控制器按照我的预期设置。但是,在所有情况下,在测试点2 时,呈现视图控制器已丢失正确的位置并已被清除&#34;拥有我想要动画的正常全屏位置。

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

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

任何想法是怎么回事?谢谢!

2 个答案:

答案 0 :(得分:38)

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

  • 将显示的(&#34;到&#34;)视图添加到容器中,然后将显示的视图放在前面。不要添加展示视图,因为您可能会将其从当前的超级视图中删除。
  • 在关闭时,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]];
        }];
    }
}

答案 1 :(得分:0)

最初,我考虑在CATransitionpresentViewController:animated:completion:视图控制器时使用dismissViewControllerAnimated:completion:来获得自定义转换效果。但是,当您显示设置视图控制器时,您希望显示View Controller的一部分,然后我认为CATransition无效,因为您无法完全控制您想要移动视图控制器的距离。

我认为最简单的方法是使用一个带有两个全屏UIView的View Controller。对于第一个UIView(View Controller的视图,即self.view),您可以布局设置,而在第二个UIView上,它是常规视图。在ViewDidLoad中,使用[self.view addSubview:2ndView];添加第二个视图。稍后当您想要显示设置视图时,可以执行

CGRect frame = secondView.frame;
frame.origin.y = the_y_coordinate_you_like;
UIView animateWithDuration:0.2 animations:^{
    secondView.frame = frame;
}];

然后用另一种方式将第二视图带回来。