这行代码:
[self dismissViewControllerAnimated:YES completion:nil]
在这种情况下,发送的信息并不是发给自身,而是发给呈现视图控制器,请求它执行关闭操作。当你呈现一个视图控制器时,你会创建一个呈现 VC 和被呈现 VC 之间的关系。因此,在呈现时不应该销毁呈现 VC(被呈现的 VC 无法将关闭消息发送回来...)。如果你没有意识到这点,就会让应用程序处于混乱状态。请参阅我的答案 Dismissing a Presented View Controller ,其中我建议使用以下方法进行更清晰地编写:
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil]
在您的情况下,您需要确保所有控制都在mainVC
中完成。您应该使用代理将正确的消息从ViewController1发送回MainViewController,以便mainVC可以关闭VC1,然后呈现VC2。
在您的.h文件中,在@interface上方为VC1添加一个协议:
@protocol ViewController1Protocol <NSObject>
- (void)dismissAndPresentVC2;
@end
在同一文件的@interface部分下面声明一个属性来保存委托指针:
@property (nonatomic,weak) id <ViewController1Protocol> delegate;
在 VC1 的 .m 文件中,dismiss 按钮的方法应该调用代理方法。
- (IBAction)buttonPressedFromVC1:(UIButton *)sender {
[self.delegate dissmissAndPresentVC2]
}
在 mainVC 中创建 VC1 时,将 mainVC 设置为 VC1 的代理:
- (IBAction)present1:(id)sender {
ViewController1* vc = [[ViewController1 alloc] initWithNibName:@"ViewController1" bundle:nil];
vc.delegate = self;
[self present:vc];
}
并实现委托方法:
- (void)dismissAndPresent2 {
[self dismissViewControllerAnimated:NO completion:^{ [self present2:nil];
}];
}
present2:
可以与你的VC2Pressed:
按钮IBAction方法相同。请注意,它是从完成块调用的,以确保在完全解除VC1之前不会呈现VC2。
您现在正在从VC1-> VCMain-> VC2移动,因此您可能只想要其中一个过渡进行动画处理。
在您的评论中,您对实现看似简单的事情所需的复杂性感到惊讶。我向您保证,这种委托模式在Objective-C和Cocoa的许多方面都是如此核心,而且这个例子是你可以得到的最简单的例子,你真的应该努力学习并熟悉它。
在苹果的视图控制器编程指南中,他们有这样说:
取消显示已呈现视图控制器
当需要取消显示呈现的视图控制器时,首选的方法是让呈现视图控制器取消显示。换句话说,尽可能让呈现视图控制器的相同视图控制器也负责取消显示。虽然有几种技术可以通知呈现视图控制器其已呈现的视图控制器应该被取消显示,但首选技术是委托。有关更多信息,请参见“使用委托与其他控制器通信”。
如果您真正思考您想要实现的内容以及您正在进行的方式,您将意识到向MainViewController发送消息以完成所有工作是唯一的出路,因为您不想使用NavigationController。如果您使用了NavController,则实际上是在“委派”,即使没有明确指定,也要求navController执行所有工作。需要有一个对象来保持对VC导航情况的中央跟踪,无论您做什么,都需要某种通信方法。
实际上,苹果的建议有点极端... 在正常情况下,您不需要制作专用代理和方法,您可以依赖于[self presentingViewController]dismissViewControllerAnimated:
-当您在像您这样的情况下希望解雇远程对象时,您需要小心处理。
这里有一些您可以设想而不涉及所有委托麻烦的工作...
- (IBAction)dismiss:(id)sender {
[[self presentingViewController] dismissViewControllerAnimated:YES
completion:^{
[self.presentingViewController performSelector:@selector(presentVC2:)
withObject:nil];
}];
}
在请求当前控制器将我们dismiss后,我们有一个完成块,在其中调用presentingViewController中的方法以调用VC2。不需要委托。(块的一个重要卖点是它们减少了在这些情况下需要委托的需求)。然而在这种情况下,有一些障碍...
- 在VC1中,您不知道mainVC是否实现了present2方法 - 您可能会遇到难以调试的错误或崩溃。使用委托可以帮助您避免这种情况。
- 一旦VC1被dismiss,它就不再存在来执行完成块...还是存在?您不知道(我也不知道)...但是使用委托,您就不必担心这个问题。
- 当我尝试运行此方法时,它只是无声地挂起,没有警告或错误。
所以...请花时间学习委托!
update2
在您的评论中,您已成功通过在VC2的dismiss按钮处理程序中使用此方法使其工作:
[self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil]
这种方法确实更加简单,但它会带来许多问题。
紧密耦合
您正在将 viewController 结构硬编码在一起。例如,如果您要在 mainVC 之前插入一个新的 viewController,则必需的行为将会失效(您将导航到上一个 viewController)。在 VC1 中,您还必须 #import VC2。因此,您有相当多的相互依赖关系,这打破了 OOP/MVC 的目标。
使用代理,VC1 和 VC2 都不需要知道任何关于 mainVC 或其祖先的信息,因此我们保持所有内容松耦合和模块化。
内存
VC1 没有消失,您仍然持有指向它的两个指针:
- mainVC 的
presentedViewController
属性
- VC2 的
presentingViewController
属性
您可以通过日志记录来测试此方法,也可以直接从 VC2 中执行此操作。
[self dismissViewControllerAnimated:YES completion:nil]
它仍然可以工作,仍然可以将您带回VC1。
对我来说,这似乎是内存泄漏。
这里的警告信息提示了这一点:
[self presentViewController:vc2 animated:YES completion:nil];
[self dismissViewControllerAnimated:YES completion:nil];
// Attempt to dismiss from view controller <VC1: 0x715e460>
// while a presentation or dismiss is in progress!
你试图忽略VC1,而VC2是被呈现的VC,这个逻辑是有问题的。第二条信息并没有真正执行 - 或许会发生一些事情,但你仍然会得到两个指向你认为已经摆脱的对象的指针。(编辑 - 我检查过了,情况并不那么糟糕,当你回到mainVC时两个对象都会消失)
换句话说,请使用代理(delegates)。如果有帮助的话,我在这里提供了该模式的简要描述:
Is passing a controller in a construtor always a bad practice?
更新3
如果你真的想避免使用代理,这可能是最好的方法:
在VC1中:
[self presentViewController:VC2
animated:YES
completion:nil]
不过,不要轻易排除任何事情……因为我们已经确定,那根本就不会发生。
在VC2中:
[self.presentingViewController.presentingViewController
dismissViewControllerAnimated:YES
completion:nil]
如我们所知,我们并没有解除 VC1,因此我们可以通过 VC1 返回到 MainVC。MainVC 解除 VC1。由于 VC1 已经去了,那么与其一起存在的 VC2 也被呈现出来,因此您回到了干净状态的 MainVC。
虽然这仍然高度耦合,因为 VC1 需要知道 VC2 的情况,而 VC2 需要知道它是通过 MainVC-> VC1 到达的,但这是您在没有显式委托的情况下能够得到的最好结果。