模态视图控制器 - 如何显示和关闭 [英] modal View controllers - how to display and dismiss

查看:172
本文介绍了模态视图控制器 - 如何显示和关闭的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

关于如何通过显示和解除多个视图控制器解决问题,我在最后一周打破了我的脑袋。我创建了一个示例项目并直接从项目中粘贴代码。我有3个视图控制器及其相应的.xib文件。 MainViewController,VC1和VC2。我在主视图控制器上有两个按钮。

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.

- (IBAction)VC1Pressed:(UIButton *)sender
{
    VC1 *vc1 = [[VC1 alloc] initWithNibName:@"VC1" bundle:nil];
    [vc1 setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
    [self presentViewController:vc1 animated:YES completion:nil];
}

这会打开VC1,没有任何问题。在VC1中,我有另一个按钮,应该打开VC2,同时解雇VC1。

This opens VC1 with no issues. In VC1, I have another button that should open VC2 while at the same time dismiss 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. 

我希望它回到主视图控制器,同时VC1应该从记忆力好。当我点击主控制器上的VC1按钮时,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显示VC2并且当在VC2上单击按钮时应该返回主控制器。没有长时间运行的代码,循环或任何计时器。只是裸骨调用来查看控制器。

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.

推荐答案

这一行:

[self dismissViewControllerAnimated:YES completion:nil];

没有向自己发送消息,它实际上正在向其呈现的VC发送消息,询问它解雇。当您呈现VC时,您将在呈现VC和呈现的VC之间创建关系。所以你不应该在它呈现时销毁呈现的VC(所呈现的VC不能发回该消息的消息......)。由于您没有真正考虑到它,您将使应用程序处于混乱状态。请参阅我的回答解雇呈现的视图控制器
其中我建议这个方法写得更清楚:

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 Controller in which I recommend this method is more clearly written:

[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];

在您的情况下,您需要确保所有控制都在 mainVC 。您应该使用委托从ViewController1将正确的消息发送回MainViewController,以便mainVC可以解除VC1然后呈现VC2。

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.

VC2 VC1在@interface上方的.h文件中添加一个协议:

In VC2 VC1 add a protocol in your .h file above the @interface:

@protocol ViewController1Protocol <NSObject>

    - (void)dismissAndPresentVC2;

@end

并在@interface的同一文件中降低section声明一个属性来保存委托指针:

and lower down in the same file in the @interface section declare a property to hold the delegate pointer:

@property (nonatomic,weak) id <ViewController1Protocol> delegate;

在VC1 .m文件中,dismiss按钮方法应调用委托方法

In the VC1 .m file, the dismiss button method should call the delegate method

- (IBAction)buttonPressedFromVC1:(UIButton *)sender {
    [self.delegate dissmissAndPresentVC2]
}

现在在mainVC中,在创建VC1时将其设置为VC1的委托:

Now in mainVC, set it as VC1's delegate when creating 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: can与 VC2Pressed:按钮IBAction方法的方法相同。请注意,从完成块调用它以确保在VC1完全关闭之前不会显示VC2。

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.

您现在正在从VC1-> VCMain-> VC2移动,因此您可能只希望其中一个转换被设置为动画。

You are now moving from VC1->VCMain->VC2 so you will probably want only one of the transitions to be animated.

更新

在您的评论中,您对实现所需的复杂性表示惊讶看似简单的事情。我向你保证,这个委托模式对于Objective-C和Cocoa的大部分都是如此重要,而这个例子是关于你能得到的最简单的,你真的应该努力使它变得舒服。

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.

在Apple的 View Controller编程指南中,他们有这就是说

In Apple's View Controller Programming Guide they have this to say:


解雇呈现的视图控制器

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

如果您真的想过要实现的目标,你将如何实现它,你会发现,如果你不想使用NavigationController,那么传递你的MainViewController以完成所有工作是唯一合乎逻辑的方法。如果你使用NavController,实际上即使没有明确地,你也要委托navController完成所有的工作。需要有一些一些对象来保存VC导航内容的中心轨迹,无论你做什么,都需要一些与之通信的方法。

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 do use a NavController, in effect you are 'delegating', even if not explicitly, to the navController to do all of the work. There needs to be some object that keeps a central track of what's going on with your VC navigation, and you need some method of communicating with it, whatever you do.

在实践中,Apple的建议有点极端......在正常情况下,您不需要制作专门的委托和方法,您可以依赖 [self presentingViewController] dismissViewControllerAnimated: - 在你喜欢的情况下,你希望你的解雇对你需要注意的远程对象产生其他影响。

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.

在没有所有委托麻烦的情况下,你可以想象工作......

Here is something you could imagine to work without all the delegate hassle...

- (IBAction)dismiss:(id)sender {
    [[self presentingViewController] dismissViewControllerAnimated:YES 
                                                        completion:^{
        [self.presentingViewController performSelector:@selector(presentVC2:) 
                                            withObject:nil];
    }];

}

在要求提交控制人员解雇我们之后,我们有一个完成块,它调用presentsViewController中的方法来调用VC2。不需要代表。 (块的一大卖点是它们在这些情况下减少了对代表的需求)。然而,在这种情况下,有一些事情妨碍...

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


  • 在VC1中你没有知道 mainVC实现方法 present2 - 您最终可能会遇到难以调试的错误或崩溃。代表们可以帮助您避免这种情况。

  • 一旦VC1被解雇,它就不是真的要执行完成块...或者是它? self.presentingViewController是否意味着什么?你不知道(我也不知道)......与代表一起,你没有这种不确定性。

  • 当我尝试运行此方法时,它只是挂起而没有任何警告或错误。

  • in VC1 you don't know that 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.

所以请...花点时间学习授权!

So please... take the time to learn delegation!

update2

在您的评论中,您已经设法通过在VC2的解雇按钮处理程序中使用它来使其工作:

In your comment you have managed to make it work by using this in VC2's dismiss button handler:

 [self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil]; 

这当然要简单得多,但它会给你留下一些问题。

This is certainly much simpler, but it leaves you with a number of issues.

紧耦合

您将viewController结构连接在一起。例如,如果要在mainVC之前插入新的viewController,则所需的行为将中断(您将导航到前一个)。在VC1中你还必须#import VC2。因此,您有很多相互依赖关系,这会破坏OOP / MVC目标。

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.

使用代表,VC1和VC2都不需要了解有关mainVC或它的前提的任何内容,因此我们保持所有松散耦合和模块化。

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还没有消失,你还有两个指针:

Memory
VC1 has not gone away, you still hold two pointers to it:


  • mainVC的 presentsViewController property

  • VC2的 presentsViewController 属性

  • mainVC's presentedViewController property
  • VC2's presentingViewController property

您可以通过记录进行测试,也可以通过VC2进行测试

You can test this by logging, and also just by doing this from VC2

[self dismissViewControllerAnimated:YES completion:nil]; 

它仍然有效,仍会让你回到VC1。

It still works, still gets you back to 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!

逻辑分解,因为您试图解除显示的VC VC2是提供的VC。第二条消息并没有真正执行 - 好吧也许会发生一些事情,但你仍然留下两个指向你认为已经摆脱的对象的指针。 (编辑 - 我已经检查了这个并没有那么糟糕,当你回到mainVC时,两个对象都会消失

The logic breaks down, as you are attempting to dismiss the presenting VC of which VC2 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)

这是一个相当啰嗦的说法 - 请使用代表。如果有帮助,我在这里对模式做了另一个简短描述:

在一个construtor中传递一个控制器总是一个坏习惯?

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?

更新3

如果你真的想避开代表,这可能是最好的出路:

update 3
If you really want to avoid delegates, this could be the best way out:

在VC1中:

[self presentViewController:VC2
                   animated:YES
                 completion:nil];

但是解雇任何东西......正如我们确定的那样,它无论如何都不会发生。

But don't dismiss anything... as we ascertained, it doesn't really happen anyway.

在VC2中:

[self.presentingViewController.presentingViewController 
    dismissViewControllerAnimated:YES
                       completion:nil];

我们(知道)我们没有解雇VC1,我们可以通过<回到 / em> VC1 MainVC。 MainVC驳回VC1。因为VC1已经消失了,所以VC2随之而来,所以你回到了MainVC的干净状态。

As we (know) we haven't dismissed VC1, we can reach back through VC1 to MainVC. 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需要了解VC2 ,VC2需要知道它是通过MainVC-> VC1到达的,但是如果没有一点明确的授权,它就是你最好的。

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.

这篇关于模态视图控制器 - 如何显示和关闭的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