模态视图控制器 - 如何显示和关闭

Hem*_*ema 81 objective-c uiviewcontroller ios presentviewcontroller dismissviewcontroller

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

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

这打开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. 
Run Code Online (Sandbox Code Playgroud)

我希望它回到主视图控制器,同时VC1应该已经从内存中删除了.只有当我点击主控制器上的VC1按钮时才会显示VC1.

主视图控制器上的另一个按钮也应该能够直接绕过VC1显示VC2,并且当在VC2上单击按钮时应该返回到主控制器.没有长时间运行的代码,循环或任何计时器.只是裸骨调用来查看控制器.

fou*_*dry 188

这一行:

[self dismissViewControllerAnimated:YES completion:nil];
Run Code Online (Sandbox Code Playgroud)

它不是向自己发送消息,它实际上是向其呈现的VC发送消息,要求它进行解雇.当您呈现VC时,您将在呈现VC和呈现的VC之间创建关系.因此,您不应该在呈现时销毁呈现的VC(呈现的VC不能发送该消除消息...).由于您没有真正考虑到它,您将使应用程序处于混乱状态.请参阅我的回答解雇一个呈现的视图控制器 ,我建议使用此方法更清晰:

[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
Run Code Online (Sandbox Code Playgroud)

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

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

@protocol ViewController1Protocol <NSObject>

    - (void)dismissAndPresentVC2;

@end
Run Code Online (Sandbox Code Playgroud)

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

@property (nonatomic,weak) id <ViewController1Protocol> delegate;
Run Code Online (Sandbox Code Playgroud)

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

- (IBAction)buttonPressedFromVC1:(UIButton *)sender {
    [self.delegate dissmissAndPresentVC2]
}
Run Code Online (Sandbox Code Playgroud)

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

- (IBAction)present1:(id)sender {
    ViewController1* vc = [[ViewController1 alloc] initWithNibName:@"ViewController1" bundle:nil];
    vc.delegate = self;
    [self present:vc];
}
Run Code Online (Sandbox Code Playgroud)

并实现委托方法:

- (void)dismissAndPresent2 {
    [self dismissViewControllerAnimated:NO completion:^{
        [self present2:nil];
    }];
}
Run Code Online (Sandbox Code Playgroud)

present2:可以与VC2Pressed:按钮IBAction方法相同的方法.请注意,从完成块调用它以确保在VC1完全关闭之前不会显示VC2.

您现在正在从VC1-> VCMain-> VC2移动,因此您可能只需要对其中一个过渡进行动画处理.

更新

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

在Apple的View Controller Programming Guide中,他们有这样的说法:

解雇呈现的视图控制器

当需要关闭呈现的视图控制器时,首选方法是让呈现视图控制器关闭它.换句话说,只要有可能,呈现视图控制器的同一视图控制器也应负责解除它.尽管有几种技术用于通知呈现视图控制器应该解除其呈现的视图控制器,但是优选的技术是委托.有关更多信息,请参阅"使用委派与其他控制器通信".

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

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

这是你可以想象的工作,没有所有的代表麻烦......

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

}
Run Code Online (Sandbox Code Playgroud)

在要求呈现控制器解雇我们之后,我们有一个完成块,它调用presentationViewController中的方法来调用VC2.不需要代表.(块的一大卖点是它们在这些情况下减少了对代表的需求).但是在这种情况下,有一些事情会妨碍......

  • 在VC1中,您不知道 mainVC实现了该方法present2- 您最终可能会遇到难以调试的错误或崩溃.代表们可以帮助您避免这种情况.
  • 一旦VC1被解雇,它就不是真的要执行完成块......或者是它?self.presentingViewController是否意味着什么?你不知道(我也不知道)......与代表一起,你没有这种不确定性.
  • 当我尝试运行此方法时,它只是挂起而没有警告或错误.

所以请...花点时间学习代表团!

UPDATE2

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

 [self.view.window.rootViewController dismissViewControllerAnimated:YES completion:nil]; 
