如何在不显示中间视图的情况下通过多个视图展开

Posted

技术标签:

【中文标题】如何在不显示中间视图的情况下通过多个视图展开【英文标题】:How to unwind through multiple views without displaying intermediate views 【发布时间】:2014-11-06 17:20:02 【问题描述】:

假设我们有三个视图控制器:1、2 和 3。使用情节提要,使用展开转场从视图控制器 3 展开到视图控制器 1 非常简单。然而,当展开时,视图控制器 2 在视图控制器 1 显示之前短暂可见。有什么办法可以从 3 变为 1 而无需再次显示 2?

视图控制器 1:

- (void)viewDidAppear:(BOOL)animated 
  [super viewDidAppear:animated];
  NSLog(@"one did appear");


- (IBAction)goToTwo:(id)sender 
  NSLog(@"#### segue to two");
  [self performSegueWithIdentifier:@"TwoSegue" sender:self];


- (IBAction)unwindToOne:(UIStoryboardSegue *)sender 

视图控制器 2:

- (void)viewDidAppear:(BOOL)animated 
  [super viewDidAppear:animated];
  NSLog(@"two did appear");

- (IBAction)goToThree:(id)sender 
  NSLog(@"#### segue to three");
  [self performSegueWithIdentifier:@"ThreeSegue" sender:self];

视图控制器 3:

- (void)viewDidAppear:(BOOL)animated 
  [super viewDidAppear:animated];
  NSLog(@"three did appear");


- (IBAction)unwindToOne:(id)sender 
  NSLog(@"#### unwind to one");
  [self performSegueWithIdentifier:@"OneSegue" sender:self];

这会产生以下日志消息:

确实出现了一个 继续两个 确实出现了两个 继续三个 确实出现了三个 放松到一个 确实出现了两个 确实出现了一个

我尝试过使用自定义转场和禁用动画。尽管删除动画会使视图控制器 2 出现的时间更短,但它仍然会出现。有没有办法对这种行为进行编程?

故事板屏幕截图:

【问题讨论】:

你能显示你的故事板的截图吗? unwind segue 不应该显示中间视图。您是如何定义情节提要中的“OneSegue”的? 我添加了一个截图。展开转场是通过从三视图控制器图标拖动到退出图标并选择“unwindToOne”来创建的。 嗯……没错。您似乎已正确设置它。 SegueTwo 和 SegueThree 使用了哪些过渡? Segue 的类型。 我很确定这是 ios 8 中的一个错误。我已经通过返回多个控制器对此进行了测试,并且为所有控制器调用了 viewDidAppear,但只有在你之后的控制器下一个控制器'要简短地回到节目中。这在 iOS 7 中没有发生。 我在 iOS 11 和 Xcode 9.3 上看到了完全相同的行为。从 A 到 B,然后从 B 到 C。从 C 到 A 展开显示了 B 的短暂闪光。我通过在 B.viewDidDisappear() 中将中性背景视图带到前景来解决它。当然,这只有在中性视图与 A 的背景融合时才有效。 【参考方案1】:

这似乎是由于 unwind 搜索最近的视图控制器的方式,该控制器实现了您在情节提要中指定的展开操作。来自the documentation:

Unwind Segue 如何选择目的地

当一个展开转场被触发时,它必须找到最近的视图控制器,该控制器实现了定义展开转场时指定的展开动作。这个视图控制器成为展开转场的目的地。如果没有找到合适的视图控制器,则 unwind segue 被中止。搜索顺序如下:

viewControllerForUnwindSegueAction:fromViewController:withSender: 消息被发送到源视图控制器的父级。

viewControllerForUnwindSegueAction:fromViewController:withSender: 消息被发送到下一个父视图控制器 [...]

如果您对视图控制器是否应处理展开操作有特定要求,则可以覆盖 canPerformUnwindSegueAction:fromViewController:withSender:。

您示例中的视图控制器没有父级,因此 似乎后备(我看不到文档)是实例化每个 presentingViewController 并调用 @987654326依次@他们。从ViewControllerTwo 中的这个方法返回NO 并不会阻止它的实例化和显示,所以这并不能解决问题。

我已尝试将您的代码嵌入导航控制器中,并且效果很好。这是因为在这种情况下,UINavigationController 是每个视图控制器的父级,它处理目标视图控制器的所有选择。更多文档:

容器视图控制器

容器视图控制器在展开过程中有两个职责,这两个职责都将在下面讨论。如果您使用的是 SDK 提供的容器视图控制器,例如 UINavigationController,这些会自动处理。

如果您要创建一个简单的容器视图控制器来充当三个视图控制器的父级,则可以在调用 @ 之前使用其 viewControllerForUnwindSegueAction 方法检查其每个子控制器是否存在展开操作987654331@ 在那个控制器上,最后返回第一个返回 YES 的控制器。

选择子视图控制器来处理展开动作

