83

在过去的一周里,我一直在思考如何通过显示和关闭多个视图控制器来解决这个问题。我创建了一个示例项目并直接从项目中粘贴代码。我有 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 上的按钮时应该返回到主控制器。没有长时间运行的代码、循环或任何计时器。只是简单地调用视图控制器。

4

6 回答 6

190

这一行:

[self dismissViewControllerAnimated:YES completion:nil];

不是向自己发送消息,它实际上是向其呈现的 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 文件中,关闭按钮方法应该调用委托方法

- (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 移动,因此您可能只希望对其中一个过渡进行动画处理。

更新

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

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

关闭呈现的视图控制器

当需要关闭呈现的视图控制器时,首选方法是让呈现的视图控制器关闭它。换句话说,只要有可能,呈现视图控制器的同一个视图控制器也应该负责解除它。尽管有几种技术可以通知呈现视图控制器其呈现的视图控制器应该被解除,但首选技术是委托。有关详细信息,请参阅“使用委托与其他控制器通信”。</p>

如果您真的仔细考虑了您想要实现的目标,以及您将如何实现它,您将意识到发送您的 MainViewController 来完成所有工作是唯一合乎逻辑的出路,因为您不想使用 NavigationController。如果您确实使用了 NavController,那么实际上您是在“委托”(即使没有明确地)给 navController 来完成所有工作。需要有一些对象来集中跟踪您的 VC 导航发生的事情,并且无论您做什么,您都需要某种与之通信的方法。

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

这是您可以想象的无需所有代表麻烦的工作...

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

}

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

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

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

更新2

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

 [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 时,两个对象都会消失

这是一种相当冗长的说法——请使用代表。如果有帮助,我在这里对模式进行了另一个简要描述:
在构造函数中传递控制器总是不好的做法吗?

更新 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 到达的,但它是最好的,没有一点明确的委托。

于 2013-02-16T12:49:59.263 回答
12

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

}

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 {

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

}

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 {

}
于 2014-12-12T01:21:30.130 回答
10

我认为您误解了有关 iOS 模态视图控制器的一些核心概念。当您关闭 VC1 时,VC1 提供的任何视图控制器也会被关闭。Apple 打算让模态视图控制器以堆叠方式流动 - 在您的情况下,VC2 由 VC1 提供。一旦您从 VC1 呈现 VC2,您就会解雇 VC1,所以这完全是一团糟。为了实现你想要的,buttonPressedFromVC1 应该在 VC1 自行关闭后立即让 mainVC 出现 VC2。我认为这可以在没有代表的情况下实现。类似的东西:

UIViewController presentingVC = [self presentingViewController];
[self dismissViewControllerAnimated:YES completion:
 ^{
    [presentingVC presentViewController:vc2 animated:YES completion:nil];
 }];

请注意 self.presentingViewController 存储在其他变量中,因为在 vc1 自行关闭后,您不应对其进行任何引用。

于 2014-02-25T14:59:40.477 回答
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)
    })
}
于 2015-04-16T10:14:32.957 回答
1

我想要这个:

MapVC 是全屏地图。

当我按下一个按钮时,它会在地图上方打开 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];
}
于 2017-08-31T11:47:38.573 回答
1

我在演示时使用 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)
于 2017-11-02T17:21:25.900 回答