在导航控制器中设置后退按钮的操作


180

我正在尝试覆盖导航控制器中后退按钮的默认操作。我为目标提供了自定义按钮上的操作。奇怪的是,尽管通过backbutton属性对其进行分配时,它并不关注它们,而只是弹出当前视图并返回到根目录:

UIBarButtonItem *backButton = [[UIBarButtonItem alloc] 
                                  initWithTitle: @"Servers" 
                                  style:UIBarButtonItemStylePlain 
                                  target:self 
                                  action:@selector(home)];
self.navigationItem.backBarButtonItem = backButton;

一旦在上设置了它leftBarButtonItemnavigationItem它就会调用我的动作,但是按钮看起来像是普通的圆形按钮,而不是箭头向后的按钮:

self.navigationItem.leftBarButtonItem = backButton;

如何返回到根视图之前调用我的自定义操作?有没有办法覆盖默认的后退操作,还是有方法在离开视图时始终被调用(viewDidUnload不这样做)?


操作:@selector(home)]; 在选择器操作后需要一个:: @ selector(home :)]; 否则将无法正常工作
PartySoft

7
@PartySoft除非使用冒号声明该方法,否则情况并非如此。具有不带任何参数的按钮调用选择器是完全有效的。
mbm29414 2012年


3
苹果为什么不提供带有后退按钮形状的按钮?似乎很明显。
2013年

Answers:


363

尝试将其放入要检测印刷机的视图控制器中:

-(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];
}

1
这是一个光滑,干净,漂亮且经过深思熟虑的解决方法
boliva 2011年

6
+1很棒的技巧,但无法控制流行音乐的动画
matm 2012年

3
如果我通过按钮向代理发送消息并且代理弹出控制器,则对我不起作用-这仍然会触发。
SAHM 2012年

21
另一个问题是,如果用户按下后退按钮还是以编程方式调用了[self.navigationController popViewControllerAnimated:YES],您将无法区分
Chase Roberts

10
仅供参考:Swift版本:if (find(self.navigationController!.viewControllers as! [UIViewController],self)==nil)
hEADcRASH

177

我已经实现了UIViewController-BackButtonHandler扩展。它不需要子类化任何东西,只需将其放入您的项目中并覆盖类中的navigationShouldPopOnBackButton方法UIViewController

-(BOOL) navigationShouldPopOnBackButton {
    if(needsShowConfirmation) {
        // Show confirmation alert
        // ...
        return NO; // Ignore 'Back' button this time
    }
    return YES; // Process 'Back' button click and Pop view controler
}

下载示例应用程序


16
这是我见过的最干净的解决方案,比使用自己的自定义UIButton更好,更简单。谢谢!
ramirogm

4
navigationBar:shouldPopItem:不是私有方法,因为它是UINavigationBarDelegate协议的一部分。
2013年

1
但是UINavigationController是否已经实现了委托(返回YES)?还是将来呢?子类化可能是一个更安全的选择
2013年

8
我刚刚在iOS 7.1中实现了此功能(非常酷的BTW),并注意到返回NO后退按钮保持禁用状态(在视觉上,因为它仍然接收并响应触摸事件)。我通过elseshouldPop检查中添加一条语句并循环浏览导航栏子视图来解决此问题,并alpha在动画块中根据需要将该值设置回1:gist.github.com/idevsoftware/9754057
boliva 2014年

2
这是我见过的最好的扩展之一。非常感谢。
Srikanth 2014年

42

与Amagrammer所说的不同,这是可能的。您必须将子类化navigationController。我在这里解释了所有内容(包括示例代码)。


Apple的文档(developer.apple.com/iphone/library/documentation/UIKit/…)说:“此类不适用于子类化”。尽管我不确定它们的含义-它们的意思可能是“您通常不需要这样做”,或者可能的意思是“如果您弄乱了我们的控制器,我们将拒绝您的应用程序” ...
Kuba Suder

