关闭堆栈中较低的ViewController的行为并不像预期的那样(Dismissing a ViewController lower in the stack does not behave as expected)

我正在构建一个复杂的应用程序,中间有一个分支。

在应用程序的某个时刻,呈现了一个特定的UIViewController,我们将其称为mainViewController (缩短的mainVC )。

mainVC通过代码使用下面的代码呈现另一个视图控制器(为了隐私的原因,我mainVC了它的一部分):

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"SecondaryStoryboard" bundle:secondaryBundle]; SecondViewController *secondVC = [storyboard instantiateInitialViewController]; [self presentViewController:secondVC animated:YES completion:nil];

所以第二个secondVC稍后会呈现另一个视图控制器,称为thirdVC 。 这是通过在上面的代码中使用的故事板中设置的自定义segue完成的,代码如下所示:

@implementation VCCustomPushSegue - (void)perform { UIView *sourceView = ((UIViewController *)self.sourceViewController).view; UIView *destinationView = ((UIViewController *)self.destinationViewController).view; UIWindow *window = [[[UIApplication sharedApplication] delegate] window]; destinationView.center = CGPointMake(sourceView.center.x + sourceView.frame.size.width, destinationView.center.y); [window insertSubview:destinationView aboveSubview:sourceView]; [UIView animateWithDuration:0.4 animations:^{ destinationView.center = CGPointMake(sourceView.center.x, destinationView.center.y); sourceView.center = CGPointMake(0 - sourceView.center.x, destinationView.center.y); } completion:^(BOOL finished){ [self.sourceViewController presentViewController:self.destinationViewController animated:NO completion:nil]; }]; } @end

正如你所看到的,这个segue通过自定义动画(从右到左的幻灯片)以模态方式呈现目标视图控制器(通过使用presentViewController: 。

所以基本上到这里一切都很好。 我用经典的模态动画(从底部向上滑动)呈现第二个thirdVC ,并用我的自定义过渡呈现第三个thirdVC 。

但是当我想解雇第三个thirdVC ,我想要的是直接回到主mainVC 。 所以我从第三个thirdVC调用以下thirdVC :

self.modalTransitionStyle = UIModalTransitionStyleCoverVertical; [self.presentingViewController.presentingViewController dismissViewControllerAnimated:_animate completion:nil];

这样,我调用dismissViewControllerAnimated:直接在mainVC (由self.presentingViewController.presentingViewController引用),我期待第三个thirdVC被一个动画解散,而第二个secondVC只是在没有动画的情况下消失。

正如Apple在UIViewController类文档中所说:

呈现视图控制器负责解除其呈现的视图控制器 。 如果您在呈现的视图控制器本身上调用此方法,它会自动将该消息转发给呈现视图控制器。

如果您连续呈现多个视图控制器 ,从而构建一叠呈现的视图控制器,则在视图控制器上调用此方法时,视图控制器将取消其立即子视图控制器以及该子视图上的所有视图控制器 。 发生这种情况时, 只有最上面的视图才会以动画形式被解散 ; 任何中间视图控制器都可以简单地从堆栈中移除。 最顶层的视图使用其模式转换样式被解散,这可能与堆栈中较低视图控制器使用的样式不同。

问题在于它不会发生什么。 在我的场景中,第三个thirdVC消失了,并且显示了secondVC被经典模态幻灯片解散到底部动画。

我究竟做错了什么 ?


编辑:

所以@ codeFi的答案可能是在一个经典的项目中工作,但这里的问题是我正在研究一个框架。 因此, mainVC将在客户端应用程序中,第二个secondVC和thirdVC在我的框架中,在一个单独的故事板中。 我不能以任何其他方式访问mainVC ,除了在我的代码中引用它,所以不幸的是,这里展开的segues不是一个选项。

I'm building a complex app that has kind of a branch in the middle.

At some point in the app, a particular UIViewController is presented, we'll call it mainViewController (shortened mainVC).

The mainVC presents another view controller, by code, using the following code (I strip out parts of it for privacy reasons):

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"SecondaryStoryboard" bundle:secondaryBundle]; SecondViewController *secondVC = [storyboard instantiateInitialViewController]; [self presentViewController:secondVC animated:YES completion:nil];

So the secondVC will later present another view controller, called thirdVC. This is done using a custom segue, set in the storyboard used in the code above, which code looks like this:

