UIViewController – 自定义关闭转换问题 [英] UIViewController – issue with custom dismiss transition

查看:20
本文介绍了UIViewController – 自定义关闭转换问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

总结

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

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

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

自定义过渡 - 打开引擎盖

想要的效果是 presenting 控制器从屏幕上滑下,并且 presenting 控制器看起来躺在它的后面,从那里它抬起来填满屏幕.在呈现控制器的使用期限内,呈现控制器的顶部保持在屏幕上.它位于屏幕底部,但位于呈现的控制器之上.

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

我计划改进它,以便呈现控制器看起来真的以 3d 方式提升了透视效果,但我还没有做到这一点.

取消设置后,原来的展示控制器(引擎盖)应该会滑回屏幕,展示的控制器(设置)稍微下沉(关闭引擎盖).

代码

这是在屏幕上打开和关闭设置的方法(它只是由 UIButton 调用).您会注意到呈现视图控制器将自身设置为 .

-(void) toggleSettingsViewController{const BOOL settingsAreShowing = [self presentViewController] != nil;如果(!settingsAreShowing){UIViewController *const settingsController = [[self storyboard] instantiateViewControllerWithIdentifier: @"STSettingsViewController"];[settingsController setTransitioningDelegate: self];[settingsController setModalPresentationStyle: UIModalPresentationCustom];[self presentViewController: settingsController 动画: YES 完成: nil];}别的{[自我解雇ViewControllerAnimated:是完成:nil];}}

为了实现,呈现视图控制器也只是将自身作为

返回

-(id) animationControllerForPresentedController:(UIViewController *)presentedpresentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{回归自我;}-(id) animationControllerForDismissedController:(UIViewController *)dismissed{//测试点 1.回归自我;}

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

-(void) animateTransition:(id)transitionContext{UIViewController *const fromController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];UIViewController *const toController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];const BOOL isUnwinding = [toControllerpresentViewController] == fromController;const BOOL isPresenting = !isUnwinding;UIViewController *presentingController = isPresenting ?从控制器:到控制器;UIViewController *presentedController = isPresenting ?toController : fromController;如果(正在呈现){//将呈现的控制器(设置)添加到呈现控制器_behind_ 的视图层次结构中.[[transitionContext containerView] insertSubview: [presentedController view] underSubview: [presentingController view]];//设置呈现的设置控制器的初始位置.把它缩小,让它看起来在远处.将其向下调整,使其变暗且带有阴影.presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);presentedController.view.alpha = 0.7;[UIView animateWithDuration: [self transitionDuration: transitionContext] animations:^{//抬起呈现的控制器.presentedController.view.transform = CGAffineTransformMakeScale(1.0, 1.0);//使呈现的控制器变亮(走出阴影).presentedController.view.alpha = 1;//将呈现控制器向下推屏幕 – 稍后添加 3d 效果.presentingController.view.layer.transform = CATransform3DMakeTranslation(0,400,0);} 完成:^(BOOL 完成){[transitionContext completeTransition: ![transitionContext transitionWasCancelled]];}];}别的{//测试点 2.//!!!这行应该是不需要的!!!//它将呈现控制器重置到它应该在的位置.presentingController.view.layer.transform = CATransform3DMakeTranslation(0,400,0);[UIView animateWithDuration: [self transitionDuration: transitionContext] animations:^{//将呈现控制器返回其原始位置.presentingController.view.layer.transform = CATransform3DIdentity;//再次降低呈现的控制器并将其放回阴影中.presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);presentedController.view.alpha = 0.4;}完成:^(BOOL完成){[transitionContext completeTransition: ![transitionContext transitionWasCancelled]];}];}}-(NSTimeInterval) transitionDuration:(id)transitionContext{返回 0.5;}

问题

在上面的代码中,我已经指出!!!这行不需要!!!.

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

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

  • 我已更改其视图的框架.
  • 我已更改其视图的转换.
  • 我已更改其视图层的 3d 变换.

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

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

我怀疑我做错了什么,我会因为我的解决方法而遇到麻烦,只是掩盖了另一个问题.

有什么想法吗?谢谢!

解决方案

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

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

鉴于所有这些,我认为这应该可行:

-(void)animateTransition:(id)transitionContext{UIViewController *fromController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];UIViewController *toController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];UIView *containerView = transitionContext.containerView;const BOOL isUnwinding = [toControllerpresentViewController] == fromController;const BOOL isPresenting = !isUnwinding;UIViewController *presentingController = isPresenting ?从控制器:到控制器;UIViewController *presentedController = isPresenting ?toController : fromController;[containerView addSubview:presentingController.view];[容器视图带来SubviewToFront:presentingController.view];如果(正在呈现){//设置呈现的设置控制器的初始位置.把它缩小,让它看起来在远处.将其向下调整,使其变暗且带有阴影.presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);presentedController.view.alpha = 0.7;[UIView animateWithDuration: [self transitionDuration: transitionContext] animations:^{//抬起呈现的控制器.presentedController.view.transform = CGAffineTransformMakeScale(1.0, 1.0);//使呈现的控制器变亮(走出阴影).presentedController.view.alpha = 1;//将呈现控制器向下推屏幕 – 稍后添加 3d 效果.presentingController.view.layer.transform = CATransform3DMakeTranslation(0,400,0);} 完成:^(BOOL 完成){[transitionContext completeTransition: ![transitionContext transitionWasCancelled]];}];}别的{presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);presentedController.view.alpha = 0.7;[UIView animateWithDuration: [self transitionDuration: transitionContext] animations:^{//将呈现控制器返回其原始位置.presentingController.view.layer.transform = CATransform3DIdentity;//再次降低呈现的控制器并将其放回阴影中.presentedController.view.transform = CGAffineTransformMakeScale(0.9, 0.9);presentedController.view.alpha = 0.4;}完成:^(BOOL完成){[transitionContext completeTransition: ![transitionContext transitionWasCancelled]];}];}}

Summary

I have a content UIViewController that presents a settings UIViewController using a custom transition. The presentation is with presentViewController:animated:completion:.

When I later dismiss the settings with dismissViewControllerAnimated:completion:, the presenting controller is suddenly jumped back to it's initial position prior to the settings controller presentation.

I have a work around for this on the device but not the simulator. However, I'd like to know what I'm doing wrong rather than hack in a bodge that makes it go away. I also plan to make this animation interactive, and I suspect this issues will amplify when I do this.

Custom Transition – Opening the hood

The desired effect is that the presenting controller slides down the screen, and the presented controller is seen to be lying behind it from where it lifts up to fill the screen. The top of the presenting controller remains on-screen during the lifetime of use of the presented controller. It stays at the bottom of the screen, but above the presented controller.

You could imagine lifting the bonnet on a car (the front presenting controller) to see the engine behind (the presented settings), but the bonnet stays visible at the bottom for a bit of context.

I plan to refine this so that the presenting controller really appears to lift up with perspective in a 3d way, but I've not got that far, yet.

When the settings are dismissed, the original presenting controller (bonnet) should slide back up the screen and the presented controller (settings) sink back slightly (closing the bonnet).

Code

Here's the method that toggles the settings on and off the screen (it's just called by a UIButton). You'll notice that the presenting view controller sets itself up as the <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];
  }
}