当然,这是唯一的方法。希望我能给您更多的积分Hans!
亚当·埃伯巴赫

1
您实际上可以使用此方法阻止视图退出吗?如果您希望视图不退出,您将使popViewControllerAnimated方法返回什么?
JosephH

1
是的,你可以。请注意,不要在实现中调用超类方法!您不应该这样做,用户希望返回导航。您可以做的是要求您确认。根据Apple的文档,popViewController返回:“从堆栈中弹出的视图控制器。” 所以当什么都没有弹出时,您应该返回nil;
HansPinckaers 2010年

1
@HansPickaers我认为您关于防止视图退出的答案可能有些不正确。如果我从popViewControllerAnimated:的子类实现中显示“确认”消息,则无论我返回什么,NavigationBar仍会在树中向上设置一个动画级别。这似乎是因为单击后退按钮会在导航栏上调用shouldPopNavigationItem。我按照建议从子类方法返回nil。
deepwinter 2013年

15

迅捷版:

(来自https://stackoverflow.com/a/19132881/826435

在视图控制器中,您只需要遵循一个协议并执行所需的任何操作即可:

extension MyViewController: NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress() -> Bool {
        performSomeActionOnThePressOfABackButton()
        return false
    }
}

然后创建一个类,例如NavigationController+BackButton,然后将以下代码复制粘贴:

protocol NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress() -> Bool
}

extension UINavigationController {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
        // Prevents from a synchronization issue of popping too many navigation items
        // and not enough view controllers or viceversa from unusual tapping
        if viewControllers.count < navigationBar.items!.count {
            return true
        }

        // Check if we have a view controller that wants to respond to being popped
        var shouldPop = true
        if let viewController = topViewController as? NavigationControllerBackButtonDelegate {
            shouldPop = viewController.shouldPopOnBackButtonPress()
        }

        if (shouldPop) {
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        } else {
            // Prevent the back button from staying in an disabled state
            for view in navigationBar.subviews {
                if view.alpha < 1.0 {
                    UIView.animate(withDuration: 0.25, animations: {
                        view.alpha = 1.0
                    })
                }
            }

        }

        return false
    }
}

也许我错过了一些东西,但对我却不起作用,该扩展名的performSomeActionOnThePressOfABackButton方法从未调用过
Turvy

@FlorentBreton可能是个误会?shouldPopOnBackButtonPress只要没有错误,就应该被调用。performSomeActionOnThePressOfABackButton只是一种不存在的虚构方法。
kgaidis '16

我了解这一点,这就是为什么我performSomeActionOnThePressOfABackButton在控制器中创建了一个方法,当按下后退按钮时执行特定操作的原因,但是从未调用此方法,该操作是正常的后退操作
Turvy

1
也不为我工作。从不调用shouldPop方法。您在某个地方设置了代表吗?
蒂姆·奥丁

@TimAutin我刚刚再次测试了此方法,似乎有些变化。要了解的关键部分UINavigationControllernavigationBar.delegate设置为导航控制器。因此应该调用方法。但是,在Swift中,即使在子类中也无法调用它们。但是,我确实做到了在Objective-C中调用它们,因此我现在仅使用Objective-C版本。可能是Swift错误。
kgaidis

5

无法直接执行。有两种选择:

  1. 创建自己的自定义项UIBarButtonItem,如果测试通过,则在点击并弹出时进行验证
  2. 使用UITextField委托方法(例如)验证表单字段的内容-textFieldShouldReturn:,该方法在键盘上按下ReturnDone按钮后调用

第一个选项的缺点是无法从自定义栏按钮访问后退按钮的左箭头样式。因此,您必须使用图像或使用常规样式按钮。

第二个选项很好,因为您可以在委托方法中获取文本字段,因此可以将验证逻辑定位为发送给委托回调方法的特定文本字段。


5

