检测何时关闭呈现的视图控制器


81

假设我有一个名为VC2的视图控制器类的实例。在VC2中,有一个“取消”按钮将自行关闭。但是,当“取消”按钮被触发时,我无法检测到或接收任何回调。VC2是一个黑匣子。

视图控制器(称为VC1)将使用presentViewController:animated:completion:方法呈现VC2 。

VC2被关闭时,VC1必须检测哪些选项?

编辑:从@rory mckinnel的评论和@NicolasMiari的答案,我尝试了以下操作:

在VC2中:

-(void)cancelButton:(id)sender
{
    [self dismissViewControllerAnimated:YES completion:^{

    }];
//    [super dismissViewControllerAnimated:YES completion:^{
//        
//    }];
}

在VC1中:

//-(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
- (void)dismissViewControllerAnimated:(BOOL)flag
                           completion:(void (^ _Nullable)(void))completion
{
    NSLog(@"%s ", __PRETTY_FUNCTION__);
    [super dismissViewControllerAnimated:flag completion:completion];
//    [self dismissViewControllerAnimated:YES completion:^{
//        
//    }];
}

但是dismissViewControllerAnimatedVC1中的未被调用。


1
在VC1中,将调用viewWillAppear方法
Istvan 2015年

1
根据文档,演示控制器负责实际解雇。当所呈现的控制器自行解散时,它将要求呈现者为此做。因此,如果您dismissViewControllerAnimated在VC1控制器中重写,我相信在VC2上单击cancel时它将被调用。检测到解雇,然后调用将执行实际解雇的超类版本。
罗里·麦金尼尔

1
您可以通过调用来测试覆盖[self.presentingViewController dismissViewControllerAnimated]。内部代码可能具有不同的机制来请求演示者进行解雇。
罗里·麦金尼尔

@RoryMcKinnel:使用self.presentingViewController在我的实验室VC2中以及从真正的黑匣子中都能正常工作。如果您将您的评论放在答案中,那么我将选择它作为答案。谢谢。
user523234

一个解决方案可以在以下相关文章中找到:stackoverflow.com/a/34571641/3643020
Campbell_Souped

Answers:


64

根据文档,演示控制器负责实际解雇。当所呈现的控制器自行解散时,它将要求呈现者为此做。因此,如果您在VC1控制器中覆盖dismissViewControllerAnimated,我相信当您在VC2上单击cancel时,它将被调用。检测到解雇,然后调用将执行实际解雇的超类版本。

从讨论中发现,这似乎不起作用。而不是依赖于底层的机制,而不是调用dismissViewControllerAnimated:completion上VC2本身,通话dismissViewControllerAnimated:completionself.presentingViewController在VC2。然后,这将直接调用您的替代。

更好的方法是让VC2提供一个模态控制器完成时调用的块。

因此,在VC2中,提供一个名称为block的属性onDoneBlock

在VC1中,您显示如下:

  • 在VC1中,创建VC2

  • 将VC2的完成处理程序设置为: VC2.onDoneBlock={[VC2 dismissViewControllerAnimated:YES completion:nil]};

  • 使用[self presentViewController:VC2 animation:YEScomplete:nil]正常显示VC2控制器;

  • 在VC2中,在“取消目标”操作调用中 self.onDoneBlock();

结果是VC2告诉任何提出它的人都完成了。您可以将扩展onDoneBlock为具有参数,以指示模态是否已完成,已取消,是否成功等。


2
只想感谢并欣赏它的美丽之处。即使在4年之后!谢谢!
安娜,

45

有一个特殊的布尔属性里面UIViewControllerisBeingDismissed,你可以使用这个目的:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    if isBeingDismissed {
        // TODO: Do your stuff here.
    }
}

3
最简单的最佳答案,可以正确解决大多数问题,并且不需要额外的实现。
致敬

如果不与配对,它将无法正常工作viewDidAppear
德米特里

9
在iOS13模态演示中,当用户开始拖动控制器以将其撤消时,这将是正确的,但他们可以选择不完成撤消。
Estel

