TabBarMenu is a lightweight Swift Package that adds long-press context menus (UIMenu) to
UITabBarController tabs on iOS 18+. Works with the new UITab API and classic
viewControllers, and can override the system More tab.
- iOS 18.0+
- Swift 6.2 (Swift tools version in
Package.swift)
In Xcode:
- File → Add Packages…
- Enter the repository URL
- Add the TabBarMenu product to your target
- Conform to
TabBarMenuDelegate - Set
menuDelegateon your tab bar controller - Return a
UIMenufor the pressed tab
Tip: You can implement the
UITabdelegate method, theUIViewControllerdelegate method, or both. If both are implemented, TabBarMenu tries theUITabdelegate method first (whenUITabBarController.tabsis available) and falls back to the view-controller delegate method.
import UIKit
import TabBarMenu
final class MainTabBarController: UITabBarController, TabBarMenuDelegate {
override func viewDidLoad() {
super.viewDidLoad()
menuDelegate = self
}
// iOS 18+ UITab-based API
func tabBarController(_ tabBarController: UITabBarController, tab: UITab?) -> UIMenu? {
makeMenu(title: tab?.title)
}
// Classic UIKit (viewControllers-based)
func tabBarController(_ tabBarController: UITabBarController, viewController: UIViewController?) -> UIMenu? {
makeMenu(title: viewController?.tabBarItem.title)
}
private func makeMenu(title: String?) -> UIMenu? {
guard let title else { return nil }
let rename = UIAction(title: "Rename") { _ in
// Handle rename
}
let delete = UIAction(title: "Delete", attributes: .destructive) { _ in
// Handle delete
}
return UIMenu(title: title, children: [rename, delete])
}
}- Return
nilto disable the menu for a specific tab. - Set
menuDelegate = nilto detach TabBarMenu.
Provide a menu for the system More tab (when you have many tabs).
When menuForMoreTabWith… returns a menu, TabBarMenu presents it on tap and suppresses the system More screen.
(Long-press menus are still supported.)
func tabBarController(
_ tabBarController: UITabBarController,
menuForMoreTabWith tabs: [UITab]
) -> UIMenu? {
let titles = tabs.map(\.title).joined(separator: ", ")
return UIMenu(title: "More: \(titles)", children: [])
}If you don’t use UITab, there’s also a menuForMoreTabWith viewControllers: [UIViewController] delegate method.
updateMenuConfiguration { configuration in
configuration.minimumPressDuration = 0.5
configuration.maxVisibleTabCount = 5
}Implement configureMenuPresentationFor… to customize the anchor placement and menu host button.
func tabBarController(
_ tabBarController: UITabBarController,
configureMenuPresentationFor tab: UITab?,
tabFrame: CGRect,
in containerView: UIView,
menuHostButton: UIButton
) -> TabBarMenuAnchorPlacement? {
menuHostButton.preferredMenuElementOrder = .fixed
return .above()
}The tab parameter is nil for the system More tab.
Available placements:
.inside.above(offset:).custom(CGPoint).manual
| Inside placement | Above placement |
|---|---|
![]() |
![]() |
To refresh the menu while it’s visible:
tabBarController.updateTabBarMenu { currentMenu in
guard let currentMenu else { return currentMenu }
let refreshed = currentMenu.replacingChildren(currentMenu.children)
return refreshed
}Open Examples/TabBarDemo/TabBarDemo.xcodeproj and run the TabBarDemo scheme on iOS 18+.
MIT. See LICENSE.