Run Code Online (Sandbox Code Playgroud)

这当然要简单得多,但它会给你带来许多问题.

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

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

内存
VC1还没有消失,你仍然有两个指针:

  • mainVC的presentedViewController财产
  • VC2的presentingViewController财产

您可以通过记录来测试,也可以通过VC2执行此操作

[self dismissViewControllerAnimated:YES completion:nil]; 
Run Code Online (Sandbox Code Playgroud)

它仍然有效,仍然让你回到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!
Run Code Online (Sandbox Code Playgroud)

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

这是一种相当冗长的说法 - 请使用代表.如果有帮助的话,我在这里对模式做了另一个简短的描述:
在一个construtor中传递一个控制器总是一个坏习惯吗?

更新3
如果你真的想避免代表,这可能是最好的出路:

在VC1中:

[self presentViewController:VC2
                   animated:YES
                 completion:nil];
Run Code Online (Sandbox Code Playgroud)

但是不要忽视任何事情......正如我们所确定的那样,它无论如何都不会发生.

在VC2中:

[self.presentingViewController.presentingViewController 
    dismissViewControllerAnimated:YES
                       completion:nil];
Run Code Online (Sandbox Code Playgroud)

由于我们(知道)我们没有解散VC1,我们可以通过 VC1 回到 MainVC.MainVC驳回VC1.因为VC1已经消失了,所以VC2随之而来,所以你回到了MainVC的干净状态.

它仍然是高度耦合的,因为VC1需要知道VC2,VC2需要知道它是通过MainVC-> VC1到达的,但它是你没有一点显式委托就能得到的最好的.


Rad*_*scu 10

我认为你误解了iOS模态视图控制器的一些核心概念.当您关闭VC1时,VC1的任何呈现的视图控制器也会被关闭.Apple打算让模态视图控制器以堆叠方式流动 - 在您的情况下,VC2由VC1呈现.一旦从VC1中呈现VC2,就会解雇VC1,这样就完全搞乱了.为了达到你想要的效果,在VC1解散自己之后,buttonPressedFromVC1应该让mainVC立即呈现VC2.我认为这可以在没有代表的情况下实现.一些事情:

UIViewController presentingVC = [self presentingViewController];
[self dismissViewControllerAnimated:YES completion:
 ^{
    [presentingVC presentViewController:vc2 animated:YES completion:nil];
 }];
Run Code Online (Sandbox Code Playgroud)

请注意,self.presentingViewController存储在其他一些变量中,因为在vc1解除自身之后,您不应该对它进行任何引用.


Kin*_*ard 10

Swift中的示例,描述了代工厂上面的解释和Apple的文档:

  1. 基于Apple的文档和代工厂上面的解释(纠正一些错误),使用委托设计模式的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)
        })
    }

}
Run Code Online (Sandbox Code Playgroud)

ViewController1.swift

import UIKit

class ViewController1: UIViewController {

    var delegate: protocol<ViewControllerProtocol>!

    @IBAction func goToViewController2(sender: UIButton) {
        self.delegate.dismissViewController1AndPresentViewController2()
    }

}
Run Code Online (Sandbox Code Playgroud)

ViewController2.swift

import UIKit

class ViewController2: UIViewController {

}
Run Code Online (Sandbox Code Playgroud)
  1. 基于代工厂上面的解释(纠正一些错误),使用委托设计模式的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)
    }

}
Run Code Online (Sandbox Code Playgroud)

ViewController1.swift

import UIKit

class ViewController1: UIViewController {

    var delegate: protocol<ViewControllerProtocol>!

    @IBAction func goToViewController2(sender: UIButton) {
        self.delegate.popViewController1AndPushViewController2()
    }

}
Run Code Online (Sandbox Code Playgroud)

ViewController2.swift

import UIKit

class ViewController2: UIViewController {

}
Run Code Online (Sandbox Code Playgroud)


chr*_*sco 5

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)
    })
}
Run Code Online (Sandbox Code Playgroud)