ios UIViewController – 自定义关闭转换问题

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24085867/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 00:36:14  来源:igfitidea点击:

UIViewController – issue with custom dismiss transition

iosuiviewcontrolleruikituiviewanimationtransition

提问by Benjohn

Summary

概括

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

我有一个内容 UIViewController,它使用自定义转换呈现设置 UIViewController。演示文稿与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.

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

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 presentingcontroller slides down the screen, and the presentedcontroller 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 abovethe 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.

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

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>.

这是在屏幕上和关闭设置之间切换的方法(它只是由 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];
  }
}

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

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

So finally, the presenting view controller will receive animateTransition::

所以最后,呈现视图控制器将收到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 1and Test Point 2the 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!

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

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.
  • 我改变了它的视图框架。
  • 我改变了它的视图转换。
  • 我改变了它的视图层的 3d 变换。

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.

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

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.

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

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!

任何想法发生了什么?谢谢!

回答by John Scalo

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 beforefiring off your dismiss animation(s).
  • Likewise for the presented view's transform. On dismiss it gets reset to identity before your animateTransition is called.
  • 将呈现的(“to”)视图添加到容器中,然后将呈现的视图放在前面。不要添加呈现视图,因为您可能会将其从当前超级视图中删除。
  • 关闭时,UIKit在调用 animateTransition 之前将呈现的视图的 alpha 设置为 0 。因此,触发关闭动画之前,您需要将其设置为 1.0 或当前完成时的任何值。
  • 同样对于呈现的视图的转换。关闭时,它会在您的 animateTransition 被调用之前重置为身份。

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

回答by Dino Tw

Initially, I thought about using CATransitionto have custom transition effect when presentViewController:animated:completion:and dismissViewControllerAnimated:completion:a View Controller. But you want to show a portion of View Controller when the setting View Controller is presented, then I think CATransitionwould not help because you don't have full control of how far you want to move the View Controller.

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

I think the easiest way is to have one single View Controller with two full screen UIView. For the first UIView (View Controller's view, that is, self.view), you layout the setting, and on the second UIView, it's the regular View. In the ViewDidLoad, you add the 2nd view by using [self.view addSubview:2ndView];. Later when you want to present the setting view, you can do

我认为最简单的方法是拥有一个带有两个全屏 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;
}];

then do the other way to bring the 2ndView back.

然后以另一种方式将 2ndView 带回来。