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

咨询:点击UITabBarItem时如何创建指定样式的弹出菜单?

点击UITabBarItem弹出自定义菜单实现方案

核心思路

在tabBar(_:didSelect:)方法中,当点击指定的"Add"项时,创建自定义弹窗菜单,通过获取该TabBarItem的frame精准定位菜单显示位置,配合动画完成弹出效果。


步骤1:自定义弹出菜单视图

封装可复用的菜单视图,支持传入选项列表和点击回调:

// 自定义弹出菜单视图
class PopupMenuView: UIView {
    // 初始化方法,传入选项数组和点击回调
    init(options: [String], selectedHandler: @escaping (Int) -> Void) {
        super.init(frame: .zero)
        backgroundColor = .white
        layer.cornerRadius = 8
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOpacity = 0.2
        layer.shadowOffset = CGSize(width: 0, height: 2)
        
        // 用垂直StackView排列选项按钮
        let stackView = UIStackView()
        stackView.axis = .vertical
        stackView.spacing = 8
        stackView.isLayoutMarginsRelativeArrangement = true
        stackView.layoutMargins = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
        addSubview(stackView)
        stackView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: topAnchor),
            stackView.leadingAnchor.constraint(equalTo: leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: trailingAnchor),
            stackView.bottomAnchor.constraint(equalTo: bottomAnchor)
        ])
        
        // 为每个选项创建按钮
        for (index, option) in options.enumerated() {
            let button = UIButton(type: .system)
            button.setTitle(option, for: .normal)
            button.addTarget(self, action: #selector(optionTapped(_:)), for: .touchUpInside)
            button.tag = index
            stackView.addArrangedSubview(button)
        }
        
        self.selectedHandler = selectedHandler
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private var selectedHandler: ((Int) -> Void)?
    
    @objc private func optionTapped(_ sender: UIButton) {
        selectedHandler?(sender.tag)
        removeFromSuperview()
    }
}

步骤2:在TabBarController中实现弹出逻辑

修改tabBar(_:didSelect:)方法,添加菜单的创建、定位和显示逻辑:

class CustomTabBarController: UITabBarController {
    override func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
        guard item.title == "Add" else { return }
        
        // 获取点击的TabBarItem在TabBar中的frame
        guard let itemIndex = tabBar.items?.firstIndex(of: item),
              let itemView = tabBar.subviews[safe: itemIndex + 1], // TabBar子视图第一个是背景,后续是item视图
              let parentView = tabBar.superview else { return }
        
        // 定义菜单选项
        let menuOptions = ["新建帖子", "上传图片", "创建直播", "发起投票"]
        let menuView = PopupMenuView(options: menuOptions) { index in
            // 处理选项点击逻辑
            print("选中选项:\(menuOptions[index])")
        }
        
        // 计算菜单位置:在TabBarItem上方居中
        let menuWidth: CGFloat = 160
        let buttonHeight: CGFloat = 44
        let padding: CGFloat = 24
        let menuHeight = CGFloat(menuOptions.count) * buttonHeight + padding
        
        let menuX = itemView.frame.midX - menuWidth / 2
        let menuY = tabBar.frame.origin.y - menuHeight
        
        menuView.frame = CGRect(x: menuX, y: menuY, width: menuWidth, height: menuHeight)
        parentView.addSubview(menuView)
        
        // 添加弹出动画
        menuView.alpha = 0
        menuView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
        UIView.animate(withDuration: 0.25) {
            menuView.alpha = 1
            menuView.transform = .identity
        }
        
        // 添加空白点击关闭菜单的手势
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dismissMenu))
        tapGesture.cancelsTouchesInView = false
        parentView.addGestureRecognizer(tapGesture)
    }
    
    @objc private func dismissMenu() {
        guard let parentView = tabBar.superview else { return }
        
        // 移除菜单并清理手势
        parentView.subviews.forEach { subview in
            guard subview is PopupMenuView else { return }
            UIView.animate(withDuration: 0.25, animations: {
                subview.alpha = 0
                subview.transform = CGAffineTransform(scaleX: 0.9, y: 0.9)
            }) { _ in
                subview.removeFromSuperview()
            }
        }
        parentView.gestureRecognizers?.removeAll { $0 is UITapGestureRecognizer }
    }
}

// 扩展数组,安全访问索引
extension Collection {
    subscript(safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

关键注意事项

  • TabBarItem的frame获取:TabBar的子视图中,第一个是背景视图,后续依次是各个TabBarItem的视图,所以索引要+1;也可以通过遍历subviews筛选UITabBarButton类型的视图来更稳定地获取。
  • 菜单样式自定义:可以根据需求修改菜单的背景色、圆角、阴影、按钮字体颜色等样式。
  • 交互优化:除了点击空白关闭,还可以添加菜单的遮罩层,或者支持滑动关闭等交互。

内容的提问来源于stack exchange,提问作者Pavel Andreev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 13:45:15