You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过代码为Navigation Controller的所有ViewController添加右侧Bar Button Item

如何为UINavigationController下所有ViewController统一添加右侧Bar Button Item?

问题描述

在Scene Delegate中创建UINavigationController后,尝试直接给导航控制器设置右侧Bar Button Item,但没有任何ViewController显示该按钮:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
   
    guard let windowScene = (scene as? UIWindowScene) else { return }
 
    let window = UIWindow(windowScene: windowScene)

    let viewController = IntroViewController()
    let navigation = UINavigationController(rootViewController: viewController)
    
    // 这段代码无效,导航栏显示的是栈顶VC的navigationItem
    navigation.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Test", style: .done, target: nil, action: nil)
    navigation.navigationItem.rightBarButtonItem?.tintColor = .red
    
    let standardAppearance = UINavigationBarAppearance()
    standardAppearance.backgroundColor = .black
    
    navigation.navigationBar.standardAppearance = standardAppearance
    navigation.navigationBar.scrollEdgeAppearance = standardAppearance
    

    window.rootViewController = navigation
    self.window = window
    window.makeKeyAndVisible()
}

只有在单个ViewController的viewDidLoad中设置,该VC才会显示按钮:

self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Exit", style: .plain, target: self, action: #selector(exitPressed))

原因分析

UINavigationController自身的navigationItem不会被导航栏显示,导航栏始终展示当前导航栈顶部ViewController的navigationItem,所以直接给导航控制器设置按钮是无效的。

解决方案

方案一:创建UIViewController基类(推荐)

定义一个基类,所有需要显示右侧按钮的ViewController都继承这个基类:

class BaseViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // 统一添加右侧按钮
        let rightButton = UIBarButtonItem(title: "Test", style: .done, target: self, action: #selector(rightButtonTapped))
        rightButton.tintColor = .red
        navigationItem.rightBarButtonItem = rightButton
    }
    
    @objc func rightButtonTapped() {
        // 通用点击逻辑,子类可重写
        print("右侧按钮被点击")
    }
}

修改业务ViewController的继承关系:

class IntroViewController: BaseViewController {
    // 若需自定义按钮,可重写viewDidLoad或rightButtonTapped方法
}

优点:逻辑清晰、易维护,子类可灵活扩展,是最安全的方案。

方案二:利用UINavigationController代理

通过导航控制器的代理方法,在ViewController即将显示时统一设置按钮:

  1. 让SceneDelegate遵守UINavigationControllerDelegate
  2. 在SceneDelegate中添加代码:
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let windowScene = (scene as? UIWindowScene) else { return }
 
    let window = UIWindow(windowScene: windowScene)

    let viewController = IntroViewController()
    let navigation = UINavigationController(rootViewController: viewController)
    // 设置代理
    navigation.delegate = self
    
    let standardAppearance = UINavigationBarAppearance()
    standardAppearance.backgroundColor = .black
    
    navigation.navigationBar.standardAppearance = standardAppearance
    navigation.navigationBar.scrollEdgeAppearance = standardAppearance
    
    window.rootViewController = navigation
    self.window = window
    window.makeKeyAndVisible()
}

// MARK: - UINavigationControllerDelegate
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
    // 避免重复设置按钮
    guard viewController.navigationItem.rightBarButtonItem == nil else { return }
    
    let rightButton = UIBarButtonItem(title: "Test", style: .done, target: viewController, action: #selector(viewController.rightButtonTapped))
    rightButton.tintColor = .red
    viewController.navigationItem.rightBarButtonItem = rightButton
}
  1. 给UIViewController添加扩展实现点击方法:
extension UIViewController {
    @objc func rightButtonTapped() {
        print("右侧按钮被点击")
        // 子类可重写此方法实现自定义逻辑
    }
}

优点:无需修改现有ViewController的继承关系,适合已有项目。

方案三:方法Swizzling(谨慎使用)

通过运行时替换viewDidLoad方法,自动给所有ViewController添加按钮:

extension UIViewController {
    static func swizzleViewDidLoad() {
        let originalSelector = #selector(viewDidLoad)
        let swizzledSelector = #selector(swizzledViewDidLoad)
        
        guard let originalMethod = class_getInstanceMethod(self, originalSelector),
              let swizzledMethod = class_getInstanceMethod(self, swizzledSelector) else { return }
        
        method_exchangeImplementations(originalMethod, swizzledMethod)
    }
    
    @objc func swizzledViewDidLoad() {
        // 先执行原viewDidLoad逻辑
        self.swizzledViewDidLoad()
        
        // 排除UINavigationController本身,避免重复设置
        guard self is UINavigationController == false else { return }
        
        if navigationItem.rightBarButtonItem == nil {
            let rightButton = UIBarButtonItem(title: "Test", style: .done, target: self, action: #selector(rightButtonTapped))
            rightButton.tintColor = .red
            navigationItem.rightBarButtonItem = rightButton
        }
    }
    
    @objc func rightButtonTapped() {
        print("右侧按钮被点击")
    }
}

在SceneDelegate的scene方法开头调用Swizzle:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // 执行Swizzle
    UIViewController.swizzleViewDidLoad()
    
    // 后续原有代码...
}

注意:Swizzling属于运行时黑魔法,可能与第三方库冲突,调试难度大,仅在必要时使用。


内容的提问来源于stack exchange,提问作者Shiv Nik

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 10:06:22