出于某些线程原因,@ HansPinckaers提到的解决方案不适合我,但是我发现了一种更简单的方法来触碰“后退”按钮,因此我想在此固定下来,以免避免数小时的欺骗。其他人。诀窍非常简单:只需将透明的UIButton作为子视图添加到UINavigationBar,然后为他设置选择器就好像它是真正的按钮一样!这是一个使用Monotouch和C#的示例,但是要找到对Objective-C的转换并不难。

public class Test : UIViewController {
    public override void ViewDidLoad() {
        UIButton b = new UIButton(new RectangleF(0, 0, 60, 44)); //width must be adapted to label contained in button
        b.BackgroundColor = UIColor.Clear; //making the background invisible
        b.Title = string.Empty; // and no need to write anything
        b.TouchDown += delegate {
            Console.WriteLine("caught!");
            if (true) // check what you want here
                NavigationController.PopViewControllerAnimated(true); // and then we pop if we want
        };
        NavigationController.NavigationBar.AddSubview(button); // insert the button to the nav bar
    }
}

有趣的事实:为了测试目的并为假按钮找到合适的尺寸,我将其背景颜色设置为蓝色...并且它显示后退按钮后面!无论如何,它仍然可以抓住针对原始按钮的任何触摸。


3

此技术使您可以更改“后退”按钮的文本,而不会影响任何视图控制器的标题或在动画过程中看到后退按钮的文本更改。

将此添加到调用视图控制器中的init方法中:

UIBarButtonItem *temporaryBarButtonItem = [[UIBarButtonItem alloc] init];   
temporaryBarButtonItem.title = @"Back";
self.navigationItem.backBarButtonItem = temporaryBarButtonItem;
[temporaryBarButtonItem release];

3

最简单的方法

您可以使用UINavigationController的委托方法。willShowViewController当按下VC的后退按钮时将调用该方法。

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;

确保您的视图控制器将自己设置为继承的navigationController的委托,并符合UINavigationControllerDelegate协议
Justin Milo 2015年

3

这是我的Swift解决方案。在UIViewController的子类中,重写navigationShouldPopOnBackButton方法。

extension UIViewController {
    func navigationShouldPopOnBackButton() -> Bool {
        return true
    }
}

extension UINavigationController {

    func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {
        if let vc = self.topViewController {
            if vc.navigationShouldPopOnBackButton() {
                self.popViewControllerAnimated(true)
            } else {
                for it in navigationBar.subviews {
                    let view = it as! UIView
                    if view.alpha < 1.0 {
                        [UIView .animateWithDuration(0.25, animations: { () -> Void in
                            view.alpha = 1.0
                        })]
                    }
                }
                return false
            }
        }
        return true
    }

}

UIViewController中的覆盖方法navigationShouldPopOnBackButton不起作用-程序执行父方法而不是被覆盖的父方法。有什么解决办法吗?有人有同样的问题吗?
Pawel Cala 2015年

如果返回true,它会一直返回rootview
Pawriwes 2015年

@Pawriwes这是我写的一个似乎对我有用的解决方案:stackoverflow.com/a/34343418/826435
kgaidis 2015年

3

找到了一种保留后退按钮样式的解决方案。将以下方法添加到您的视图控制器。

-(void) overrideBack{

    UIButton *transparentButton = [[UIButton alloc] init];
    [transparentButton setFrame:CGRectMake(0,0, 50, 40)];
    [transparentButton setBackgroundColor:[UIColor clearColor]];
    [transparentButton addTarget:self action:@selector(backAction:) forControlEvents:UIControlEventTouchUpInside];
    [self.navigationController.navigationBar addSubview:transparentButton];


}

现在提供以下方法所需的功能:

-(void)backAction:(UIBarButtonItem *)sender {
    //Your functionality
}

它所做的就是用透明按钮覆盖后退按钮;)


3

覆盖navigationBar(_导航栏:shouldPop):即使可行,也不是一个好主意。对我来说,它导航返回时会导致随机崩溃。我建议您通过从navigationItem中删除默认的backButton并创建如下所示的自定义后退按钮来覆盖后退按钮:

override func viewDidLoad(){
   super.viewDidLoad()
   
   navigationItem.leftBarButton = .init(title: "Go Back", ... , action: #selector(myCutsomBackAction) 

   ...
 
}

=======================================

建立与先前的响应UIAlertSwift5异步方式


protocol NavigationControllerBackButtonDelegate {
    func shouldPopOnBackButtonPress(_ completion: @escaping (Bool) -> ())
}

extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
      
        if viewControllers.count < navigationBar.items!.count {
            return true
        }
        
        // Check if we have a view controller that wants to respond to being popped
        
        if let viewController = topViewController as? NavigationControllerBackButtonDelegate {
            
            viewController.shouldPopOnBackButtonPress { shouldPop in
                if (shouldPop) {
                    /// on confirm => pop
                    DispatchQueue.main.async {
                        self.popViewController(animated: true)
                    }
                } else {
                    /// on cancel => do nothing
                }
            }
            /// return false => so navigator will cancel the popBack
            /// until user confirm or cancel
            return false
        }else{
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        }
        return true
    }
}

在您的控制器上


extension MyController: NavigationControllerBackButtonDelegate {
    
    func shouldPopOnBackButtonPress(_ completion: @escaping (Bool) -> ()) {
    
        let msg = "message"
        
        /// show UIAlert
        alertAttention(msg: msg, actions: [
            
            .init(title: "Continuer", style: .destructive, handler: { _ in
                completion(true)
            }),
            .init(title: "Annuler", style: .cancel, handler: { _ in
                completion(false)
            })
            ])
   
    }

}

您能否提供有关if viewControllers.count <navigationBar.items!.count {return true}检查的详细信息?
H4Hugo

//避免出现弹出太多导航项的同步问题,//避免因不正常的敲击而
导致

2

我不认为这是容易的。我相信解决此问题的唯一方法是制作自己的后退按钮箭头图像。起初,这让我感到沮丧,但我明白为什么出于一致性的原因而将其排除在外。

您可以通过创建常规按钮并隐藏默认的后退按钮来关闭(无箭头):

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:@"Servers" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease];
self.navigationItem.hidesBackButton = YES;

2
是的,问题是我希望它看起来像普通的后退按钮,只需要它先调用我的自定义操作即可…
鹦鹉

2

通过子类化和重写方法,有一种更简单的方法UINavigationBarShouldPopItem


我想您的意思是说要对UINavigationController类进行子类化并实现shouldPopItem方法。那对我来说很好。但是,该方法不应像您期望的那样简单地返回YES或NO。此处提供说明和解决方案:stackoverflow.com/a/7453933/462162
arlomedia 2014年

2

onegray的解决方案并不安全。根据Apple的官方文件,https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html

“如果在一个类别中声明的方法的名称与原始类中的方法的名称相同,或者在同一类(甚至是超类)的另一个类别中的方法的名称相同,则该行为对于使用哪种方法实现未定义如果您在自己的类中使用类别,则不太可能出现问题,但是在使用类别向标准Cocoa或Cocoa Touch类中添加方法时,可能会引起问题。”


2

使用Swift:

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(animated)
    if self.navigationController?.topViewController != self {
        print("back button tapped")
    }
}

从iOS 10开始,也许是更早的时候,这不再起作用。
Murray Sagal

2

这是@oneway的Swift 3版本,用于在触发导航栏后退按钮事件之前将其捕获。由于UINavigationBarDelegate无法使用UIViewController,你需要创建时将触发的委托navigationBar shouldPop被调用。

@objc public protocol BackButtonDelegate {
      @objc optional func navigationShouldPopOnBackButton() -> Bool 
}

extension UINavigationController: UINavigationBarDelegate  {

    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {

        if viewControllers.count < (navigationBar.items?.count)! {                
            return true
        }

        var shouldPop = true
        let vc = self.topViewController

        if vc.responds(to: #selector(vc.navigationShouldPopOnBackButton)) {
            shouldPop = vc.navigationShouldPopOnBackButton()
        }

        if shouldPop {
            DispatchQueue.main.async {
                self.popViewController(animated: true)
            }
        } else {
            for subView in navigationBar.subviews {
                if(0 < subView.alpha && subView.alpha < 1) {
                    UIView.animate(withDuration: 0.25, animations: {
                        subView.alpha = 1
                    })
                }
            }
        }

        return false
    }
}

然后,在视图控制器中添加委托函数:

class BaseVC: UIViewController, BackButtonDelegate {
    func navigationShouldPopOnBackButton() -> Bool {
        if ... {
            return true
        } else {
            return false
        }        
    }
}

我意识到我们经常想为用户添加警报控制器,以决定他们是否要返回。如果是这样,您可以随时执行以下操作return falsenavigationShouldPopOnBackButton()关闭视图控制器:

func navigationShouldPopOnBackButton() -> Bool {
     let alert = UIAlertController(title: "Warning",
                                          message: "Do you want to quit?",
                                          preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "Yes", style: .default, handler: { UIAlertAction in self.yes()}))
            alert.addAction(UIAlertAction(title: "No", style: .cancel, handler: { UIAlertAction in self.no()}))
            present(alert, animated: true, completion: nil)
      return false
}

func yes() {
     print("yes")
     DispatchQueue.main.async {
            _ = self.navigationController?.popViewController(animated: true)
        }
}

func no() {
    print("no")       
}

我遇到一个错误:Value of type 'UIViewController' has no member 'navigationShouldPopOnBackButton' 当我尝试编译您的代码时,对于该行if vc.responds(to: #selector(v...,该行还self.topViewController返回一个可选参数,并且对此也有警告。
桑卡尔

FWIW,我通过以下方式修复了该代码:let vc = self.topViewController as! MyViewController到目前为止,它似乎工作正常。如果您认为这是正确的更改,则可以编辑代码。此外,如果您认为不应该这样做,我将很高兴知道为什么。感谢您的代码。您可能应该撰写有关此内容的博客文章,因为此答案根据票数已被隐藏。
桑卡尔

@SankarP收到该错误的原因是您MyViewController可能不符合BackButtonDelegate。而不是强迫解包,您应该这样做guard let vc = self.topViewController as? MyViewController else { return true }以避免可能的崩溃。
Lawliet

谢谢。我认为警卫声明应该变成:guard let vc = self.topViewController as? MyViewController else { self.popViewController(animated: true) return true }确保在无法正确投射屏幕的情况下将屏幕移至正确的页面。我现在知道该navigationBar函数在所有VC中都被调用,而不仅仅是存在此代码的viewcontroller。也许也可以更新答案中的代码?谢谢。
桑卡尔

2

Swift 4 iOS 11.3版本:

这是基于来自 https://stackoverflow.com/a/34343418/4316579的

我不确定该扩展程序何时停止工作,但是在撰写本文时(Swift 4),除非您按如下所述声明UINavigationBarDelegate一致性,否则似乎不再执行该扩展程序。

希望这对那些想知道为什么其扩展名不再起作用的人们有所帮助。

extension UINavigationController: UINavigationBarDelegate {
    public func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {

    }
}

1

通过使用当前保留为“ nil”的目标变量和动作变量,您应该能够连接保存对话框,以便在“选择”按钮时调用它们。注意,这可能会在奇怪的时刻触发。

我主要同意Amagrammer,但我不认为将箭头自定义按钮变得如此困难。我只是将重命名后退按钮,进行屏幕截图,将需要的按钮尺寸进行Photoshop处理,然后将其作为按钮顶部的图像。


我同意您可以使用photoshop,如果我真的想要它,我想我可以这样做,但是现在决定改变外观,以使它按我想要的方式工作。
约翰·巴林格2009年

是的,除了将动作附加到backBarButtonItem时不会触发这些动作。我不知道这是错误还是功能;甚至Apple也可能不知道。关于照相购物练习,再次提醒我,苹果会拒绝使用滥用规范符号的应用程序。
Amagrammer

注意:此答案已从重复项中合并。
Shog9 2013年

1

您可以尝试访问NavigationBars右键按钮项并设置其选择器属性...这里有一个参考UIBarButtonItem引用,如果此工作不起作用,则另一件事是,将导航栏的右键按钮项设置为您自定义的UIBarButtonItem创建并设置其选择器...希望有帮助


注意:此答案已从重复项中合并。
Shog9 2013年

1

对于需要这样的用户输入的表单,我建议将其作为“模式”而不是导航堆栈的一部分来调用。这样,他们就必须处理表单上的事务,然后可以使用自定义按钮进行验证并关闭它。您甚至可以设计一个导航栏,该导航栏看起来与应用程序的其余部分相同,但可以提供更多控制权。


注意:此答案已从重复项中合并。
Shog9 2013年

1

要拦截“后退”按钮,只需用透明的UIControl覆盖它并拦截触摸即可。

@interface MyViewController : UIViewController
{
    UIControl   *backCover;
    BOOL        inhibitBackButtonBOOL;
}
@end

@implementation MyViewController
-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    // Cover the back button (cannot do this in viewWillAppear -- too soon)
    if ( backCover == nil ) {
        backCover = [[UIControl alloc] initWithFrame:CGRectMake( 0, 0, 80, 44)];
#if TARGET_IPHONE_SIMULATOR
        // show the cover for testing
        backCover.backgroundColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:0.15];
#endif
        [backCover addTarget:self action:@selector(backCoverAction) forControlEvents:UIControlEventTouchDown];
        UINavigationBar *navBar = self.navigationController.navigationBar;
        [navBar addSubview:backCover];
    }
}

-(void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];

    [backCover removeFromSuperview];
    backCover = nil;
}

- (void)backCoverAction
{
    if ( inhibitBackButtonBOOL ) {
        NSLog(@"Back button aborted");
        // notify the user why...
    } else {
        [self.navigationController popViewControllerAnimated:YES]; // "Back"
    }
}
@end

注意:此答案已从重复项中合并。
Shog9 2013年

1

至少在Xcode 5中,有一个简单而不错的(不是完美的)解决方案。在IB中,将Bar Button Item拖出Utilities窗格,然后将其拖放到Navigation Bar的Back按钮所在的左侧。将标签设置为“返回”。您将拥有一个可以正常使用的按钮,可以将其绑定到IBAction并关闭viewController。我正在做一些工作,然后触发轻松的搜索,它运行完美。

不理想的是,此按钮没有获得<箭头,并且不继承前一个VC的标题,但是我认为可以对此进行管理。就我的目的而言,我将新的“后退”按钮设置为“完成”按钮,因此目的很明确。

最后,您还可以在IB导航器中使用两个“后退”按钮,但是为清楚起见对其进行标记很容易。

在此处输入图片说明


1

迅速

override func viewWillDisappear(animated: Bool) {
    let viewControllers = self.navigationController?.viewControllers!
    if indexOfArray(viewControllers!, searchObject: self) == nil {
        // do something
    }
    super.viewWillDisappear(animated)
}

func indexOfArray(array:[AnyObject], searchObject: AnyObject)-> Int? {
    for (index, value) in enumerate(array) {
        if value as UIViewController == searchObject as UIViewController {
            return index
        }
    }
    return nil
}

1

这种方法对我有用(但是“后退”按钮将没有“ <”符号):

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIBarButtonItem* backNavButton = [[UIBarButtonItem alloc] initWithTitle:@"Back"
                                                                      style:UIBarButtonItemStyleBordered
                                                                     target:self
                                                                     action:@selector(backButtonClicked)];
    self.navigationItem.leftBarButtonItem = backNavButton;
}

-(void)backButtonClicked
{
    // Do something...
    AppDelegate* delegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
    [delegate.navController popViewControllerAnimated:YES];
}

