UITapGestureRecognizer疑似Bug求助:自定义导航栏菜单按钮问题
我来帮你搞定这个基础视图控制器的实现,这样所有子类ViewController都能直接复用导航栏右侧的菜单按钮功能,思路和代码细节如下:
实现步骤
1. 先封装自定义的菜单按钮视图 NavigationMenuButtonView
这个视图要独立负责菜单的显示、隐藏以及交互逻辑,这样基类ViewController不用操心菜单的细节,直接用就行:
import UIKit class NavigationMenuButtonView: UIView { // 触发菜单的按钮 private let menuTriggerBtn: UIButton = { let btn = UIButton(type: .system) btn.setTitle("菜单", for: .normal) btn.titleLabel?.font = UIFont.systemFont(ofSize: 16) btn.tintColor = .black return btn }() // 要显示的菜单视图 private let menuContentView: UIView = { let view = UIView() view.backgroundColor = .white view.layer.cornerRadius = 8 view.layer.shadowColor = UIColor.black.cgColor view.layer.shadowOpacity = 0.15 view.layer.shadowOffset = CGSize(width: 0, height: 2) view.isHidden = true view.alpha = 0 return view }() override init(frame: CGRect) { super.init(frame: frame) setupSubviews() bindActions() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { // 添加触发按钮到当前视图 addSubview(menuTriggerBtn) menuTriggerBtn.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ menuTriggerBtn.leadingAnchor.constraint(equalTo: leadingAnchor), menuTriggerBtn.trailingAnchor.constraint(equalTo: trailingAnchor), menuTriggerBtn.topAnchor.constraint(equalTo: topAnchor), menuTriggerBtn.bottomAnchor.constraint(equalTo: bottomAnchor) ]) // 将菜单视图添加到窗口,确保能覆盖所有页面内容 guard let window = UIApplication.shared.connectedScenes .compactMap({ $0 as? UIWindowScene }) .first?.windows.first else { return } window.addSubview(menuContentView) menuContentView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ menuContentView.trailingAnchor.constraint(equalTo: window.trailingAnchor, constant: -16), menuContentView.topAnchor.constraint(equalTo: window.safeAreaLayoutGuide.topAnchor, constant: 60), menuContentView.widthAnchor.constraint(equalToConstant: 180), menuContentView.heightAnchor.constraint(equalToConstant: 240) ]) } private func bindActions() { // 绑定按钮点击事件,切换菜单显示状态 menuTriggerBtn.addTarget(self, action: #selector(toggleMenuDisplay), for: .touchUpInside) // 添加空白处点击隐藏菜单的手势 let tapGesture = UITapGestureRecognizer(target: self, action: #selector(hideMenuWhenTapOutside)) tapGesture.cancelsTouchesInView = false guard let window = UIApplication.shared.connectedScenes .compactMap({ $0 as? UIWindowScene }) .first?.windows.first else { return } window.addGestureRecognizer(tapGesture) } @objc private func toggleMenuDisplay() { menuContentView.isHidden = !menuContentView.isHidden // 添加平滑的动画效果 UIView.animate(withDuration: 0.25) { self.menuContentView.alpha = self.menuContentView.isHidden ? 0 : 1 } } @objc private func hideMenuWhenTapOutside(_ gesture: UITapGestureRecognizer) { let menuPoint = gesture.location(in: menuContentView) let btnPoint = gesture.location(in: self) // 判断点击位置不在菜单和按钮上,就隐藏菜单 if !menuContentView.bounds.contains(menuPoint) && !menuTriggerBtn.bounds.contains(btnPoint) { menuContentView.isHidden = true UIView.animate(withDuration: 0.25) { self.menuContentView.alpha = 0 } } } }
2. 创建基础视图控制器 BaseViewController
所有业务页面的ViewController都继承这个基类,它会自动配置导航栏右侧的菜单按钮:
import UIKit class BaseViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() setupUniversalNavigationBar() } private func setupUniversalNavigationBar() { // 初始化自定义菜单按钮视图,设置合适的尺寸(适配导航栏高度) let menuBtnView = NavigationMenuButtonView(frame: CGRect(x: 0, y: 0, width: 60, height: 44)) // 将自定义视图设置为导航栏右侧按钮 navigationItem.rightBarButtonItem = UIBarButtonItem(customView: menuBtnView) // 可以在这里设置所有页面通用的导航栏样式 navigationController?.navigationBar.backgroundColor = .white navigationController?.navigationBar.tintColor = .black navigationController?.navigationBar.shadowImage = UIImage() } }
3. 子类ViewController直接继承使用
之后你的业务页面只需要继承BaseViewController,就能自动拥有导航栏右侧的菜单按钮了:
class HomeViewController: BaseViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .lightGray title = "首页" // 这里可以添加当前页面的业务逻辑,菜单功能已经自动具备 } }
额外优化建议
- 如果不同页面需要不同的菜单内容,可以给
NavigationMenuButtonView添加代理方法或者配置接口,让子类ViewController可以自定义菜单里的元素。 - 如果菜单只需要在当前页面显示,也可以把
menuContentView添加到当前ViewController的view上,调整约束即可。 - 记得处理内存问题,比如菜单视图的手势可以在视图销毁时移除,避免内存泄漏。
内容的提问来源于stack exchange,提问作者user2876886
相关产品推荐
相关产品推荐