@implementation VCCustomPushSegue - (void)perform { UIView *sourceView = ((UIViewController *)self.sourceViewController).view; UIView *destinationView = ((UIViewController *)self.destinationViewController).view; UIWindow *window = [[[UIApplication sharedApplication] delegate] window]; destinationView.center = CGPointMake(sourceView.center.x + sourceView.frame.size.width, destinationView.center.y); [window insertSubview:destinationView aboveSubview:sourceView]; [UIView animateWithDuration:0.4 animations:^{ destinationView.center = CGPointMake(sourceView.center.x, destinationView.center.y); sourceView.center = CGPointMake(0 - sourceView.center.x, destinationView.center.y); } completion:^(BOOL finished){ [self.sourceViewController presentViewController:self.destinationViewController animated:NO completion:nil]; }]; } @end

As you can see this segue presents the destination view controller modally (by the use of presentViewController:) with a custom animation (a slide from right to left).

So basically up to here everything is fine. I present the secondVC with a classic modal animation (slide up from bottom) and present the thirdVC with my custom transition.

But when I want to dismiss the thirdVC, what I want is to go back directly to the mainVC. So I call the following from the thirdVC :

self.modalTransitionStyle = UIModalTransitionStyleCoverVertical; [self.presentingViewController.presentingViewController dismissViewControllerAnimated:_animate completion:nil];

That way, I'm calling dismissViewControllerAnimated: directly on mainVC (referenced by self.presentingViewController.presentingViewController), and I'm expecting the thirdVC to be dismissed with an animation, and the secondVC to just disappear without animation.

As Apple says in the UIViewController Class Documentation:

The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, it automatically forwards the message to the presenting view controller.

If you present several view controllers in succession, thus building a stack of presented view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack. When this happens, only the top-most view is dismissed in an animated fashion; any intermediate view controllers are simply removed from the stack. The top-most view is dismissed using its modal transition style, which may differ from the styles used by other view controllers lower in the stack.

The issue is that it's not what happens. In my scenario, the thirdVC disappears, and shows the secondVC being dismissed with the classic modal slide to bottom animation.

What am I doing wrong ?


Edit :

So @codeFi's answer is probably working in a classic project, but the problem here is that I'm working on a framework. So mainVC would be in a client app, and the secondVC and thirdVC are in my framework, in a separate storyboard. I don't have access to mainVC in any other way than a reference to it in my code, so unwind segues are unfortunately not an option here.

最满意答案

我一直有这个完全相同的问题,我设法通过添加屏幕的快照作为secondVC.view的子视图来secondVC.view ,如下所示:

if (self.presentedViewController.presentedViewController) { [self.presentedViewController.view addSubview:[[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO]]; } [self dismissViewControllerAnimated:YES completion:nil];

不漂亮,但它似乎工作。

注意:如果您的secondVC具有导航栏,则需要隐藏快照屏幕和将快照作为子视图添加到secondVC的导航栏,否则快照将出现在导航栏下方,从而看似显示双导航酒吧在解雇动画期间。 码:

if (self.presentedViewController.presentedViewController) { UIView *snapshot = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO]; [self.presentedViewController.navigationController setNavigationBarHidden:YES animated:NO]; [self.presentedViewController.view addSubview:snapshot]; } [self dismissViewControllerAnimated:YES completion:nil];

I've been having this exact same issue, and I've managed to visually work around it by adding a snapshot of the screen as a subview to secondVC.view, like so:

if (self.presentedViewController.presentedViewController) { [self.presentedViewController.view addSubview:[[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO]]; } [self dismissViewControllerAnimated:YES completion:nil];

Not pretty, but it seems to be working.

NOTE: if your secondVC has a navigation bar, you will need to hide the navigation bar in between snapshotting the screen and adding the snapshot as a subview to secondVC, as otherwise the snapshot will appear below the navigation bar, thus seemingly displaying a double navigation bar during the dismissal animation. Code:

if (self.presentedViewController.presentedViewController) { UIView *snapshot = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO]; [self.presentedViewController.navigationController setNavigationBarHidden:YES animated:NO]; [self.presentedViewController.view addSubview:snapshot]; } [self dismissViewControllerAnimated:YES completion:nil];关闭堆栈中较低的ViewController的行为并不像预期的那样(Dismissing a ViewController lower in the stack does not behave as expected)

