当导航栏的返回按钮(返回上一屏幕,返回父视图)被按下时,我需要执行一些操作。
有没有一些方法可以实现捕获事件并触发一些动作,在屏幕消失之前暂停并保存数据?
当导航栏的返回按钮(返回上一屏幕,返回父视图)被按下时,我需要执行一些操作。
有没有一些方法可以实现捕获事件并触发一些动作,在屏幕消失之前暂停并保存数据?
更新: 根据一些评论,原回答中的解决方案似乎在iOS 8+的某些情况下无法正常工作。我没有更多细节无法验证这是否属实。
然而,对于那些处于这种情况的人,有一个替代方案。可以通过重写 willMove(toParentViewController:)
来检测视图控制器何时被弹出。基本思路是当 parent
是 nil
时,视图控制器正在被弹出。
请参阅 “实现容器视图控制器” 了解更多详情。
自从iOS 5以来,我发现处理这种情况最简单的方法是使用新的方法 - (BOOL)isMovingFromParentViewController
:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController) {
// Do your stuff here
}
}
- (BOOL)isMovingFromParentViewController
在导航堆栈中推送和弹出控制器时有意义。
但是,如果您正在显示模态视图控制器,则应改用- (BOOL)isBeingDismissed
:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isBeingDismissed) {
// Do your stuff here
}
}
如此问题中所述,您可以将两个属性结合起来:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController || self.isBeingDismissed) {
// Do your stuff here
}
}
其他解决方案依赖于存在一个UINavigationBar
。与此不同,我更喜欢我的方法,因为它将执行所需的任务与触发事件(即按下返回按钮)分离。
self.isMovingFromParentViewController
。如文档所述,"只有在以下方法内调用此方法时,该方法才返回 YES:" viewWillDisappear:
和 viewDidDisappear:
。同时,请确保你正在使用导航控制器,而不是呈现模态视图控制器。 - elitalonpopToRootViewControllerAnimated
以编程方式弹出导航堆栈时,self.isMovingFromParentViewController
的值为TRUE - 而没有触摸返回按钮。这种情况下应该否决你的答案?(主题说“在导航栏上按下'返回'按钮”) - kas-kadoverride func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParentViewController(){ println("back button pressed") } }
- Camillo-viewDidDisappear:
方法中执行此操作,因为有可能会出现 -viewWillDisappear:
没有 -viewDidDisappear:
的情况(例如当你开始滑动以取消导航控制器项时,然后取消该滑动)。 - Heath Borders当点击返回按钮时,viewWillAppear()
和viewDidDisappear()
确实会被调用,但它们也会在其他时间被调用。请参见答案末尾的更多内容。
检测返回按钮最好在视图控制器从其父视图控制器(导航控制器)中移除时进行,可以借助willMoveToParentViewController(_:)
或didMoveToParentViewController()
来完成。
如果parent为nil,则该视图控制器将从导航堆栈中弹出并关闭。如果parent不为nil,则该视图控制器正在被添加到堆栈并呈现。
// Objective-C
-(void)willMoveToParentViewController:(UIViewController *)parent {
[super willMoveToParentViewController:parent];
if (!parent){
// The back button was pressed or interactive gesture used
}
}
// Swift
override func willMove(toParent parent: UIViewController?) {
super.willMove(toParent: parent)
if parent == nil {
// The back button was pressed or interactive gesture used
}
}
使用 didMove
代替 willMove
,并检查 self.parent
来在视图控制器被解除后执行操作。
请注意,检查父级不允许您“暂停”过渡,如果您需要进行某种异步保存。要实现这个,您可以实现以下方法。唯一的缺点是您会失去iOS风格/动画的漂亮的后退按钮。在可交互的滑动手势上也要小心。使用以下代码处理此情况。
var backButton : UIBarButtonItem!
override func viewDidLoad() {
super.viewDidLoad()
// Disable the swipe to make sure you get your chance to save
self.navigationController?.interactivePopGestureRecognizer.enabled = false
// Replace the default back button
self.navigationItem.setHidesBackButton(true, animated: false)
self.backButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Plain, target: self, action: "goBack")
self.navigationItem.leftBarButtonItem = backButton
}
// Then handle the button selection
func goBack() {
// Here we just remove the back button, you could also disabled it or better yet show an activityIndicator
self.navigationItem.leftBarButtonItem = nil
someData.saveInBackground { (success, error) -> Void in
if success {
self.navigationController?.popViewControllerAnimated(true)
// Don't forget to re-enable the interactive gesture
self.navigationController?.interactivePopGestureRecognizer.enabled = true
}
else {
self.navigationItem.leftBarButtonItem = self.backButton
// Handle the error
}
}
}
当从listVC
到settingsVC
并返回listVC
时,请遵循对detailVC
的调用。
List > Detail(推送 detailVC)Detail.viewDidAppear <- appear
Detail > Settings(推送 settingsVC)Detail.viewDidDisappear
<- disappear
返回时...
Settings > Detail(弹出 settingsVC)Detail.viewDidAppear <- appear
Detail > List(弹出 detailVC)Detail.viewDidDisappear
<- disappear
请注意,viewDidDisappear
不仅在后退时被调用,而且在前进时也会被多次调用。对于快速操作可能是需要的,但对于像网络调用保存这样的更复杂的操作可能不是需要的。
didMoveToParantViewController:
方法在视图不再可见时执行操作。这对于具有interactiveGesture的iOS7非常有帮助。 - WCByrne_ = self.navigationController?.popViewController(animated: true)
进行编程时,也会调用此方法,因此它不仅在按下返回按钮时被调用。我正在寻找一个仅在按下返回按钮时起作用的调用。 - Ethan Allen声称这不起作用的人是错误的:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
print("we are being popped")
}
}
这很好用。那么是什么导致了它不起作用的普遍误解?
问题似乎是由于一个不同方法的不正确实现引起的,即willMove(toParent:)
的实现忘记调用super
。
如果你实现willMove(toParent:)
而没有调用super
,那么self.isMovingFromParent
将会是false
,使用viewWillDisappear
看起来会失败。它并没有失败; 是你搞砸了它。
注意:真正的问题通常是第二个视图控制器检测到第一个视图控制器被弹出。请参见此处的更一般讨论:Unified UIViewController "became frontmost" detection?
编辑 有评论建议应该使用viewDidDisappear
而不是viewWillDisappear
。
willDisappear
中检查它,在didDisappear
中检查会更好。 - badhanganesh第一种方法
- (void)didMoveToParentViewController:(UIViewController *)parent
{
if (![parent isEqual:self.parentViewController]) {
NSLog(@"Back pressed");
}
}
第二种方法
-(void) viewWillDisappear:(BOOL)animated {
if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
// back button was pressed. We know this is true because self is no longer
// in the navigation stack.
}
[super viewWillDisappear:animated];
}
我已经花了两天时间来解决这个问题。我认为最好的方法就是创建一个扩展类和一个协议,像这样:
@protocol UINavigationControllerBackButtonDelegate <NSObject>
/**
* Indicates that the back button was pressed.
* If this message is implemented the pop logic must be manually handled.
*/
- (void)backButtonPressed;
@end
@interface UINavigationController(BackButtonHandler)
@end
@implementation UINavigationController(BackButtonHandler)
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
UIViewController *topViewController = self.topViewController;
BOOL wasBackButtonClicked = topViewController.navigationItem == item;
SEL backButtonPressedSel = @selector(backButtonPressed);
if (wasBackButtonClicked && [topViewController respondsToSelector:backButtonPressedSel]) {
[topViewController performSelector:backButtonPressedSel];
return NO;
}
else {
[self popViewControllerAnimated:YES];
return YES;
}
}
@end
UINavigationController
都会收到调用navigationBar:shouldPopItem:
的消息。在那里,我们检测是否按下了返回键或其他任何按钮。backButtonPressedSel
中手动弹出视图控制器,如果一切正常。UINavigationViewController
进行了子类化,并实现了navigationBar:shouldPopItem:
,不用担心,这不会产生干扰。if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
这对我在iOS 9.3.x和Swift中有效:
override func didMoveToParentViewController(parent: UIViewController?) {
super.didMoveToParentViewController(parent)
if parent == self.navigationController?.parentViewController {
print("Back tapped")
}
}
willMove
是否存在与willDisappear
相同的问题:用户可以通过滑动开始解除视图控制器,willDisappear
将被调用,但用户仍然可以取消滑动! - Mycroft Canner
- (BOOL) navigationShouldPopOnBackButton
{
[self backAction];
return NO;
}
- (void) backAction {
// your code goes here
// show confirmation alert, for example
// ...
}
extension UIViewController {
@objc func navigationShouldPopOnBackButton() -> Bool {
return true
}
}
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
return self.topViewController?.navigationShouldPopOnBackButton() ?? true
}
}
以下代码需要放在你想要控制返回按钮操作的视图控制器中:
override func navigationShouldPopOnBackButton() -> Bool {
self.backAction()//Your action you want to perform.
return true
}
UIBarButtonItem *l_backButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRewind target:self action:@selector(backToRootView:)];
self.navigationItem.leftBarButtonItem = l_backButton;
- (void) backToRootView:(id)sender {
// Perform some custom code
[self.navigationController popToRootViewControllerAnimated:YES];
}
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
if ([viewController isKindOfClass:[HomeController class]]) {
NSLog(@"Show home controller");
}
对于带有UINavigationController的Swift:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if self.navigationController?.topViewController != self {
print("back button tapped")
}
}