ios 模态视图控制器 - 如何显示和关闭
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/14907518/
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
modal View controllers - how to display and dismiss
提问by Hema
I'm breaking my head for the last one week on how to solve the issue with showing and dismissing multiple view controllers. I have created a sample project and pasting the code directly from the project. I have 3 view controllers with their corresponding .xib files. MainViewController, VC1 and VC2. I have two buttons on the main view controller.
在过去的一周里,我一直在思考如何解决显示和关闭多个视图控制器的问题。我创建了一个示例项目并直接从项目粘贴代码。我有 3 个带有相应 .xib 文件的视图控制器。主视图控制器、VC1 和 VC2。我在主视图控制器上有两个按钮。
- (IBAction)VC1Pressed:(UIButton *)sender
{
VC1 *vc1 = [[VC1 alloc] initWithNibName:@"VC1" bundle:nil];
[vc1 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[self presentViewController:vc1 animated:YES completion:nil];
}
This opens VC1 with no issues. In VC1, I have another button that should open VC2 while at the same time dismiss VC1.
这可以毫无问题地打开 VC1。在 VC1 中,我有另一个按钮应该打开 VC2,同时关闭 VC1。
- (IBAction)buttonPressedFromVC1:(UIButton *)sender
{
VC2 *vc2 = [[VC2 alloc] initWithNibName:@"VC2" bundle:nil];
[vc2 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[self presentViewController:vc2 animated:YES completion:nil];
[self dismissViewControllerAnimated:YES completion:nil];
} // This shows a warning: Attempt to dismiss from view controller <VC1: 0x715e460> while a presentation or dismiss is in progress!
- (IBAction)buttonPressedFromVC2:(UIButton *)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
} // This is going back to VC1.
I want it go back to the main view controller while at the same time VC1 should have been removed from memory for good. VC1 should only show up when I click on the VC1 button on the main controller.
我希望它返回到主视图控制器,同时 VC1 应该从内存中永久删除。VC1 应该只在我点击主控制器上的 VC1 按钮时出现。
The other button on the Main view controller should also be able to display VC2 directly bypassing VC1 and should come back to the main controller when a button is clicked on VC2. There is no long running code, loops or any timers. Just bare bone calls to view controllers.
主视图控制器上的另一个按钮也应该能够直接绕过 VC1 显示 VC2,并且在单击 VC2 上的按钮时应该返回到主控制器。没有长时间运行的代码、循环或任何计时器。只是简单地调用视图控制器。
回答by foundry
This line:
这一行:
[self dismissViewControllerAnimated:YES completion:nil];
isn't sending a message to itself, it's actually sending a message to its presenting VC, asking it to do the dismissing. When you present a VC, you create a relationship between the presenting VC and the presented one. So you should not destroy the presenting VC while it is presenting (the presented VC can't send that dismiss message back…). As you're not really taking account of it you are leaving the app in a confused state. See my answer Dismissing a Presented View Controllerin which I recommend this method is more clearly written:
不是向自己发送消息,它实际上是向其呈现的 VC 发送消息,要求它进行解雇。当你展示一个 VC 时,你在展示的 VC 和被展示的 VC 之间建立了一种关系。所以你不应该在呈现的时候销毁呈现的 VC(呈现的 VC 不能发回解雇消息......)。由于您没有真正考虑到这一点,因此您将应用程序置于混乱状态。请参阅我的回答Dismissing a Presented View Controller,其中我推荐的这种方法写得更清楚:
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
In your case, you need to ensure that all of the controlling is done in mainVC
. You should use a delegate to send the correct message back to MainViewController from ViewController1, so that mainVC can dismiss VC1 and then present VC2.
在您的情况下,您需要确保所有控制都在mainVC
. 您应该使用委托将正确的消息从 ViewController1 发送回 MainViewController,以便 mainVC 可以关闭 VC1,然后呈现 VC2。
In VC2VC1 add a protocol in your .h file above the @interface:
在VC2VC1 中,在 @interface 上方的 .h 文件中添加一个协议:
@protocol ViewController1Protocol <NSObject>
- (void)dismissAndPresentVC2;
@end
and lower down in the same file in the @interface section declare a property to hold the delegate pointer:
并在@interface 部分的同一个文件中向下声明一个属性来保存委托指针:
@property (nonatomic,weak) id <ViewController1Protocol> delegate;
In the VC1 .m file, the dismiss button method should call the delegate method
在VC1.m文件中,dismiss按钮方法应该调用delegate方法
- (IBAction)buttonPressedFromVC1:(UIButton *)sender {
[self.delegate dissmissAndPresentVC2]
}
Now in mainVC, set it as VC1's delegate when creating VC1:
现在在 mainVC 中,在创建 VC1 时将其设置为 VC1 的委托:
- (IBAction)present1:(id)sender {
ViewController1* vc = [[ViewController1 alloc] initWithNibName:@"ViewController1" bundle:nil];
vc.delegate = self;
[self present:vc];
}
and implement the delegate method:
并实现委托方法:
- (void)dismissAndPresent2 {
[self dismissViewControllerAnimated:NO completion:^{
[self present2:nil];
}];
}
present2:
can be the same method as your VC2Pressed:
button IBAction method. Note that it is called from the completion block to ensure that VC2 is not presented until VC1 is fully dismissed.
present2:
可以与您的VC2Pressed:
按钮 IBAction 方法相同。请注意,它是从完成块调用的,以确保在 VC1 完全解除之前不会出现 VC2。
You are now moving from VC1->VCMain->VC2 so you will probably want only one of the transitions to be animated.
您现在正在从 VC1->VCMain->VC2 移动,因此您可能只希望对其中一个过渡进行动画处理。
update
更新
In your comments you express surprise at the complexity required to achieve a seemingly simple thing. I assure you, this delegation pattern is so central to much of Objective-C and Cocoa, and this example is about the most simple you can get, that you really should make the effort to get comfortable with it.
在您的评论中,您对实现看似简单的事情所需的复杂性表示惊讶。我向你保证,这种委托模式是 Objective-C 和 Cocoa 的核心,这个例子是你能得到的最简单的例子,你真的应该努力适应它。
In Apple's View Controller Programming Guidethey have this to say:
在 Apple 的View Controller Programming Guide 中,他们这样说:
Dismissing a Presented View Controller
When it comes time to dismiss a presented view controller, the preferred approach is to let the presenting view controller dismiss it. In other words, whenever possible, the same view controller that presented the view controller should also take responsibility for dismissing it. Although there are several techniques for notifying the presenting view controller that its presented view controller should be dismissed, the preferred technique is delegation. For more information, see “Using Delegation to Communicate with Other Controllers.”
关闭呈现的视图控制器
当需要关闭呈现的视图控制器时,首选方法是让呈现的视图控制器关闭它。换句话说,只要有可能,呈现视图控制器的同一个视图控制器也应该负责解除它。尽管有多种技术可以通知呈现视图控制器其呈现的视图控制器应该被解除,但首选技术是委托。有关更多信息,请参阅“使用委派与其他控制器通信”。
If you really think through what you want to achieve, and how you are going about it, you will realise that messaging your MainViewController to do all of the work is the only logical way out given that you don't want to use a NavigationController. If you douse a NavController, in effect you are 'delegating', even if not explicitly, to the navController to do all of the work. There needs to be someobject that keeps a central track of what's going on with your VC navigation, and you need somemethod of communicating with it, whatever you do.
如果您真的仔细考虑了您想要实现的目标以及您将如何实现它,您将意识到在您不想使用 NavigationController 的情况下,向 MainViewController 发送消息以完成所有工作是唯一合乎逻辑的出路。如果你确实使用了 NavController,实际上你是在“委托”,即使没有明确地,给 navController 来完成所有的工作。需要有一些对象来保持对 VC 导航正在发生的事情的中央跟踪,并且无论您做什么,您都需要某种与它进行通信的方法。
In practice Apple's advice is a little extreme... in normal cases, you don't need to make a dedicated delegate and method, you can rely on [self presentingViewController] dismissViewControllerAnimated:
- it's when in cases like yours that you want your dismissing to have other effects on remote objects that you need to take care.
在实践中,Apple 的建议有点极端......在正常情况下,您不需要制作专用的委托和方法,您可以依靠[self presentingViewController] dismissViewControllerAnimated:
- 在像您这样的情况下,您希望您的解雇对远程产生其他影响你需要照顾的对象。
Here is something you could imagineto work without all the delegate hassle...
这是您可以想象在没有所有委托麻烦的情况下工作的东西......
- (IBAction)dismiss:(id)sender {
[[self presentingViewController] dismissViewControllerAnimated:YES
completion:^{
[self.presentingViewController performSelector:@selector(presentVC2:)
withObject:nil];
}];
}
After asking the presenting controller to dismiss us, we have a completion block which calls a method in the presentingViewController to invoke VC2. No delegate needed. (A big selling point of blocks is that they reduce the need for delegates in these circumstances). However in this case there are a few things getting in the way...
在要求呈现控制器关闭我们之后,我们有一个完成块,它调用presentingViewController 中的一个方法来调用VC2。不需要代表。(块的一大卖点是它们在这些情况下减少了对代表的需求)。然而,在这种情况下,有一些事情阻碍了......
- in VC1 you don't knowthat mainVC implements the method
present2
- you can end up with difficult-to-debug errors or crashes. Delegates help you to avoid this. - once VC1 is dismissed, it's not really around to execute the completion block... or is it? Does self.presentingViewController mean anything any more? You don't know (neither do I)... with a delegate, you don't have this uncertainty.
- When I try to run this method, it just hangs with no warning or errors.
- 在 VC1 中,您不知道mainVC 实现了该方法
present2
- 您最终可能会遇到难以调试的错误或崩溃。代表可以帮助您避免这种情况。 - 一旦 VC1 被解散,它就不是真的要执行完成块了……或者是吗?self.presentingViewController 还有什么意义吗?你不知道(我也不知道)……有了代表,你就没有这种不确定性。
- 当我尝试运行此方法时,它只是挂起,没有警告或错误。
So please... take the time to learn delegation!
所以请...花时间学习委托!
update2
更新2
In your comment you have managed to make it work by using this in VC2's dismiss button handler:
在您的评论中,您已成功通过在 VC2 的关闭按钮处理程序中使用它来使其工作:
[self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
This is certainly much simpler, but it leaves you with a number of issues.
这当然要简单得多,但它会给您带来许多问题。
Tight coupling
You are hard-wiring your viewController structure together. For example, if you were to insert a new viewController before mainVC, your required behaviour would break (you would navigate to the prior one). In VC1 you have also had to #import VC2. Therefore you have quite a lot of inter-dependencies, which breaks OOP/MVC objectives.
紧耦合
您正在将您的 viewController 结构硬连接在一起。例如,如果您要在 mainVC 之前插入一个新的 viewController,您所需的行为就会中断(您将导航到前一个)。在 VC1 中,您还必须#import VC2。因此,您有很多相互依赖关系,这打破了 OOP/MVC 目标。
Using delegates, neither VC1 nor VC2 need to know anything about mainVC or it's antecedents so we keep everything loosely-coupled and modular.
使用委托,VC1 和 VC2 都不需要了解有关 mainVC 或其前因的任何信息,因此我们将所有内容保持松散耦合和模块化。
Memory
VC1 has not gone away, you still hold two pointers to it:
内存
VC1并没有消失,你仍然持有两个指向它的指针:
- mainVC's
presentedViewController
property - VC2's
presentingViewController
property
- mainVC 的
presentedViewController
属性 - VC2的
presentingViewController
属性
You can test this by logging, and also just by doing this from VC2
您可以通过记录来测试这一点,也可以通过 VC2 进行测试
[self dismissViewControllerAnimated:YES completion:nil];
It still works, still gets you back to VC1.
它仍然有效,仍然可以让您回到 VC1。
That seems to me like a memory leak.
在我看来,这就像内存泄漏。
The clue to this is in the warning you are getting here:
对此的线索在于您在此处收到的警告:
[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!
The logic breaks down, as you are attempting to dismiss the presenting VC of whichVC2 is the presented VC. The second message doesn't really get executed - well perhaps some stuff happens, but you are still left with two pointers to an object you thought you had got rid of. (edit - I've checked this and it's not so bad, both objects do go away when you get back to mainVC)
逻辑崩溃了,因为您试图消除其中VC2 是呈现的 VC 的呈现 VC。第二条消息并没有真正被执行——好吧,也许发生了一些事情,但你仍然有两个指向你认为已经摆脱的对象的指针。(编辑 - 我已经检查过这个,还不错,当你回到 mainVC 时,两个对象都会消失)
That's a rather long-winded way of saying - please, use delegates. If it helps, I made another brief description of the pattern here:
Is passing a controller in a construtor always a bad practice?
这是一种相当冗长的说法 - 请使用委托。如果有帮助,我在这里对模式进行了另一次简要描述:
在构造函数中传递控制器总是不好的做法吗?
update 3
If you really want to avoid delegates, this could be the best way out:
更新 3
如果你真的想避免委托,这可能是最好的出路:
In VC1:
在 VC1 中:
[self presentViewController:VC2
animated:YES
completion:nil];
But don'tdismiss anything... as we ascertained, it doesn't really happen anyway.
但是不要忽视任何事情......正如我们所确定的那样,无论如何它并没有真正发生。
In VC2:
在 VC2 中:
[self.presentingViewController.presentingViewController
dismissViewControllerAnimated:YES
completion:nil];
As we (know) we haven't dismissed VC1, we can reach back throughVC1 toMainVC. MainVC dismisses VC1. Because VC1 has gone, it's presented VC2 goes with it, so you are back at MainVC in a clean state.
正如我们(知道)我们还没有关闭 VC1,我们可以通过VC1返回到MainVC。MainVC 驳回了 VC1。因为 VC1 已经消失,所以它呈现 VC2 随之而来,所以您以干净的状态回到 MainVC。
It's still highly coupled, as VC1 needs to know about VC2, and VC2 needs to know that it was arrived at via MainVC->VC1, but it's the best you're going to get without a bit of explicit delegation.
它仍然是高度耦合的,因为 VC1 需要知道 VC2,而 VC2 需要知道它是通过 MainVC->VC1 到达的,但这是最好的,无需一些显式委派。
回答by King-Wizard
Example in Swift, picturing the foundry's explanation above and the Apple's documentation:
Swift 中的示例,描绘了上述代工厂的解释和 Apple 的文档:
- Basing on the Apple's documentationand the foundry's explanation above (correcting some errors), presentViewController version using delegate design pattern:
- 根据Apple 的文档和上述代工厂的解释(更正一些错误),使用委托设计模式的presentViewController 版本:
ViewController.swift
视图控制器.swift
import UIKit
protocol ViewControllerProtocol {
func dismissViewController1AndPresentViewController2()
}
class ViewController: UIViewController, ViewControllerProtocol {
@IBAction func goToViewController1BtnPressed(sender: UIButton) {
let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1
vc1.delegate = self
vc1.modalTransitionStyle = UIModalTransitionStyle.FlipHorizontal
self.presentViewController(vc1, animated: true, completion: nil)
}
func dismissViewController1AndPresentViewController2() {
self.dismissViewControllerAnimated(false, completion: { () -> Void in
let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2
self.presentViewController(vc2, animated: true, completion: nil)
})
}
}
ViewController1.swift
ViewController1.swift
import UIKit
class ViewController1: UIViewController {
var delegate: protocol<ViewControllerProtocol>!
@IBAction func goToViewController2(sender: UIButton) {
self.delegate.dismissViewController1AndPresentViewController2()
}
}
ViewController2.swift
ViewController2.swift
import UIKit
class ViewController2: UIViewController {
}
- Basing on the foundry's explanation above (correcting some errors), pushViewController version using delegate design pattern:
- 基于代工厂上面的解释(更正一些错误),pushViewController 版本使用委托设计模式:
ViewController.swift
视图控制器.swift
import UIKit
protocol ViewControllerProtocol {
func popViewController1AndPushViewController2()
}
class ViewController: UIViewController, ViewControllerProtocol {
@IBAction func goToViewController1BtnPressed(sender: UIButton) {
let vc1: ViewController1 = self.storyboard?.instantiateViewControllerWithIdentifier("VC1") as ViewController1
vc1.delegate = self
self.navigationController?.pushViewController(vc1, animated: true)
}
func popViewController1AndPushViewController2() {
self.navigationController?.popViewControllerAnimated(false)
let vc2: ViewController2 = self.storyboard?.instantiateViewControllerWithIdentifier("VC2") as ViewController2
self.navigationController?.pushViewController(vc2, animated: true)
}
}
ViewController1.swift
ViewController1.swift
import UIKit
class ViewController1: UIViewController {
var delegate: protocol<ViewControllerProtocol>!
@IBAction func goToViewController2(sender: UIButton) {
self.delegate.popViewController1AndPushViewController2()
}
}
ViewController2.swift
ViewController2.swift
import UIKit
class ViewController2: UIViewController {
}
回答by Radu Simionescu
I think you misunderstood some core concepts about iOS modal view controllers. When you dismiss VC1, any presented view controllers by VC1 are dismissed as well. Apple intended for modal view controllers to flow in a stacked manner - in your case VC2 is presented by VC1. You are dismissing VC1 as soon as you present VC2 from VC1 so it is a total mess. To achieve what you want, buttonPressedFromVC1 should have the mainVC present VC2 immediately after VC1 dismisses itself. And I think this can be achieved without delegates. Something along the lines:
我认为您误解了有关 iOS 模态视图控制器的一些核心概念。当您关闭 VC1 时,任何由 VC1 呈现的视图控制器也将被关闭。Apple 旨在让模态视图控制器以堆叠方式流动 - 在您的情况下,VC2 由 VC1 呈现。一旦您从 VC1 呈现 VC2,您就会解雇 VC1,因此它完全是一团糟。为了实现你想要的,buttonPressedFromVC1 应该让 mainVC 在 VC1 解散后立即呈现 VC2。我认为这可以在没有代表的情况下实现。沿线的东西:
UIViewController presentingVC = [self presentingViewController];
[self dismissViewControllerAnimated:YES completion:
^{
[presentingVC presentViewController:vc2 animated:YES completion:nil];
}];
Note that self.presentingViewController is stored in some other variable, because after vc1 dismisses itself, you shouldn't make any references to it.
请注意, self.presentingViewController 存储在其他一些变量中,因为在 vc1 自行解除后,您不应对其进行任何引用。
回答by chrisco
Radu Simionescu - awesome work! and below Your solution for Swift lovers:
Radu Simionescu - 很棒的工作!及以下为 Swift 爱好者提供的解决方案:
@IBAction func showSecondControlerAndCloseCurrentOne(sender: UIButton) {
let secondViewController = storyboard?.instantiateViewControllerWithIdentifier("ConrollerStoryboardID") as UIViewControllerClass // change it as You need it
var presentingVC = self.presentingViewController
self.dismissViewControllerAnimated(false, completion: { () -> Void in
presentingVC!.presentViewController(secondViewController, animated: true, completion: nil)
})
}
回答by Mer
I wanted this:
我想要这个:
MapVC is a Map in full screen.
MapVC 是全屏地图。
When I press a button, it opens PopupVC (not in full screen) above the map.
当我按下按钮时,它会在地图上方打开 PopupVC(非全屏)。
When I press a button in PopupVC, it returns to MapVC, and then I want to execute viewDidAppear.
当我在 PopupVC 中按下按钮时,它返回到 MapVC,然后我想执行 viewDidAppear。
I did this:
我这样做了:
MapVC.m: in the button action, a segue programmatically, and set delegate
MapVC.m:在按钮动作中,以编程方式进行转场,并设置委托
- (void) buttonMapAction{
PopupVC *popvc = [self.storyboard instantiateViewControllerWithIdentifier:@"popup"];
popvc.delegate = self;
[self presentViewController:popvc animated:YES completion:nil];
}
- (void)dismissAndPresentMap {
[self dismissViewControllerAnimated:NO completion:^{
NSLog(@"dismissAndPresentMap");
//When returns of the other view I call viewDidAppear but you can call to other functions
[self viewDidAppear:YES];
}];
}
PopupVC.h: before @interface, add the protocol
PopupVC.h:在@interface之前,添加协议
@protocol PopupVCProtocol <NSObject>
- (void)dismissAndPresentMap;
@end
after @interface, a new property
在@interface 之后,一个新属性
@property (nonatomic,weak) id <PopupVCProtocol> delegate;
PopupVC.m:
PopupVC.m:
- (void) buttonPopupAction{
//jump to dismissAndPresentMap on Map view
[self.delegate dismissAndPresentMap];
}
回答by Duong Ngo
I have solved the issue by using UINavigationController when presenting. In MainVC, when presenting VC1
我在演示时使用 UINavigationController 解决了这个问题。在 MainVC 中,当呈现 VC1 时
let vc1 = VC1()
let navigationVC = UINavigationController(rootViewController: vc1)
self.present(navigationVC, animated: true, completion: nil)
In VC1, when I would like to show VC2 and dismiss VC1 in same time (just one animation), I can have a push animation by
在 VC1 中,当我想同时显示 VC2 和关闭 VC1(只有一个动画)时,我可以通过
let vc2 = VC2()
self.navigationController?.setViewControllers([vc2], animated: true)
And in VC2, when close the view controller, as usual we can use:
在 VC2 中,当关闭视图控制器时,我们可以像往常一样使用:
self.dismiss(animated: true, completion: nil)