【问题标题】:UIViewController – issue with custom dismiss transitionUIViewController – 自定义关闭过渡的问题
【发布时间】:2014-06-06 15:46:22
【问题描述】:

总结

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

当我稍后使用dismissViewControllerAnimated:completion: 取消设置时,呈现控制器突然跳回到设置控制器呈现之前的初始位置。

我在设备上解决了这个问题,但在模拟器上没有。但是,我想知道我做错了什么,而不是闯入一个让它消失的困境。我还计划让这个动画具有交互性,我怀疑这个问题会在我这样做时放大。

自定义过渡 - 打开引擎盖

想要的效果是 presenting 控制器在屏幕上向下滑动,而 presenting 控制器看起来躺在它的后面,从它抬起来填满屏幕.呈现控制器的顶部在呈现控制器的使用期间保持在屏幕上。它停留在屏幕底部,但呈现的控制器之上。

您可以想象抬起汽车上的发动机罩(前部呈现控制器)以查看后面的发动机(显示的设置),但发动机罩在一些上下文中保持在底部可见。

我计划对此进行改进,以使呈现控制器看起来确实以 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;
}

问题

在上面的代码中,我已经指出 !!!这行不应该是必需的!!!

发生的情况是,在 Test Point 1Test Point 2 之间,呈现视图控制器的屏幕位置被重置为默认的全屏边界。因此,它并没有在屏幕底部准备好再次平滑动画,而是突然向上跳到屏幕的位置,它也应该平滑动画!

我尝试了各种方法来为屏幕下方的呈现视图控制器设置动画:

  • 我更改了它的视图框架。
  • 我已更改其视图的变换。
  • 我已更改其视图层的 3d 变换。

在所有情况下,在 测试点 1,当请求转换委托时,呈现控制器已按我的预期设置。但是,在所有情况下,在 测试点 2,呈现的视图控制器都丢失了正确的位置,并且已被“清除”为具有我想要为其设置动画的正常全屏位置。 p>

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

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

有什么想法吗?谢谢!

【问题讨论】:

    标签: ios uiviewcontroller uikit uiviewanimationtransition


    【解决方案1】:

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

    • 将呈现的(“to”)视图添加到容器中,然后将呈现的视图置于前面。 不要添加展示视图,因为您可能会将其从当前的超级视图中删除
    • 在关闭时,UIKit 在调用 animateTransition 之前将呈现视图的 alpha 设置为 0。因此,您需要将其设置为 1.0 或当前完成时触发您的解除动画之前的任何值。
    • 对于呈现视图的变换也是如此。关闭时,它会在调用 animateTransition 之前重置为身份。

    鉴于这一切,我认为这应该可行:

    -(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]];
            }];
        }
    }
    

    【讨论】:

    • "The view of the currently active view controller is likely already a subview of this container view but the view belonging to the view controller being presented may need to be added." 之外没有记录问题 #1。我只是觉得这是一种很好的做法。我怀疑另外两个 UIKit 错误,我已经提交了 rdar://17295478 但没有收到任何回复。我想知道这是否与 iOS 8 SDK 不同?
    • 我发现将要呈现的视图控制器的 modalPresentationStyle 设置为 UIModalPresentationCustom 会导致关闭时出现白色视图(仅限 iOS 8)。如果您遇到此问题,请尝试注释掉 viewController.modalPresentationStyle = UIModalPresentationCustom;
    • @Awesomeness 该错误仍处于打开状态(一个好兆头),但我看不到任何活动。雷达编号为 17476279,标题为“使用自定义转换关闭会导致白屏”,以防万一。
    • 伙计们,对于那些在 animateTransition 后出现黑/白屏幕的人:(关闭时)。我终于找到了原因。不要注释掉 vc.modalPresentationStyle = UIModalPresentationCustom。问题出在这一行:[transitionContext.containerView addSubview:fromViewController.view]。不要再次重新添加“来自”(UITransitionContextFromViewControllerKey)视图,因为它已经在视图层次结构中。在 iOS7 上可以,但在 iOS8 上不行。
    • 只是改进 SoftDesigner 的答案,您所要做的就是在演示时 [transitionContext.containerView addSubview:toViewController.view]。解散时无需再次添加。展示/关闭时也无需添加 fromViewController.view。它适用于 iOS 7+
    【解决方案2】:

    最初,我考虑在presentViewController:animated:completion:dismissViewControllerAnimated:completion: 一个视图控制器时使用CATransition 来自定义过渡效果。但是您想在设置 View Controller 时显示 View Controller 的一部分,那么我认为 CATransition 将无济于事,因为您无法完全控制要移动 View Controller 的距离。

    我认为最简单的方法是让一个 View Controller 和两个全屏 UIView。第一个 UIView(View Controller 的视图,也就是 self.view),你布局设置,第二个 UIView 就是普通的 View。在 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;
    }];
    

    然后以其他方式恢复 2ndView。

    【讨论】:

    • 感谢您的回答,但我不明白您声称 CCTransition 无法完全控制控制器移动的距离。文档等中是否提到了这一点?
    • CATransition 是控制过渡应该如何,例如向右或向左移动,淡入或淡出,过渡速度。我不明白它如何控制你想要移动视图的距离,也许我错过了什么。
    • 啊,好吧——Apple 添加了一个非常重要的库来支持 iOS 7 中的视图控制器转换。它应该做这种事情。这是reasonable introduction
    • 我不熟悉UIViewControllerContextTransitioning,但它看起来与CATransition不同。链接中的示例代码也使用UIView animateWithDuration:animations:来实现动画效果。
    • 这就是我打算使用CATransition 来更改过渡样式的方式,或者使用presentViewController:animated:completion:dismissViewControllerAnimated:completion:iPhone - designing my own viewController transition 隐藏视图控制器。
    猜你喜欢
    • 2020-01-08
    • 1970-01-01
    • 1970-01-01
    • 2014-09-08
    • 2023-03-07
    • 2019-06-16
    • 2013-09-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多