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.
This package relies on undocumented APIs and runtime behavior, so extra care is needed before using it in App Store-bound projects.
- 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 actions = tabs.map { tab in
UIAction(title: tab.title, image: tab.image) { _ in
tabBarController.selectTabContent(tab)
}
}
return UIMenu(title: "More", children: actions)
}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.