44

使用块属性

在VC2中声明

var onDoneBlock : ((Bool) -> Void)?

在VC1中设置

VC2.onDoneBlock = { result in
                // Do something
            }

即将解散时致电VC2

onDoneBlock!(true)

@ Bryce64它对我不起作用,在代码转到onDoneBlock时,出现了“线程1:致命错误:意外发现nil,同时展开了一个可选值”!(真)
Lucas

@Lucas听起来您在VC1中没有正确声明它。“!” 如果未正确设置,则会强制展开错误。
brycejl '18

1
假设只显示一个View Controller。你可能在上帝知道的导航堆栈上。
Lee Probert

@LeeProbert就是这样。我们提供了一个导航控制器,它的堆栈中有大约10个可能的子控制器,几乎每个子控制器都可以触发解雇……在这种情况下,任何完成块都必须传递给所有10个此类控制器
Igor Vasilev

13

呈现呈现的视图控制器都可以调用dismissViewController:animated:以关闭呈现的视图控制器。

前一个选项是(可以说)设计上的“正确”选项:同一“父”视图控制器负责呈现和消除模式(“子”)视图控制器。

但是,后者更为方便:通常,“ dismiss”按钮被附加到所显示的视图控制器的视图,并且已将所述视图控制器设置为其动作目标。

如果您采用前一种方法,那么您已经知道呈现视图控制器中发生解雇的代码行:在dismissViewControllerAnimated:completion:或之后在完成块中运行代码。

如果您采用后一种方法(呈现的视图控制器将自身关闭),请记住,dismissViewControllerAnimated:completion:从呈现的视图控制器进行调用会使UIKit依次在呈现的视图控制器上调用该方法:

讨论区

呈现视图控制器负责解散其呈现的视图控制器。如果您在呈现的视图控制器本身上调用此方法,则UIKit会要求呈现的视图控制器处理撤消。

来源:UIViewController类参考

因此,为了拦截此类事件,您可以在呈现的视图控制器中重写该方法:

override func dismiss(animated flag: Bool,
                         completion: (() -> Void)?) {
    super.dismiss(animated: flag, completion: completion)

    // Your custom code here...
}

1
没问题。但是事实证明它没有按预期工作。幸运的是,@ RoryMcKinnel的答案似乎提供了更多选择。
Nicolas Miari

尽管这种方法足够通用,可以从基本视图控制器广告中重写视图控制器,从而覆盖其中的dismissViewControllerAnimated。但是,如果您尝试在导航视图控制器中的视图控制器中完成包装,它就会失败
hariszaman

4
当用户从顶部滑动以退出模态视图控制器时,不会调用它!
德米特里

3
extension Foo: UIAdaptivePresentationControllerDelegate {
    func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
        //call whatever you want
    }
}

vc.presentationController?.delegate = foo

1
iOS 13.0+
gondo

2

您可以使用unwind segue执行此任务,而无需使用dismissModalViewController。在VC1中定义一个解散方法。

有关如何创建展开序列的信息,请参见以下链接:https: //stackoverflow.com/a/15839298/5647055 。

假设已设置了放宽序号,则在为“取消”按钮定义的操作方法中,可以按以下方式执行序号:

[self performSegueWithIdentifier:@"YourUnwindSegueName" sender:nil];

现在,每当您按下VC2中的“取消”按钮时,该按钮都将被关闭并显示VC1。它还将调用您在VC1中定义的unwind方法。现在,您知道何时关闭了显示的视图控制器。


2

我使用以下命令向协调器发信号,告知视图控制器“完成”。这AVPlayerViewController在tvOS应用程序的子类中使用,在playerVC解除转换完成后将被调用:

class PlayerViewController: AVPlayerViewController {
  var onDismissal: (() -> Void)?