我正在构建一个复杂的应用程序,中间有一个分支。

在应用程序的某个时刻,呈现了一个特定的UIViewController,我们将其称为mainViewController (缩短的mainVC )。

mainVC通过代码使用下面的代码呈现另一个视图控制器(为了隐私的原因,我mainVC了它的一部分):

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"SecondaryStoryboard" bundle:secondaryBundle]; SecondViewController *secondVC = [storyboard instantiateInitialViewController]; [self presentViewController:secondVC animated:YES completion:nil];

所以第二个secondVC稍后会呈现另一个视图控制器,称为thirdVC 。 这是通过在上面的代码中使用的故事板中设置的自定义segue完成的,代码如下所示:

@implementation VCCustomPushSegue - (void)perform { UIView *sourceView = ((UIViewController *)self.sourceViewController).view; UIView *destinationView = ((UIViewController *)self.destinationViewController).view; UIWindow *window = [[[UIApplication sharedApplication] delegate] window]; destinationView.center = CGPointMake(sourceView.center.x + sourceView.frame.size.width, destinationView.center.y); [window insertSubview:destinationView aboveSubview:sourceView]; [UIView animateWithDuration:0.4 animations:^{ destinationView.center = CGPointMake(sourceView.center.x, destinationView.center.y); sourceView.center = CGPointMake(0 - sourceView.center.x, destinationView.center.y); } completion:^(BOOL finished){ [self.sourceViewController presentViewController:self.destinationViewController animated:NO completion:nil]; }]; } @end

正如你所看到的,这个segue通过自定义动画(从右到左的幻灯片)以模态方式呈现目标视图控制器(通过使用presentViewController: 。

所以基本上到这里一切都很好。 我用经典的模态动画(从底部向上滑动)呈现第二个thirdVC ,并用我的自定义过渡呈现第三个thirdVC 。

但是当我想解雇第三个thirdVC ,我想要的是直接回到主mainVC 。 所以我从第三个thirdVC调用以下thirdVC :

self.modalTransitionStyle = UIModalTransitionStyleCoverVertical; [self.presentingViewController.presentingViewController dismissViewControllerAnimated:_animate completion:nil];

这样,我调用dismissViewControllerAnimated:直接在mainVC (由self.presentingViewController.presentingViewController引用),我期待第三个thirdVC被一个动画解散,而第二个secondVC只是在没有动画的情况下消失。

正如Apple在UIViewController类文档中所说:

呈现视图控制器负责解除其呈现的视图控制器 。 如果您在呈现的视图控制器本身上调用此方法,它会自动将该消息转发给呈现视图控制器。

如果您连续呈现多个视图控制器 ,从而构建一叠呈现的视图控制器,则在视图控制器上调用此方法时,视图控制器将取消其立即子视图控制器以及该子视图上的所有视图控制器 。 发生这种情况时, 只有最上面的视图才会以动画形式被解散 ; 任何中间视图控制器都可以简单地从堆栈中移除。 最顶层的视图使用其模式转换样式被解散,这可能与堆栈中较低视图控制器使用的样式不同。

问题在于它不会发生什么。 在我的场景中,第三个thirdVC消失了,并且显示了secondVC被经典模态幻灯片解散到底部动画。

我究竟做错了什么 ?


编辑:

所以@ codeFi的答案可能是在一个经典的项目中工作,但这里的问题是我正在研究一个框架。 因此, mainVC将在客户端应用程序中,第二个secondVC和thirdVC在我的框架中,在一个单独的故事板中。 我不能以任何其他方式访问mainVC ,除了在我的代码中引用它,所以不幸的是,这里展开的segues不是一个选项。

I'm building a complex app that has kind of a branch in the middle.

At some point in the app, a particular UIViewController is presented, we'll call it mainViewController (shortened mainVC).

The mainVC presents another view controller, by code, using the following code (I strip out parts of it for privacy reasons):

UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"SecondaryStoryboard" bundle:secondaryBundle]; SecondViewController *secondVC = [storyboard instantiateInitialViewController]; [self presentViewController:secondVC animated:YES completion:nil];

So the secondVC will later present another view controller, called thirdVC. This is done using a custom segue, set in the storyboard used in the code above, which code looks like this:

