咨询:点击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
相关产品推荐
相关产品推荐