  override func beginAppearanceTransition(_ isAppearing: Bool, animated: Bool) {
    super.beginAppearanceTransition(isAppearing, animated: animated)
    transitionCoordinator?.animate(alongsideTransition: nil,
      completion: { [weak self] _ in
         if !isAppearing {
            self?.onDismissal?()
        }
    })
  }
}

您不应该继承AVPLayerViewController。苹果文档说:“不支持对AVPlayerViewController进行子类化并覆盖其方法,这将导致未定义的行为。”
Neru

2

willMove(toParent: UIViewController?)下列方式使用似乎对我有用。(在iOS12上测试)。

override func willMove(toParent parent: UIViewController?) {
    super.willMove(toParent: parent);

    if parent == nil
    {
        // View controller is being removed.
        // Perform onDismiss action
    }
}

1

@ user523234-“但是未调用VC1中的dismissViewControllerAnimated。”

您不能假设VC1确实在进行演示-可以说是根视图控制器VC0。涉及3个视图控制器:

  • sourceViewController
  • presentingViewController
  • presentViewController

在你的榜样,VC1 = sourceViewControllerVC2 = presentedViewController?? = presentingViewController-也许VC1,也许不是。

但是,在关闭VC2时,您始终可以依赖于VC1.animationControllerForDismissedController的调用(如果您已实现委托方法),并且在该方法中您可以使用VC1做您想做的事情


1

在处理此问题时,我已经看过很多次了,我想我可能最终会为可能的答案提供一些启示。

如果您需要知道用户启动的动作(例如屏幕上的手势)是否涉及UIActionController的解雇,并且不想花时间在创建子类或扩展或代码中的任何内容上,则可以选择一种方法。

事实证明,UIActionControllerpopoverPresentationController属性(或具有这种效果的任何UIViewController)具有可以在您的代码中随时设置的委托,该委托的类型为UIPopoverPresentationControllerDelegate,并且具有以下方法:

从动作控制器分配委托,在委托类(视图,视图控制器或其他)中实现您选择的方法,瞧!

希望这可以帮助。


自iOS 13以来,这些功能已被弃用
。Doh

0
  1. 创建一个类文件(.h / .m)并将其命名为:DismissSegue
  2. 选择子类别:UIStoryboardSegue

  3. 转到DismissSegue.m文件并写下以下代码:

    - (void)perform {
        UIViewController *sourceViewController = self.sourceViewController;
        [sourceViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil];
    }
    
  4. 打开情节提要,然后从取消按钮Ctrl +拖动到VC1,然后选择“动作Segue”为“关闭”,您就完成了。


0

如果在被弃用的视图控制器上重写:

override func removeFromParentViewController() {
    super.removeFromParentViewController()
    // your code here
}

至少这对我有用。


@JohnScalo不是正确的,相当多的“本机”视图控制器层次结构使用子/父基元实现自己。
mxcl



0

viewWillDisappear提出的视图控制器中的替代功能。

override func viewWillDisappear(_ animated: Bool) {
    //Your code here
}

如果不与配对,它将无法正常工作viewDidAppear
德米特里

1
确保致电super.viewWillDisappear
戴尔

0

如前所述,解决方案是使用 override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil)

对于那些想知道为什么override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil)并非总能正常工作的人,您可能会发现,UINavigationController如果正在管理该呼叫,则该呼叫将被拦截。我写了一个子类,应该可以帮助您:

class DismissingNavigationController: UINavigationController { override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { super.dismiss(animated: flag, completion: completion) topViewController?.dismiss(animated: flag, completion: completion) } }


0

如果要处理视图控制器的关闭,则应使用以下代码。

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    if (self.isBeingDismissed && self.completion != NULL) {
        self.completion();
    }
}

不幸的是,我们无法在覆盖方法中调用完成-(void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^ _Nullable)(void))completion;因为仅当您调用此视图控制器的dismiss方法时才调用此方法。


viewWillDisappear如果不与配对,也无法正常工作viewDidAppear
德米特里

当VC被完全覆盖时(例如,使用模式),将调用viewWillDisappear。您可能没有被解雇
Lou Franco


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.