如何在调用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
相关产品推荐
相关产品推荐

