繁体   English   中英

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

[英]Detect when a presented view controller is dismissed

假设,我有一个名为 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:^{
//        
//    }];
}

但是 VC1 中的dismissViewControllerAnimated没有被调用。

根据文档,呈现控制器负责实际解雇。 当被呈现的控制器解散自己时,它会要求呈现者为它做这件事。 因此,如果您在 VC1 控制器中覆盖了dismissViewControllerAnimated,我相信当您在 VC2 上点击取消时它会被调用。 检测解雇,然后调用将执行实际解雇的超类版本。

从讨论中发现这似乎不起作用。 与其依赖底层机制, dismissViewControllerAnimated:completion在VC2本身上调用dismissViewControllerAnimated:completionself.presentingViewController在VC2中调用dismissViewControllerAnimated:completion上的self.presentingViewController dismissViewControllerAnimated:completion 这将直接调用您的覆盖。

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

所以在 VC2 中,提供一个名为onDoneBlock的块属性。

在 VC1 中,您显示如下:

  • 在VC1中,创建VC2

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

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

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

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

UIViewController有一个名为isBeingDismissed的特殊布尔属性,您可以将其用于此目的:

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

使用块属性

在 VC2 中声明

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

在 VC1 中设置

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

当您要解雇时调用 VC2

onDoneBlock!(true)

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

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

然而,后者更方便:通常,“关闭”按钮附加到呈现的视图控制器的视图,并且将所述视图控制器设置为其动作目标。

如果你采用前一种方法,你已经知道你的呈现视图控制器中发生解雇的代码行:要么在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...
}
extension Foo: UIAdaptivePresentationControllerDelegate {
    func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
        //call whatever you want
    }
}

vc.presentationController?.delegate = foo

以下列方式使用willMove(toParent: UIViewController?)似乎对我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
    }
}

您可以在要观察另一个呈现的视图控制器的解除的父视图控制器上使用UIAdaptivePresentationControllerDelegate

anotherViewControllerYouWantToObserve.transitioningDelegate = self

并观察解雇:

func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    print("anotherViewControllerYouWantToObserve was dismissed")
    return nil
}

你可以使用 unwind segue 来完成这个任务,不需要使用dismissModalViewController。 在 VC1 中定义一个 unwind segue 方法。

请参阅有关如何创建展开转场的链接, https: //stackoverflow.com/a/15839298/5647055。

假设您的 unwind segue 已设置,在为“取消”按钮定义的操作方法中,您可以将 segue 执行为 -

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

现在,只要您按下 VC2 中的“取消”按钮,它就会被解除,而 VC1 就会出现。 它还将调用您在 VC1 中定义的 unwind 方法。 现在,您知道显示的视图控制器何时被解除。

我使用以下命令向协调器发出视图控制器已“完成”的信号。 这在 tvOS 应用程序的AVPlayerViewController子类中使用,将在 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?()
        }
    })
  }
}

@ user523234 - “但是 VC1 中的dismissViewControllerAnimated 没有被调用。”

您不能假设 VC1 确实进行了呈现——比如说,它可能是根视图控制器 VC0。 涉及 3 个视图控制器:

  • 源视图控制器
  • 呈现视图控制器
  • 呈现的视图控制器

在您的示例中, VC1 = sourceViewControllerVC2 = presentedViewController VC1 = sourceViewController?? = presentingViewController ?? = presentingViewController - 也许是 VC1,也许不是。

但是,在解除 VC2 时,您始终可以依赖于 VC1.animationControllerForDismissedController 被调用(如果您已经实现了委托方法),并且在该方法中您可以使用 VC1 执行您想要的操作

在处理这个问题时,我已经多次看到这篇文章,我想我可能最终会阐明可能的答案。

如果您需要知道用户启动的操作(如屏幕上的手势)是否会关闭 UIActionController ,并且不想花时间在您的代码中创建子类或扩展或任何内容,那么还有一种替代方法。

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

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

希望这可以帮助。

另一种选择是收听您的自定义 UIPresentationController 的dismissalTransitionDidEnd()

如果您有一个模态演示文稿,它可以像页面一样通过滑动来消除,那么这很有效。

override func endAppearanceTransition() {
            if isBeingDismissed{
                print("dismissal logic here")
            }
 }
  1. 创建一个类文件 (.h/.m) 并将其命名为:DismissSegue
  2. 选择子类:UIStoryboardSegue

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

     - (void)perform { UIViewController *sourceViewController = self.sourceViewController; [sourceViewController.presentingViewController dismissViewControllerAnimated:YES completion:nil]; }
  4. 打开故事板,然后按住 Ctrl 键并从取消按钮拖动到 VC1 并选择 Action Segue 为 Dismiss,您就完成了。

如果您覆盖正在关闭的视图控制器:

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

至少这对我有用。

override viewDidAppear对我viewDidAppear 我在我的模态中使用了单例,现在可以在调用 VC、模态和其他任何地方设置和获取它。

假设,我有一个名为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:^{
//        
//    }];
}

但是没有调用VC1中的dismissViewControllerAnimated

如前所述,解决方案是使用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) } }

如果您想处理视图控制器关闭,您应该使用下面的代码。

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

不幸的是,我们不能在重写的方法中调用完成 - (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^ _Nullable)(void))completion; 因为只有在调用此视图控制器的解除方法时才会调用此方法。

我为 ViewController 使用了deinit

deinit {
    dataSource.stopUpdates()
}

在释放类实例之前立即调用析构器。

我没有看到似乎是一个简单的答案。 如果这是重复,请原谅我...

由于 VC1 负责解除 VC2,因此您需要在某个时候调用 vc1.dismiss()。 所以你可以在 VC1 中覆盖dismiss() 并将你的操作代码放在那里:

class VC1 : UIViewController {
    override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
        super.dismiss(animated: flag, completion: completion)
        // PLACE YOUR ACTION CODE HERE
    }
}

编辑:您可能希望在关闭完成时触发您的代码,而不是在它开始时。 所以在这种情况下,你应该使用:

    override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
        super.dismiss(animated: flag) {
            if let unwrapCompletion = completion { unwrapCompletion() }
            // PLACE YOUR ACTION HERE
        }
    }

一种更有效的方法是创建一个用于呈现控制器的协议,然后调用 childControllers

protocol DismissListener {
    
    func childControllerWillDismiss(_ controller : UIViewController,  animated : Bool)
    func childControllerDidDismiss(_ controller : UIViewController,  animated : Bool)
}

extension UIViewController {
    
    func dismissWithListener(animated flag: Bool, completion: (() -> Void)? = nil){
        
        self.viewWillDismiss(flag)
        self.dismiss(animated: flag, completion: {
            completion?()
            self.viewDidDismiss(true)
        })
    }
    
    func viewWillDismiss(_ animate : Bool) {
        (presentingViewController as? DismissListener)?.childControllerWillDismiss(self, animated: animate)
    }
    
    func viewDidDismiss(_ animate : Bool) {
        (presentingViewController as? DismissListener)?.childControllerDidDismiss(self, animated: animate)
    }
}

然后当视图即将关闭时:

self.dismissWithListener(animated: true, completion: nil)

最后只需将协议添加到您希望收听的任何视图控制器!

class ViewController: UIViewController, DismissListener {

    func childControllerWillDismiss(_ controller: UIViewController, animated: Bool) {
    }
    
    func childControllerDidDismiss(_ controller: UIViewController, animated: Bool) {
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM