模态视图控制器 – 如何显示和解除
在最后一个星期,我打破了如何解决显示和解散多个视图控制器的问题。 我已经创build了一个示例项目,并直接从项目中粘贴代码。 我有3个视图控制器与他们相应的.xib文件。 MainViewController,VC1和VC2。 我在主视图控制器上有两个button。
- (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的button,同时closuresVC1。
- (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应该已经从内存中删除好。 当我点击主控制器上的VC1button时,VC1应该只显示出来。
主视图控制器上的另一个button也应该能够直接绕过VC1显示VC2,并且在VC2上单击一个button时应该回到主控制器。 没有长时间运行的代码,循环或任何定时器。 只需裸骨调用来查看控制器。
这一行:
[self dismissViewControllerAnimated:YES completion:nil];
没有给自己发送消息,实际上是向其提交的VC发送一条消息,要求它解雇。 当你介绍一个风险投资,你在风险投资和提出的风险之间build立关系。 所以你不应该在提交的时候销毁提交的VC(提交的VC不能发送这个解雇消息回来…)。 由于您没有真正考虑到这一点,所以您将以困惑的状态离开应用程序。 查看我的答案closures一个提出的视图控制器 ,我推荐这个方法写得更清楚:
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
在你的情况下,你需要确保所有的控制在mainVC
完成。 您应该使用委托将正确的消息从ViewController1发送回MainViewController,以便mainVC可以closuresVC1,然后呈现VC2。
在VC2 VC1中,在@interface上面的.h文件中添加一个协议:
@protocol ViewController1Protocol <NSObject> - (void)dismissAndPresentVC2; @end
并在@interface部分的同一个文件中声明一个属性来保存委托指针:
@property (nonatomic,weak) id <ViewController1Protocol> delegate;
在VC1 .m文件中,解除button方法应该调用委托方法
- (IBAction)buttonPressedFromVC1:(UIButton *)sender { [self.delegate dissmissAndPresentVC2] }
现在在mainVC中,创buildVC1时将其设置为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:
button的IBAction方法一样。 请注意,它是从完成块中调用的,以确保VC2不会出现,直到VC1完全解除为止。
您现在正在从VC1-> VCMain-> VC2移动,因此您可能只希望其中一个过渡animation。
更新
在你的评论中,你expression了对实现一个看似简单的事情所需要的复杂性的惊讶。 我向你保证,这个委托模式对于Objective-C和Cocoa来说非常重要,这个例子是关于最简单的你可以得到的,你应该努力去适应它。
在苹果的View Controller编程指南中,他们有这样的说法 :
拒绝提出的视图控制器
当需要closures一个呈现的视图控制器时,首选的方法是让呈现视图控制器closures它。 换句话说,只要有可能,呈现视图控制器的视图控制器也应该负责解除视图控制器。 虽然有几种技术用于通知呈现视图控制器它的呈现的视图控制器应该被解散,但是优选的技术是委派。 有关更多信息,请参阅“使用委派与其他控制器通信”。
如果你真的想通过你想要达到的目标,以及你如何去做,你会发现给你的MainViewController做消息是完成所有工作的唯一合理的方法,因为你不想使用NavigationController。 如果你确实使用了NavController,那么实际上你是在委托navController来做所有的工作。 需要有一些对象来跟踪你的VC导航正在发生什么,并且你需要一些与之通信的方法,无论你做什么。
在实践中,苹果的build议是有点极端的…在正常情况下,你不需要做一个专门的委托和方法,你可以依靠[self presentingViewController] dismissViewControllerAnimated:
– 在像你这样的情况下,你想解雇对远程对象有其他的影响,你需要注意。
这是你可以想象的工作,没有所有的代表麻烦…
- (IBAction)dismiss:(id)sender { [[self presentingViewController] dismissViewControllerAnimated:YES completion:^{ [self.presentingViewController performSelector:@selector(presentVC2:) withObject:nil]; }]; }
在请求呈现控制器closures我们之后,我们有一个完成块,在presentationViewController中调用一个方法来调用VC2。 没有代表需要。 (一个很大的卖点就是在这种情况下减less了对代表的需求)。 但在这种情况下,有一些事情正在进行中…
- 在VC1中,你不知道 mainVC是否实现了
present2
方法 – 你最终可能会遇到难以debugging的错误或崩溃。 代表们帮助你避免这种情况。 - 一旦VC1被解雇,这不是真的要执行完成块…或者是它? self.presentingViewController是否意味着什么? 你不知道(我也不知道)……有一个代表,你没有这个不确定性。
- 当我尝试运行此方法时,它只是挂起,没有任何警告或错误。
所以请…花时间学习代表团!
UPDATE2
在你的评论中,你已经设法通过在VC2的解雇button处理程序中使用它:
[self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil];
这当然简单得多,但是会给你带来很多问题。
紧耦合
你很难将你的viewController结构连接在一起。 例如,如果你要在mainVC之前插入一个新的viewController,你需要的行为就会中断(你可以导航到前一个)。 在VC1中,你也必须#import VC2。 因此,你有相当多的相互依赖,这打破了OOP / MVC的目标。
使用委托,VC1和VC2都不需要知道任何关于mainVC或它的前因,所以我们保持一切松耦合和模块化。
记忆
VC1还没有消失,你仍然持有两个指针:
- mainVC
presentedViewController
ViewController属性 - VC2的
presentingViewController
属性
你可以通过日志来testing,也可以通过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!
逻辑崩溃,因为你正试图解除提交VC 的 VC2是提交的VC。 第二个消息并没有真正被执行 – 也许有些东西会发生,但是你仍然留下两个指向你认为已经被删除的对象的指针。 ( 编辑 – 我已经检查了这一点,并没有那么糟糕,当你回到mainVC时,两个对象都会消失 )
这是一个相当冗长的说法 – 请使用代表。 如果有帮助的话,我对这里的模式做了一个简单的描述:
是否通过一个控制器在一个construtor总是一个不好的做法?
更新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到达的,但这是最好的,你将得到没有一点明确的委托。
我想你误解了一些关于iOS模式视图控制器的核心概念。 当你closuresVC1时,任何由VC1提供的视图控制器也将被解散。 苹果公司打算让模态视图控制器以堆叠方式stream动 – 在你的情况下,VC2由VC1提供。 你从VC1出现VC2就立即解散了VC1,所以这是一团糟。 为了达到你想要的效果,buttonPressedFromVC1应该在VC1closures后立即拥有VCVC。 而且我认为这可以在没有代表的情况下实现。 沿线的东西:
UIViewController presentingVC = [self presentingViewController]; [self dismissViewControllerAnimated:YES completion: ^{ [presentingVC presentViewController:vc2 animated:YES completion:nil]; }];
请注意,self.presentingViewController存储在其他一些variables中,因为在vc1解散之后,您不应该对其进行任何引用。
在Swift中的例子,描述了上面代码的解释和Apple的文档:
- 根据苹果的文档和上面的代码解释(纠正一些错误),使用委托devise模式的presentViewController版本:
ViewController.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
import UIKit class ViewController1: UIViewController { var delegate: protocol<ViewControllerProtocol>! @IBAction func goToViewController2(sender: UIButton) { self.delegate.dismissViewController1AndPresentViewController2() } }
ViewController2.swift
import UIKit class ViewController2: UIViewController { }
- 根据上面代码的解释(纠正一些错误),使用委托devise模式的pushViewController版本:
ViewController.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
import UIKit class ViewController1: UIViewController { var delegate: protocol<ViewControllerProtocol>! @IBAction func goToViewController2(sender: UIButton) { self.delegate.popViewController1AndPushViewController2() } }
ViewController2.swift
import UIKit class ViewController2: UIViewController { }
拉杜Simionescu – 令人敬畏的工作! 和下面你的解决scheme为斯威夫特爱好者:
@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) }) }
我想这个:
MapVC是一个全屏的地图。
当我按下button时,它将在地图上方打开PopupVC(不是全屏幕)。
当我按下PopupVC中的一个button,它返回到MapVC,然后我想要执行viewDidAppear。
我做到了这一点:
MapVC.m:在button动作中,以编程方式进行切换,并设置委托
- (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:在@interface之前,添加协议
@protocol PopupVCProtocol <NSObject> - (void)dismissAndPresentMap; @end
@interface之后,一个新的属性
@property (nonatomic,weak) id <PopupVCProtocol> delegate;
PopupVC.m:
- (void) buttonPopupAction{ //jump to dismissAndPresentMap on Map view [self.delegate dismissAndPresentMap]; }
我已经通过使用UINavigationController来解决这个问题。 在MainVC中,当呈现VC1时
let vc1 = VC1() let navigationVC = UINavigationController(rootViewController: vc1) self.present(navigationVC, animated: true, completion: nil)
在VC1中,当我想在同一时间显示VC2并closuresVC1(只有一个animation)时,我可以通过
let vc2 = VC2() self.navigationController?.setViewControllers([vc2], animated: true)
而在VC2中,当closures视图控制器时,照常使用:
self.dismiss(animated: true, completion: nil)