为什么覆盖 View Controller 的 navigationItem 似乎没有任何作用?

Why doesn't overriding a View Controller's navigationItem seem to do anything?

我现在已经查看了大量关于从 UINavigationBar 上的后退按钮删除文本的帖子。他们似乎不再工作了 (iOS 10+)。即使他们这样做了,他们也没有 "smell correct"...

我的问题是,为什么这行不通?

class MyViewController: UIViewController {

    private var _navItem: UINavigationItem? = nil
    override var navigationItem: UINavigationItem {
        if _navItem == nil {
            let item = super.navigationItem
            let backBarButtonItem = UIBarButtonItem(title: "Back", style: .plain, target: nil, action: nil)
            backBarButtonItem.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.clear], for: .normal)
            backBarButtonItem.setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.clear], for: .highlighted)
            item.backBarButtonItem = backBarButtonItem
            _navItem = item
        }
        return _navItem!
    }
    //...
}

我希望看到一个带有明文的按钮,但我看到前一个视图控制器的标题是后退按钮文本。

我需要继承 UINavigationController 吗?或者乱搞 UINavigationBarDelegate?

尽管 Allen 的回答是正确的,但您需要在 "previous view controller" 而不是您看到后退按钮的那个上执行此操作。

虽然您也可以在同一个控制器上实现您的目标,但这是我的解决方案,

这个函数将return你UIBarButtonItem:

UIBarButtonItem  *bbBack = [self overrideBackBarButtonItemWithTarget:self action:@selector(btnBackTapped:)];

在当前控制器中的某个位置实现选择器:

- (IBAction)btnBackTapped:(id)sender {
    [self.navigationController popViewControllerAnimated:YES];
}

函数:

-(UIBarButtonItem*)overrideBackBarButtonItemWithTarget:(nullable id)target action:(nullable SEL)action  {


    UIButton *btn = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 65 , 44)];
    [btn setImage:[UIImage imageNamed:@"nav-back"] forState:UIControlStateNormal];
    btn.imageEdgeInsets = UIEdgeInsetsMake(0, -15, 0, 15);//move image to the right
    [btn addTarget:target action:action forControlEvents:UIControlEventTouchUpInside];
    UIBarButtonItem *barbuttonBack = [[UIBarButtonItem alloc] initWithCustomView:btn];
    UIBarButtonItem *spacer = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
    spacer.width = -15;

    UIViewController *_self = (UIViewController*)target;
    [_self.navigationItem setLeftBarButtonItems:[NSArray arrayWithObjects:spacer,barbuttonBack, nil] animated:NO];

    return barbuttonBack;
}

Navigation back image

感谢 Allen Humphreys,但我在上面发布的问题确实确保它们在后退按钮上没有文本,但您必须确保所有视图控制器都继承自此 class .

它似乎没有做任何事情,因为我认为设置 backButton 文本是为了当视图控制器本身是当前在堆栈上可见的那个时,但它实际上意味着 "what is the back button title when this view controller is second from the top?"

我的问题出现是因为视图控制器 'second from the top' 没有使用这个覆盖的导航项。

这是一个非常常见的混淆点,因为 API 从表面上看有点违反直觉。 documentation that explains 也有点绕

When this navigation item is immediately below the top item in the stack, the navigation controller derives the back button for the navigation bar from this navigation item. When this property is nil, the navigation item uses the value in its title property to create an appropriate back button.

基本上,您需要在 "previous view controller" 而不是您看到后退按钮的那个上执行此操作。

虽然我不确定你的总体目标是什么,但要做到这一点 "app wide",你有几个选择。

  • 将其放入您整个项目的某种全局基础视图控制器中class。如果您想为应用程序的不同部分使用不同的基础 class,当然在处理不同类型的系统控制器时,这会特别麻烦。

  • UIAppearance 方法,其缺点(或优点?)会影响每个栏按钮项目,包括后退按钮。这个解决方案通常过于宽泛,尽管它无疑是全球性的。

    UIBarButtonItem.appearance().setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.clear], for: .normal)
    UIBarButtonItem.appearance().setTitleTextAttributes([NSForegroundColorAttributeName: UIColor.clear], for: .highlighted)
    
  • 在顶视图控制器上提供一个自定义的左栏按钮项目,您希望后退按钮打开。此解决方案再次需要某种基础 class,或好的 'ol copy-pasta。当然,也失去了为您提供系统后备雪佛龙的能力。另一种选择是扩展(Gabriel 的回答提供了一个很好的例子)。

凡事都有取舍。

我遇到了完全相同的问题,想使用没有文字的图标,但文字不断出现。经过长时间的搜索和大量尝试,我不得不求助于创建自定义按钮并将其设置为 leftBarButtonItem。 相关代码如下:

protocol CustomBackButton {
    func configureBackButton()
    func removeBackButton()
}

extension CustomBackButton where Self: UIViewController {
    func configureBackButton() {
        let backButton = UIButton(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
        backButton.setImage(#imageLiteral(resourceName: "backIconButton"), for: .normal)
        backButton.addTarget(self, action: #selector(didTouchBackButton), for: .touchUpInside)

        self.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backButton)
        self.navigationController?.navigationBar.tintColor = .lightGray
    }

    func removeBackButton() {
        self.navigationItem.hidesBackButton = true
    }

}

extension UIViewController {
    func didTouchBackButton() {
        self.navigationController?.popViewController(animated: true)
    }
}

使用 removeBackButton() 只是因为我有一个弹出窗口 window 不应该有后退按钮,所以我将其删除。