1

Swift版本的@onegray的答案

protocol RequestsNavigationPopVerification {
    var confirmationTitle: String { get }
    var confirmationMessage: String { get }
}

extension RequestsNavigationPopVerification where Self: UIViewController {
    var confirmationTitle: String {
        return "Go back?"
    }

    var confirmationMessage: String {
        return "Are you sure?"
    }
}

final class NavigationController: UINavigationController {

    func navigationBar(navigationBar: UINavigationBar, shouldPopItem item: UINavigationItem) -> Bool {

        guard let requestsPopConfirm = topViewController as? RequestsNavigationPopVerification else {
            popViewControllerAnimated(true)
            return true
        }

        let alertController = UIAlertController(title: requestsPopConfirm.confirmationTitle, message: requestsPopConfirm.confirmationMessage, preferredStyle: .Alert)

        alertController.addAction(UIAlertAction(title: "Cancel", style: .Cancel) { _ in
            dispatch_async(dispatch_get_main_queue(), {
                let dimmed = navigationBar.subviews.flatMap { $0.alpha < 1 ? $0 : nil }
                UIView.animateWithDuration(0.25) {
                    dimmed.forEach { $0.alpha = 1 }
                }
            })
            return
        })

        alertController.addAction(UIAlertAction(title: "Go back", style: .Default) { _ in
            dispatch_async(dispatch_get_main_queue(), {
                self.popViewControllerAnimated(true)
            })
        })

        presentViewController(alertController, animated: true, completion: nil)

        return false
    }
}

现在,在任何控制器中,只要遵守RequestsNavigationPopVerification该行为,默认情况下就会采用此行为。


1

isMovingFromParentViewController

override func viewWillDisappear(animated: Bool) {
    super.viewWillDisappear(true)

    if self.isMovingFromParentViewController {
        // current viewController is removed from parent
        // do some work
    }
}

您能否进一步说明这如何证明按下了后退按钮?
Murray Sagal's

这很简单,但是只有当您确定要从可能加载的任何子视图返回该视图时,它才起作用。如果子级在返回父级时跳过此视图,则不会调用您的代码(该视图在未从父级移开的情况下已经消失了)。但这是同样的问题,仅按OP的要求在“后退”按钮触发时处理事件。因此,这是对他的问题的简单答案。
CMont17年

这是超级简单而优雅的。我喜欢它。仅有的一个问题:即使用户在中途取消,只要用户向后滑动,这也会触发。也许更好的解决方案是将这些代码放入viewDidDisappear。这样一来,只有在视图消失后才会触发。
Phontaine Judd

1

@William的答案是正确的,但是,如果用户启动了向后滑动手势,则该viewWillDisappear方法将被调用,即使滑动self也不会出现在导航堆栈中(即,self.navigationController.viewControllers将不包含self)尚未完成,并且实际上未弹出视图控制器。因此,解决方案将是:

  1. 禁用向后滑动手势viewDidAppear,仅允许使用“后退”按钮,方法是:

    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)])
    {
        self.navigationController.interactivePopGestureRecognizer.enabled = NO;
    }
  2. 或简单地viewDidDisappear改为使用,如下所示:

    - (void)viewDidDisappear:(BOOL)animated
    {
        [super viewDidDisappear:animated];
        if (![self.navigationController.viewControllers containsObject:self])
        {
            // back button was pressed or the the swipe-to-go-back gesture was
            // completed. We know this is true because self is no longer
            // in the navigation stack.
        }
    }

0

到目前为止,我发现的解决方案不是很好,但是对我有用。采取这个答案,我还要检查是否以编程方式弹出:

- (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];

0

找到了新的方法:

物镜

- (void)didMoveToParentViewController:(UIViewController *)parent{
    if (parent == NULL) {
        NSLog(@"Back Pressed");
    }
}

迅速

override func didMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        println("Back Pressed")
    }
}
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.