@implementation VCCustomPushSegue - (void)perform { UIView *sourceView = ((UIViewController *)self.sourceViewController).view; UIView *destinationView = ((UIViewController *)self.destinationViewController).view; UIWindow *window = [[[UIApplication sharedApplication] delegate] window]; destinationView.center = CGPointMake(sourceView.center.x + sourceView.frame.size.width, destinationView.center.y); [window insertSubview:destinationView aboveSubview:sourceView]; [UIView animateWithDuration:0.4 animations:^{ destinationView.center = CGPointMake(sourceView.center.x, destinationView.center.y); sourceView.center = CGPointMake(0 - sourceView.center.x, destinationView.center.y); } completion:^(BOOL finished){ [self.sourceViewController presentViewController:self.destinationViewController animated:NO completion:nil]; }]; } @end

As you can see this segue presents the destination view controller modally (by the use of presentViewController:) with a custom animation (a slide from right to left).

So basically up to here everything is fine. I present the secondVC with a classic modal animation (slide up from bottom) and present the thirdVC with my custom transition.

But when I want to dismiss the thirdVC, what I want is to go back directly to the mainVC. So I call the following from the thirdVC :

self.modalTransitionStyle = UIModalTransitionStyleCoverVertical; [self.presentingViewController.presentingViewController dismissViewControllerAnimated:_animate completion:nil];

That way, I'm calling dismissViewControllerAnimated: directly on mainVC (referenced by self.presentingViewController.presentingViewController), and I'm expecting the thirdVC to be dismissed with an animation, and the secondVC to just disappear without animation.

As Apple says in the UIViewController Class Documentation:

The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, it automatically forwards the message to the presenting view controller.

If you present several view controllers in succession, thus building a stack of presented view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack. When this happens, only the top-most view is dismissed in an animated fashion; any intermediate view controllers are simply removed from the stack. The top-most view is dismissed using its modal transition style, which may differ from the styles used by other view controllers lower in the stack.

The issue is that it's not what happens. In my scenario, the thirdVC disappears, and shows the secondVC being dismissed with the classic modal slide to bottom animation.

What am I doing wrong ?


Edit :

So @codeFi's answer is probably working in a classic project, but the problem here is that I'm working on a framework. So mainVC would be in a client app, and the secondVC and thirdVC are in my framework, in a separate storyboard. I don't have access to mainVC in any other way than a reference to it in my code, so unwind segues are unfortunately not an option here.

最满意答案

我一直有这个完全相同的问题,我设法通过添加屏幕的快照作为secondVC.view的子视图来secondVC.view ,如下所示:

if (self.presentedViewController.presentedViewController) { [self.presentedViewController.view addSubview:[[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO]]; } [self dismissViewControllerAnimated:YES completion:nil];

不漂亮,但它似乎工作。

注意:如果您的secondVC具有导航栏,则需要隐藏快照屏幕和将快照作为子视图添加到secondVC的导航栏,否则快照将出现在导航栏下方,从而看似显示双导航酒吧在解雇动画期间。 码:

if (self.presentedViewController.presentedViewController) { UIView *snapshot = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO]; [self.presentedViewController.navigationController setNavigationBarHidden:YES animated:NO]; [self.presentedViewController.view addSubview:snapshot]; } [self dismissViewControllerAnimated:YES completion:nil];

I've been having this exact same issue, and I've managed to visually work around it by adding a snapshot of the screen as a subview to secondVC.view, like so:

if (self.presentedViewController.presentedViewController) { [self.presentedViewController.view addSubview:[[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO]]; } [self dismissViewControllerAnimated:YES completion:nil];

Not pretty, but it seems to be working.

NOTE: if your secondVC has a navigation bar, you will need to hide the navigation bar in between snapshotting the screen and adding the snapshot as a subview to secondVC, as otherwise the snapshot will appear below the navigation bar, thus seemingly displaying a double navigation bar during the dismissal animation. Code:

if (self.presentedViewController.presentedViewController) { UIView *snapshot = [[UIScreen mainScreen] snapshotViewAfterScreenUpdates:NO]; [self.presentedViewController.navigationController setNavigationBarHidden:YES animated:NO]; [self.presentedViewController.view addSubview:snapshot]; } [self dismissViewControllerAnimated:YES completion:nil];