在导航控制器设置后退按钮动作(Setting action for back button in n

2019-10-22 06:34发布

我试图覆盖后退按钮在导航控制器的默认操作。 我提供了一个目标,自定义按钮的动作。 奇怪的是分配给它的时候虽然后退按钮属性不重视他们,这只是弹出当前视图并返回到根:

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

当我把它通过leftBarButtonItemnavigationItem调用我的动作,但是那么按钮看起来像一个普通的圆而不是一个带箭头的后面一个:

self.navigationItem.leftBarButtonItem = backButton;

我怎样才能得到它,然后回到根视图叫我的自定义操作? 有没有办法覆盖默认回的动作,还是有离去的视图(时总是调用的方法viewDidUnload没有做到这一点)?

Answer 1:

尝试把这个到您要检测的按视图控制器:

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


Answer 2:

我实现的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
}

下载示例应用程序 。



Answer 3:

不像Amagrammer说,这是可能的。 你必须继承你的navigationController 。 我解释了一切在这里 (包括示例代码)。



Answer 4:

斯威夫特版本:

(的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
    }
}


Answer 5:

这是不可能直接做。 有几个备选方案:

  1. 创建自己的自定义UIBarButtonItem ,用于验证自来水,如果测试通过弹出
  2. 验证使用表单字段内容UITextField委托方法,如-textFieldShouldReturn:其在后称为ReturnDone按钮被按下键盘上

第一个选项的缺点是后退按钮的左向箭头样式不能从自定义栏按钮来访问。 所以,你必须使用一个图像或一个普通样式的按钮去。

因为你得到的文本字段后面的委托方法,这样你就可以针对你的验证逻辑发送到委托的回调方法的具体文本字段中的第二个选项是很好的。



Answer 6:

对于某些线程的原因,通过@HansPinckaers mentionned的解决方案是不适合我,但我发现了一个非常简单的方法捉对后退按钮触摸,我想这脚倒在这里的情况下,这可能避免欺骗的小时其他人。 诀窍是非常简单:只需添加一个透明的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
    }
}

有趣的事实:用于测试目的,并找到了我的假按钮好尺寸,我设置了蓝色背景...它显示了背面按钮后面 ! 无论如何,它还是捕捉任何触摸靶向原来的按钮。



Answer 7:

这种技术可以让你改变“后退”按钮上的文字,而不会影响任何视图控制器的标题或看到动画过程中后退按钮文字变化。

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

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


Answer 8:

最简单的方法

您可以使用的UINavigationController的委托方法。 该方法willShowViewController被称为当你的VC的后退按钮是任何你想回来的时候按下BTN pressed.do

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


Answer 9:

发现保留了返回按钮风格以及溶液。 下面的方法添加到您的视图控制器。

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

它是所有来覆盖一个透明的按钮返回按钮;)



Answer 10:

我不相信这是可能的,很容易。 我认为解决这个问题的唯一办法就是让自己后退按钮箭头图像放置在那里。 这是令人沮丧的我在第一,但我知道原因,出于一致性的缘故,所以它被放置。

您可以通过创建一个常规按键和隐藏的默认后退按钮亲近(不带箭头):

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


Answer 11:

有仅通过子类的委托方法更简单的方法UINavigationBar覆盖 ShouldPopItem 方法



Answer 12:

这里是我的迅速解决。 在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
    }

}


Answer 13:

onegray的解决方案是不safe.According通过苹果的官方文件https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html ,我们应该避免这样做。

“如果在一个类中声明的方法的名称是一样的,在原来的类中的方法,或在同一类其他类别的方法(甚至是超类),为使用哪种方法实施的行为是未定义在运行时,这是不太可能,如果你正在使用的类别有自己的类,但使用分类方法添加到标准可可或可可触摸类时,可能会导致问题是一个问题。”



Answer 14:

使用斯威夫特:

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


Answer 15:

这是斯威夫特3版本@Oneway的答案吧被炒鱿鱼之前赶上导航栏后退按钮事件。 由于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")       
}


Answer 16:

通过使用您正在离开“零”目标和行动变量,你应该能够连线您保存的对话框中,所以当按钮被“选中”,他们被称为。 小心,这可能会在瞬间怪触发。

我同意大多与Amagrammer,但我不认为这将是很难做出与箭头自定义的按钮。 我只想重新命名返回按钮,采取了截屏,PHOTOSHOP需要按钮的大小,并有一个在你的按钮上方的图像。



Answer 17:

您可以尝试访问NavigationBars右侧按钮项并设置其属性选择...继承人引用的UIBarButtonItem参考 ,如果将工作变形点焊工作doenst就是另外一回事,导航栏的右键项设置为自定义的UIBarButtonItem你创建并设置其选择......希望这有助于



Answer 18:

对于需要用户输入这样的形式,我会建议调用它作为一个“多态”,而不是你的导航堆栈的一部分。 他们要照顾生意的形式这样的话,那么你可以验证它使用自定义按钮驳回。 你甚至可以设计一个导航栏,看起来与您的应用程序的其余部分,但给你更多的控制权。



Answer 19:

拦截返回按钮,简单地用透明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


Answer 20:

至少在Xcode 5,有一个简单的和相当不错的(不完美)的解决方案。 在IB,将一个栏按钮项关闭工具窗格,并将其放到了导航栏左侧,其中后退按钮会。 将标签设置为“回”。 你将有一个功能按键,可配合你的IBAction为并关闭您的viewController。 我做了一些工作,然后触发开卷SEGUE和它完美的作品。

什么是不理想的是,这个按钮并没有得到<箭头,不发扬以前的风险投资头衔,但我认为这是可以管理的。 对于我而言,我设置了新的后退按钮来“完成”按钮,它的目的是明确的。

您也结束了在IB导航两个按钮,但它是很容易把它归类为清楚起见。



Answer 21:

迅速

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
}


Answer 22:

这种方法为我工作(但“后退”按钮将不会有“<”号):

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


Answer 23:

使用isMovingFromParentViewController

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

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


Answer 24:

斯威夫特4的iOS 11.3版本:

这建立在从kgaidis从答案https://stackoverflow.com/a/34343418/4316579

我不知道当分机停止工作,但在写这篇文章(SWIFT 4)的时候,它出现的扩展将不再除非你声明UINavigationBarDelegate符合下述执行。

希望这可以帮助那些想知道为什么他们的扩展不再工作的人。

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

    }
}


Answer 25:

从@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. } } 


Answer 26:

异步方式的建筑,位于Swift5UIAlert以前的回复


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)
            })
            ])

    }

}


Answer 27:

到目前为止,我已经找到了解决方案不是很漂亮,但它为我工作。 以这个答案 ,我还要检查我是否弹出编程或不:

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


Answer 28:

找到了新的方式来做到这一点:

Objective-C的

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

迅速

override func didMoveToParentViewController(parent: UIViewController?) {
    if parent == nil {
        println("Back Pressed")
    }
}


Answer 29:

@ 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而这种行为在默认情况下采用。



文章来源: Setting action for back button in navigation controller