To implement <UIViewControllerAnimatedTransitioning> the presenting view controller also just returns itself as the <UIViewControllerAnimatedTransitioning>

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

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

So finally, the presenting view controller will receive 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;
}

Problem

In the code above, I've indicated !!!This line should not be needed!!!.

What's happening is that between Test Point 1 and Test Point 2 the screen position of the presenting view controller is reset to be the default full screen bounds. So, instead of being at the bottom of the screen ready to animate back up again smoothly, it suddenly jumps up the screen to position that it is meant to smoothly animate too!

I've tried various approaches to animating the presenting view controller down the screen:

  • I've changed its view's frame.
  • I've changed its view's transform.
  • I've changed its view's layer's 3d transform.

In all cases, at Test Point 1, when the transition delegate is asked for, the presenting controller is set up as I would expect. However, in all cases, at Test Point 2, the presenting view controller has lost the correct position and has been "cleared" to have the normal full screen position that I want to animate it to.

In the work around above I explicitly relocate the presenting view controller back to where it should be at the start of the animation with !!!This line should not be needed!!!. This seems to work on the device with the current version of iOS 7. However, on the simulator, the controller is visible at the cleared position for at least one frame.

I am suspicious that I am doing something else wrong, and that I'm going to get in to trouble with my workaround just masking another problem.

Any ideas what's going on? Thanks!

解决方案

A few potential gotchas with dismissal of modally presented view controllers using custom transition animations:

  • Add the presented ("to") view to the container, then bring the presented view front. Don't add the presenting view as you might remove it from its current superview.
  • On dismiss, UIKit sets the alpha of the presented view to 0 before animateTransition is called. So you'll want to set it to 1.0 or whatever it was at the completion of the present before firing off your dismiss animation(s).
  • Likewise for the presented view's transform. On dismiss it gets reset to identity before your animateTransition is called.

Given all that, I think this should work:

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

这篇关于UIViewController – 自定义关闭转换问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