After upgrading my project to iOS 18, I’ve encountered an issue where the UITabBarController
no longer displays the tab bar items or icons properly. The same code has been working flawlessly across iOS versions 11 to 17, so the problem seems specific to iOS 18. Additionally, I've noticed that the selectedIndex
occasionally gets set to Int.max
, which leads to out-of-bounds values and results in the wrong tab being selected.
Expected Behavior:
- Tab bar items/icons should display correctly, as they did in iOS 11–17.
- The first tab should be selected by default.
selectedIndex
should not be set toInt.max
.
Actual Behavior:
- Tab bar items/icons are not displaying correctly.
- The first tab is not selected by default
selectedIndex
gets set toInt.max
, causing defaulting to the wrong tab.
General Notes:
- Xcode Version 16.1 beta (16B5001e)
- iOS 18.1
- I contacted Apple Support and they marked the issue as resolved with no explanation.
Are there any changes to the UITabBarController or tab bar layout behavior in iOS 18 that I need to be aware of? How can I fix or work around this issue to ensure my UITabBarController behaves as it did in previous iOS versions?
Here is the code snippet:
import UIKit
class MainTabBarController: UITabBarController {
enum ViewControllerIndex: Int, CaseIterable {
case channels
var name: String {
switch self {
case .activity: return "Activity"
}
}
}
private var tabBarViewControllers: [UIViewController] = []
var tabItems: [MainTabBarController.ViewControllerIndex] = ViewControllerIndex.allCases
override func viewDidLoad() {
super.viewDidLoad()
let tabBarItems = self.tabBar.items ?? []
for anItem in tabBarItems {
anItem.setTitleTextAttributes(
[NSAttributedString.Key.foregroundColor: UIColor.clear],
for: .normal
)
}
tabBarViewControllers = viewControllers ?? []
updateVisibleViewControllers(animated: false)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Manually laying out tab bar sub views to fix a rendering problem when multitasking view size changes
tabBar.layoutSubviews()
}
func updateVisibleViewControllers(animated: Bool) {
var tabItems: [MainTabBarController.ViewControllerIndex] = []
tabItems.append(contentsOf: [.channels]) // other tabs here
var subset: [UIViewController] = []
for item in tabItems {
subset.append(tabBarViewControllers[item.rawValue])
}
var updatedSelectedIndex = selectedIndex
if self.tabItems.count > selectedIndex {
let selectedSection = self.tabItems[selectedIndex]
updatedSelectedIndex = tabItems.firstIndex(of: selectedSection) ?? 0
}
setViewControllers(subset, animated: false)
self.tabItems = tabItems
if selectedIndex != updatedSelectedIndex {
self.selectedIndex = updatedSelectedIndex
}
}
func select(_ section: MainTabBarController.ViewControllerIndex) {
if let index = self.tabItems.firstIndex(of: section) {
self.selectedIndex = index
}
}
private func indexOfBarItem(_ barItem: UITabBarItem) -> Int {
let items = tabBar.items ?? []
return items.firstIndex(of: barItem) ?? 0
}
}