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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 08:16:40