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

如何在调用ViewController中实现通用导航栏按钮的点击事件?

解决方案:将通用导航栏按钮事件交回给调用ViewController处理

刚好之前我也处理过类似的需求,核心就是把点击事件的响应权从UIViewController扩展交回给具体的业务ViewController,下面给你两种实用的实现方式,你可以根据场景选择:


方法一:使用协议(Protocol)实现规范回调

这种方式适合需要统一接口的场景,能让多个ViewController遵循相同的事件处理规范。

步骤1:定义事件协议

首先创建一个协议,用来声明按钮点击的回调方法:

import UIKit

// 定义导航栏按钮的回调协议
protocol NavigationBarButtonDelegate: AnyObject {
    func nextButtonClicked()
}

步骤2:修改通用导航栏扩展

修改你的UIViewController扩展,把按钮点击的触发逻辑转交给协议方法:

@objc extension UIViewController { 
    @objc func setBarButtonItem(titleLabel: String) { 
        let view = UIView(frame: CGRect(x: 0, y: 0, width: 204, height: 38)) 
        let nextBtn = UIButton(type: .custom) 
        nextBtn.frame = CGRect(x: 0, y: 0, width: 30, height: 30) 
        // 先让扩展内部的方法接收点击事件
        nextBtn.addTarget(self, action: #selector(handleNextButtonTap), for: .touchUpInside) 
        view.addSubview(nextBtn) 
        self.navigationItem.setLeftBarButton(UIBarButtonItem(customView: view), animated: true) 
    }

    // 私有方法:统一处理点击,再转交给遵循协议的ViewController
    @objc private func handleNextButtonTap() {
        (self as? NavigationBarButtonDelegate)?.nextButtonClicked()
    } 
}

步骤3:在业务ViewController中实现协议

在需要使用这个导航栏的ViewController里,遵循协议并实现点击逻辑:

class HomeViewController: UIViewController, NavigationBarButtonDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        // 设置导航栏按钮
        setBarButtonItem(titleLabel: "hello...")
    }

    // 实现协议方法,写具体的点击逻辑
    func nextButtonClicked() {
        print("HomeVC里的Next按钮被点击了!")
        // 这里可以写跳转、弹窗等业务逻辑
        let detailVC = DetailViewController()
        navigationController?.pushViewController(detailVC, animated: true)
    }
}

方法二:使用闭包(Closure)实现灵活回调

这种方式更灵活,不需要ViewController遵循任何协议,适合每个ViewController需要自定义不同点击逻辑的场景。

步骤1:修改通用导航栏扩展(增加闭包参数)

我们通过关联对象来保存闭包,避免扩展无法添加存储属性的限制:

import UIKit

@objc extension UIViewController { 
    // 修改方法,增加点击回调的闭包参数
    @objc func setBarButtonItem(titleLabel: String, onNextTap: (() -> Void)?) { 
        let view = UIView(frame: CGRect(x: 0, y: 0, width: 204, height: 38)) 
        let nextBtn = UIButton(type: .custom) 
        nextBtn.frame = CGRect(x: 0, y: 0, width: 30, height: 30) 
        // 用关联对象把闭包绑定到按钮上
        objc_setAssociatedObject(nextBtn, &AssociatedKeys.nextTapClosure, onNextTap, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        nextBtn.addTarget(self, action: #selector(handleNextButtonTap(_:)), for: .touchUpInside) 
        view.addSubview(nextBtn) 
        self.navigationItem.setLeftBarButton(UIBarButtonItem(customView: view), animated: true) 
    }

    @objc private func handleNextButtonTap(_ sender: UIButton) {
        // 取出闭包并执行
        let closure = objc_getAssociatedObject(sender, &AssociatedKeys.nextTapClosure) as? (() -> Void)
        closure?()
    } 
}

// 定义关联对象的Key(私有结构体避免命名冲突)
private struct AssociatedKeys {
    static var nextTapClosure = "nextTapClosure"
}

步骤2:在业务ViewController中传入闭包

调用时直接传入点击后的处理逻辑即可:

class ProfileViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // 设置导航栏按钮,并直接写点击逻辑
        setBarButtonItem(titleLabel: "hello...") { [weak self] in
            guard let self = self else { return }
            print("ProfileVC里的Next按钮被点击了!")
            // 自定义业务逻辑,比如弹出编辑弹窗
            let alert = UIAlertController(title: "提示", message: "你点击了Next按钮", preferredStyle: .alert)
            alert.addAction(UIAlertAction(title: "确定", style: .default))
            self.present(alert, animated: true)
        }
    }
}

两种方式对比

  • 协议方式:适合多个ViewController有统一的点击处理规范,代码结构更清晰,便于维护。
  • 闭包方式:更轻量化、灵活,不需要额外遵循协议,适合每个ViewController的点击逻辑差异较大的场景。

内容的提问来源于stack exchange,提问作者Lalit kumar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 09:18:25