如 Unwind Segue 如何选择其目的地中所述,展开 segue 的源视图控制器会根据其父级来定位要处理展开操作的视图控制器。您的容器视图控制器应该覆盖清单 2 中所示的方法,以在其子视图控制器中搜索想要处理展开操作的视图控制器。如果容器的子视图控制器都不想处理展开动作,它应该调用超级的实现并返回结果。

容器视图控制器应该以对该容器有意义的方式搜索其子级。例如,UINavigationController 从导航堆栈顶部的视图开始向后搜索其 viewControllers 数组。

清单 2 重写此方法以返回要处理展开操作的视图控制器。 - (UIViewController *)viewControllerForUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender

容器视图控制器设计有一个 Apple 专用的 whole article,我不会在这里重复(Apple 在这个答案中已经写了足够多的东西了!)但看起来需要一些思考才能得到它是的,因为这取决于您希望它们在您的应用程序中扮演的确切角色。

一个快速的解决方法是,使用 unwind segues 获得你想要的行为,可以将视图控制器嵌入到 UINavigationController,然后使用隐藏导航栏

[self.navigationController setNavigationBarHidden:YES];

【讨论】:

当segues 是“Push” segues 时有效。但如果它们是模态转场或自定义转场,则在展开期间仍会出现视图 2。这让我找到了一个我将发布的解决方案/解决方法。【参考方案2】:

乔希的回答让我找到了解决方案。以下是如何做到这一点:

创建一个根 UINavigationController,并将其分配给扩展 UINavigationController 并覆盖 segueForUnwindingToViewController:fromViewController:identifier 方法的类。如果需要,可以按标识符过滤。

自定义导航控制器:

- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier 
  return [[CustomUnwindSegue alloc] initWithIdentifier:identifier source:fromViewController destination:toViewController];

创建一个自定义推送转场,其行为类似于模态转场,但使用了我们的导航控制器。将此用于所有“推送”segues。

CustomPushSegue:

-(void) perform
  [[self.sourceViewController navigationController] pushViewController:self.destinationViewController animated:NO];

创建一个自定义展开转场,它使用导航控制器弹出到目的地。这由我们的导航控制器在 segueForUnwindingToViewController:fromViewController:identifier 方法中调用。

CustomUnwindSegue:

- (void)perform 
  [[self.destinationViewController navigationController] popToViewController:self.destinationViewController animated:NO];

通过利用这些模式的组合,第二个视图控制器在展开过程中永远不会出现。

新的日志输出:

确实出现了一个 #### 转到两个 确实出现了两个 #### 转为三个 确实出现了三个 #### 展开为一个 确实出现了一个

我希望这可以帮助遇到同样问题的其他人。

【讨论】:

【参考方案3】:

看起来正在使用自定义转场,因此可能会以某种方式进行干扰,尽管我从未见过它发生过。我建议您查看 Apple 的示例项目。它还包含自定义转场,因此它应该是您的一个很好的起点。

Apple's Unwind Segue Example

【讨论】:

这发生在 push、modal 和 custom segues 中。最后我使用了自定义转场,因为当使用 [self.destinationViewControllerdismissViewControllerAnimated:NO completion:nil] 进行自定义展开转场时,视图控制器 2 在屏幕上显示的时间最短。【参考方案4】:

我很惊讶您看到了您所看到的行为,但改变它的一种方法是使用显式关闭而不是展开转场(假设前转转场是模态的)。

如果 VC1 这样做,一切都会正常:

[self dismissViewControllerAnimated:YES completion:^];

或者如果其他一些 vc 这样做:

[vc1 dismissViewControllerAnimated:YES completion:^];

唯一的问题是,如果您想从其他 vc 中解散,则需要 vc1 的句柄。您可以使用委托模式让 vc1 知道它应该执行解除操作,但更简单的解决方案是让 vc2 或 3 在应该发生展开时发布通知。

VC 2 或 3 可以这样做:

// whenever we want to dismiss
[[NSNotificationCenter defaultCenter] postNotificationName:@"TimeToDismiss" object:self];

VC1 可以做到这一点:

[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(doDismiss:)
                                             name:@"TimeToDismiss"
                                           object:nil];

- (void)doDismiss:(NSNotification *)notification 
    [self dismissViewControllerAnimated:YES completion:^];

【讨论】:

以上是关于如何在不显示中间视图的情况下通过多个视图展开的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用单例的情况下在多个视图控制器之间传递数据?

Java/Android:如何在不切断边缘视图的情况下显示一系列图像视图?

如何在不使用临时表或视图的情况下在多个列上使用 PIVOT [重复]

如何在不使用对话框的情况下在操作栏上方显示视图?

在不使用导航控制器的情况下转到第二个视图时如何显示动画?

如何在不违反 CSP 的情况下绑定剃须刀视图中的 onchange 等事件