首页 文章

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

提问于
浏览
79

关于如何通过显示和解除多个视图控制器来解决问题,我在最后一周打破了我的头脑 . 我创建了一个示例项目并直接从项目中粘贴代码 . 我有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];
}

这打开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.

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

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

6 回答

  • 185

    我想要这个:

    MapVC是一个全屏 Map .

    当我按下按钮时,它会在 Map 上方打开PopupVC(不是全屏) .

    当我按下PopupVC中的按钮时,它返回到MapVC,然后我想执行viewDidAppear .

    我这样做了:

    MapVC.m:在按钮操作中,以编程方式设置segue,并设置委托

    - (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];
    }
    
  • 9

    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)
        })
    }
    
  • 4

    我认为你误解了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];
     }];
    

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

  • 0

    我在演示时使用UINavigationController解决了这个问题 . 在MainVC中,当呈现VC1时

    let vc1 = VC1()
    let navigationVC = UINavigationController(rootViewController: vc1)
    self.present(navigationVC, animated: true, completion: nil)
    

    在VC1中,当我想同时显示VC2并解除VC1(只有一个动画)时,我可以通过

    let vc2 = VC2()
    self.navigationController?.setViewControllers([vc2], animated: true)
    

    而在VC2中,当关闭视图控制器时,像往常一样我们可以使用:

    self.dismiss(animated: true, completion: nil)
    
  • 0

    这一行:

    [self dismissViewControllerAnimated:YES completion:nil];
    

    isn 't sending a message to itself, it'实际上是向其呈现的VC发送消息,要求它进行解雇 . 当您呈现VC时,您将在呈现VC和呈现的VC之间创建关系 . 所以你不应该在它呈现的时候销毁呈现的VC(所呈现的VC可能并没有真正考虑到你在混淆状态下离开应用程序 . 请参阅我的回答Dismissing a Presented View Controller,其中我建议这种方法写得更清楚:

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

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

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

    @protocol ViewController1Protocol <NSObject>
    
        - (void)dismissAndPresentVC2;
    
    @end
    

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

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

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

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

    现在在mainVC中,在创建VC1时将其设置为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移动,因此您可能只需要对其中一个转换进行动画处理 .

    update

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

    在Apple的View Controller编程指南中,他们有this to say

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

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

    在实践中,Apple 's advice is a little extreme... in normal cases, you don' t需要创建一个专用的委托和方法,你可以依赖 [self presentingViewController] dismissViewControllerAnimated: - 在像你这样的情况下,你希望你的解雇对你需要注意的远程对象产生其他影响 .

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

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

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

    在VC1中

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

    • 一旦VC1被解雇,它就知道了(我也不知道)......与代表一起,你没有这种不确定性 .

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

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

    update2

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

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

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

    紧耦合
    您将viewController结构连接在一起 . 例如,如果要在mainVC之前插入新的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!
    

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

    这是一种相当冗长的说法 - 请使用代表 . 如果它有帮助,我在这里做了另一个简短的模式描述:
    Is passing a controller in a construtor always a bad practice?

    update 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到达的,但它是没有一点显式委托就能得到的最好的 .

  • 9

    Swift 中的示例,描绘了代工厂's explanation above and the Apple'的文档:

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

相关问题