检测呈现的视图控制器何时被解除

时间:2015-09-29 20:29:26

标签: ios uiviewcontroller

假设我有一个名为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没有被调用。

22 个答案:

答案 0 :(得分:35)

根据文件,提交控制人负责实际解雇。当呈现的控制器解散时,它会要求演示者为此进行操作。因此,如果你在VC1控制器中覆盖dismissViewControllerAnimated,我相信当你在VC2上点击取消时它会被调用。检测解雇,然后调用将执行实际解雇的超类版本。

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

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

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

在VC1中,您显示如下:

  • 在VC1中,创建VC2

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

  • 使用[self presentViewController:VC2 animated:YES completion:nil];

  • 正常呈现VC2控制器
  • 在VC2中,取消目标操作调用self.onDoneBlock();

结果是VC2告诉任何人提出它已完成。您可以扩展onDoneBlock以包含指示模式是否已被注释,取消,成功等的参数....

答案 1 :(得分:20)

使用区块属性

在VC2中声明

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

在VC1中设置

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

当您即将解雇

时,请在VC2中拨打电话
onDoneBlock!(true)

答案 2 :(得分:9)

呈现呈现的视图控制器都可以调用dismissViewController:animated:以解除显示的视图控制器。

前一种选择(可以说是)"正确的"一,设计明智:相同的父母"视图控制器负责显示和解除模态("子")视图控制器。

然而,后者更方便:通常,"解雇"按钮附加到呈现的视图控制器的视图中,并且它将所述视图控制器设置为其操作目标。

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

如果您采用后一种方法(呈现的视图控制器自行解除),请记住,从呈现的视图控制器调用dismissViewControllerAnimated:completion:会导致UIKit在呈现视图控制器上调用该方法:

  

<强>讨论

     

呈现视图控制器负责   解雇它提出的视图控制器。如果你调用这个方法   在呈现的视图控制器本身,UIKit要求呈现   查看控制人处理解雇。

source: UIViewController Class Reference

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

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

    // Your custom code here...
}

答案 3 :(得分:3)

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

vc.presentationController?.delegate = foo

答案 4 :(得分:2)

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

请参阅此链接,了解如何创建展开segue,https://stackoverflow.com/a/15839298/5647055

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

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

现在,无论何时按下VC2中的“取消”按钮,它都会被解除,VC1将会出现。它还将调用您在VC1中定义的unwind方法。现在,您知道所呈现的视图控制器何时被解除。

答案 5 :(得分:2)

我使用以下内容向协调器发出信号,告知视图控制器已完成&#34;。这在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?()
        }
    })
  }
}

答案 6 :(得分:1)

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

你不能假设VC1实际上进行了呈现 - 它可能是根视图控制器,VC0。涉及3个视图控制器:

  
      
  • sourceViewController
  •   
  • presentingViewController
  •   
  • presentedViewController
  •   

在您的示例中,request_idVC1 = sourceViewControllerVC2 = presentedViewController - 可能是VC1,也许不是。

但是,在解除VC2时,你总是可以依赖VC1.animationControllerForDismissedController(如果你已经实现了委托方法),在那个方法中你可以用VC1做你想做的事

答案 7 :(得分:1)

更高效的方法是创建一个用于呈现控制器的协议,然后调用 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) {
    }
}

答案 8 :(得分:1)

按以下方式使用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
    }
}

答案 9 :(得分:1)

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

答案 10 :(得分:0)

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

override func endAppearanceTransition() {
            if isBeingDismissed{
                print("dismissal logic here")
            }
 }

答案 11 :(得分:0)

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

  3. 转到DismissSegue.m文件&amp;记下以下代码:

    //LOGIN FORM
    app.get('/login', function (req, res) {
        res.render('../views/signup-signin/login');
    });
    
    //LOGIN POST
    app.post('/login', function (req, res) {
        User.find(req.body.username, function (user) {
            bcrypt.compare(req.body.password, user.password, function (err, result) {
                if (result) {
                    req.session.currentUser = user.id;
                    res.redirect('/');
                } else {
                    res.redirect('/login');
                }
            });
        });
    });
    
    //LOGOUT
    app.delete('/logout', function (req, res) {
        req.session.currentUser = null;
        res.redirect('/');
    });
    
  4. 打开故事板&amp;然后按Ctrl +从取消按钮拖动到VC1&amp;选择Action Segue作为Dismiss,你就完成了。

答案 12 :(得分:0)

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

anotherViewControllerYouWantToObserve.transitioningDelegate = self

并观察解雇:

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

答案 13 :(得分:0)

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

由于 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
        }
    }

答案 14 :(得分:0)

我已将 deinit 用于ViewController

deinit {
    dataSource.stopUpdates()
}

在取消分配类实例之前立即调用反初始化器。

答案 15 :(得分: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方法时,才会调用此方法。

答案 16 :(得分:0)

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

如果您需要了解用户发起的操作(例如屏幕上的手势)是否因UIActionController而被解雇,并且不想花时间在创建子类或扩展或代码中的任何内容上,还有一种选择。

事实证明,popoverPresentationController(或相应的任何UIViewController)的UIActionController属性具有一个delegate,您可以随时在代码中进行设置,类型为UIPopoverPresentationControllerDelegate,并具有以下方法:

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

希望这会有所帮助。

答案 17 :(得分: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) } }

答案 18 :(得分:0)

在显示的视图控制器中覆盖def callback(data): # do stuff cv2.imshow(winname, mat) cv2.waitKey(delay) rospy.subscribe(name, data_class, callback=callback) rospy.spin() 函数。

viewWillDisappear

答案 19 :(得分:0)

override viewDidAppear为我做了诀窍。我在我的模态中使用了Singleton,现在我可以在调用VC,模态和其他任何地方设置和获取它。

答案 20 :(得分:0)

答案 21 :(得分:-1)

如果在视图控制器上覆盖被覆盖:

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

至少这对我有用。