【发布时间】:2012-01-03 22:21:44
【问题描述】:
当在导航栏上按下后退按钮(返回上一屏幕,返回父视图)按钮时,我需要执行一些操作。
我可以实现一些方法来捕获事件并触发一些操作以在屏幕消失之前暂停和保存数据吗?
【问题讨论】:
-
我是这样做的show decision here
标签: iphone objective-c ios xcode
当在导航栏上按下后退按钮(返回上一屏幕,返回父视图)按钮时,我需要执行一些操作。
我可以实现一些方法来捕获事件并触发一些操作以在屏幕消失之前暂停和保存数据吗?
【问题讨论】:
标签: iphone objective-c ios xcode
更新: 根据一些 cmets 的说法,原始答案中的解决方案似乎在 iOS 8+ 的某些场景下不起作用。如果没有进一步的细节,我无法验证是否确实如此。
但是对于你们这些人来说,在这种情况下还有另一种选择。通过覆盖willMove(toParentViewController:) 可以检测何时弹出视图控制器。基本思想是当parent 为nil 时弹出视图控制器。
查看"Implementing a Container View Controller"了解更多详情。
从 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
}
}
如this question 中所述,您可以将这两个属性结合起来:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController || self.isBeingDismissed) {
// Do your stuff here
}
}
其他解决方案依赖于UINavigationBar 的存在。相反,我更喜欢我的方法,因为它将需要执行的任务与触发事件的操作(即按下后退按钮)分离。
【讨论】:
popToRootViewControllerAnimated 以编程方式弹出导航堆栈时,self.isMovingFromParentViewController 具有 TRUE 值 - 无需触摸后退按钮。我应该否决你的答案吗? (主题说“在导航栏上按下'返回'按钮”)
override func viewWillDisappear(animated: Bool) { super.viewWillDisappear(animated) if isMovingFromParentViewController(){ println("back button pressed") } }
-viewDidDisappear: 内执行此操作,因为您可能会在没有-viewDidDisappear: 的情况下获得-viewWillDisappear:(例如当您开始滑动以关闭导航控制器项目然后取消它时滑动。
虽然viewWillAppear() 和viewDidDisappear() 在点击返回按钮时被调用,但它们在其他时间也会被调用。有关更多信息,请参阅答案末尾。
在willMoveToParentViewController(_:) 或didMoveToParentViewController() 的帮助下将VC 从其父级(NavigationController)中移除时,更好地检测后退按钮
如果 parent 为 nil,则视图控制器将从导航堆栈中弹出并关闭。如果 parent 不为零,则将其添加到堆栈并呈现。
// 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
}
}
将willMove 替换为didMove 并检查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
}
}
}
如果您没有收到 viewWillAppear viewDidDisappear 问题,让我们来看一个示例。假设您有三个视图控制器:
当您从listVC 到settingsVC 再回到listVC 时,让我们关注detailVC 上的呼叫
列表>详细信息(推送详细信息VC)Detail.viewDidAppear详细信息>设置(推送设置VC)Detail.viewDidDisappear
当我们返回时...
设置 > 详细信息(弹出 settingsVC)Detail.viewDidAppear 详细信息 > 列表 (pop detailVC) Detail.viewDidDisappear
注意viewDidDisappear 被多次调用,不仅在返回时,而且在前进时。对于可能需要的快速操作,但对于更复杂的操作(例如要保存的网络调用),可能不需要。
【讨论】:
didMoveToParantViewController: 在视图不再可见时执行工作。使用 interactiveGesutre 对 iOS7 很有帮助
_ = self.navigationController?.popViewController(animated: true) 时也会调用它,因此它不仅仅是在按下后退按钮时调用。我正在寻找一个仅在按下 Back 时有效的呼叫。
那些声称这不起作用的人是错误的:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if self.isMovingFromParent {
print("we are being popped")
}
}
效果很好。那么是什么导致了人们普遍认为它没有呢?
问题似乎是由于不同方法的错误实现,即willMove(toParent:)的实现忘记调用super。
如果您在不调用super 的情况下实现willMove(toParent:),则self.isMovingFromParent 将是false,并且viewWillDisappear 的使用似乎会失败。它没有失败。你把它弄坏了。
注意:真正的问题通常是 second 视图控制器检测到 first 视图控制器已弹出。另请参阅此处更一般的讨论:Unified UIViewController "became frontmost" detection?
EDIT 一条评论表明这应该是viewDidDisappear 而不是viewWillDisappear。
【讨论】:
true - 从视图控制器的左边缘 - 即使滑动没有完全弹出它。因此,不要在willDisappear 中检查它,而是在didDisappear 中进行检查。
第一种方法
- (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];
}
【讨论】:
我已经玩(或解决)这个问题两天了。 IMO 最好的方法就是创建一个扩展类和一个协议,如下所示:
@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 将被调用,但用户仍然可以取消刷卡!
你可以使用返回按钮回调,像这样:
- (BOOL) navigationShouldPopOnBackButton
{
[self backAction];
return NO;
}
- (void) backAction {
// your code goes here
// show confirmation alert, for example
// ...
}
对于 swift 版本,您可以在全局范围内执行类似操作
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
}
【讨论】:
navigationShouldPopOnBackButton 来自哪里?它不是公共 API 的一部分。
为了记录,我认为这更像是他正在寻找的东西......
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];
}
【讨论】:
最好的方法是使用 UINavigationController 委托方法
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
使用这个你可以知道哪个控制器正在显示 UINavigationController。
if ([viewController isKindOfClass:[HomeController class]]) {
NSLog(@"Show home controller");
}
【讨论】:
您应该查看UINavigationBarDelegate Protocol。 在这种情况下,您可能需要使用 navigationBar:shouldPopItem: 方法。
【讨论】:
正如 Coli88 所说,您应该检查 UINavigationBarDelegate 协议。
更一般的方式,你也可以在当前可见的视图控制器保留的视图即将消失时,使用- (void)viewWillDisapear:(BOOL)animated来执行自定义工作。不幸的是,这将涵盖推送和弹出案例。
【讨论】:
正如purrrminator 所说,elitalon 的答案并不完全正确,因为即使以编程方式弹出控制器也会执行your stuff。
到目前为止我找到的解决方案不是很好,但它对我有用。除了elitalon 所说的,我还检查了我是否以编程方式弹出:
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if ((self.isMovingFromParentViewController || self.isBeingDismissed)
&& !self.isPoppingProgrammatically) {
// Do your stuff here
}
}
在以编程方式弹出之前,您必须将该属性添加到控制器并将其设置为 YES:
self.isPoppingProgrammatically = YES;
[self.navigationController popViewControllerAnimated:YES];
感谢您的帮助!
【讨论】:
对于带有 UINavigationController 的 Swift:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
if self.navigationController?.topViewController != self {
print("back button tapped")
}
}
【讨论】:
我通过在左侧的navigationBar 中添加一个UIControl 解决了这个问题。
UIControl *leftBarItemControl = [[UIControl alloc] initWithFrame:CGRectMake(0, 0, 90, 44)];
[leftBarItemControl addTarget:self action:@selector(onLeftItemClick:) forControlEvents:UIControlEventTouchUpInside];
self.leftItemControl = leftBarItemControl;
[self.navigationController.navigationBar addSubview:leftBarItemControl];
[self.navigationController.navigationBar bringSubviewToFront:leftBarItemControl];
当视图消失时,您需要记住将其删除:
- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (self.leftItemControl) {
[self.leftItemControl removeFromSuperview];
}
}
就是这样!
【讨论】:
7ynk3r 的回答与我最终使用的非常接近,但需要进行一些调整:
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
UIViewController *topViewController = self.topViewController;
BOOL wasBackButtonClicked = topViewController.navigationItem == item;
if (wasBackButtonClicked) {
if ([topViewController respondsToSelector:@selector(navBackButtonPressed)]) {
// if user did press back on the view controller where you handle the navBackButtonPressed
[topViewController performSelector:@selector(navBackButtonPressed)];
return NO;
} else {
// if user did press back but you are not on the view controller that can handle the navBackButtonPressed
[self popViewControllerAnimated:YES];
return YES;
}
} else {
// when you call popViewController programmatically you do not want to pop it twice
return YES;
}
}
【讨论】:
我使用了Pedro Magalhães 解决方案,除了navigationBar:shouldPop 在我这样的扩展中使用它时没有被调用:
extension UINavigationController: UINavigationBarDelegate {
public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
return self.topViewController?.navigationShouldPopOnBackButton() ?? true
}
但UINavigationController 子类中的相同内容运行良好。
class NavigationController: UINavigationController, UINavigationBarDelegate {
func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
return self.topViewController?.navigationShouldPopOnBackButton() ?? true
}
我从 iOS 13 看到一些其他问题报告此方法未被调用(但其他委托方法被按预期调用)?
【讨论】:
self.navigationController.isMovingFromParentViewController 在我使用的 iOS8 和 9 上不再工作:
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
if (self.navigationController.topViewController != self)
{
// Is Popping
}
}
【讨论】:
最终找到解决方案..我们正在寻找的方法是“willShowViewController”,它是 UINavigationController 的委托方法
//IMPORT UINavigationControllerDelegate !!
class PushedController: UIViewController, UINavigationControllerDelegate {
override func viewDidLoad() {
//set delegate to current class (self)
navigationController?.delegate = self
}
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
//MyViewController shoud be the name of your parent Class
if var myViewController = viewController as? MyViewController {
//YOUR STUFF
}
}
}
【讨论】:
MyViewController 耦合到PushedController。