Disable New UITabBarController Style in iPadOS 18
Problem Statement
iPadOS 18 introduces a new floating tab bar design that moves tabs to the top of the screen. While some may welcome this update, many existing apps need to maintain the classic bottom-aligned tab bar for UX consistency, design compatibility, or existing customizations. This article explains how to revert to the pre-iPadOS 18 tab bar appearance across SwiftUI and UIKit.
Recommended Solution
Override Horizontal Size Class
The most reliable method is overriding the horizontal size class to .compact
or .unspecified
. This preserves Apple's official APIs while achieving the desired appearance:
UIKit Implementation:
class CustomTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 18.0, *), UIDevice.current.userInterfaceIdiom == .pad {
traitOverrides.horizontalSizeClass = .compact // Use .unspecified to show all tabs
}
}
}
SwiftUI Implementation:
TabView {
// Your tabs here
}
.environment(\.horizontalSizeClass, .compact)
Key Differences
.compact
: Bottom-aligned tabs with "More" menu (ideal for ≤ 5 tabs).unspecified
: Bottom-aligned tabs without truncation (ideal for > 5 tabs)
Prevent Side Effects in Child Views
Overriding the size class affects child view controllers. Restore the original traits in child content:
UIKit:
class ChildViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 17.0, *) {
traitOverrides.horizontalSizeClass = .regular
}
}
}
SwiftUI:
struct ChildView: View {
@Environment(\.horizontalSizeClass) private var originalSizeClass
var body: some View {
ContentView()
.environment(\.horizontalSizeClass, originalSizeClass)
}
}
macOS Compatibility
For iPad apps running on macOS Sequoia:
class MacCompatibleTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
if #available(iOS 18.0, *), ProcessInfo.processInfo.isiOSAppOnMac {
mode = .tabSidebar
sidebar.isHidden = true
traitOverrides.horizontalSizeClass = .compact
}
}
}
Alternative Approaches
UserDefaults Method (Use with Caution)
@main
struct MyApp: App {
init() {
UserDefaults(suiteName: "com.apple.UIKit")?.set(false, forKey: "UseFloatingTabBar")
}
}
Compatibility Risks
- Must be set before UI initialization
- Uses private APIs (may cause App Store rejection)
- Affects all UIKit components in your app
Rotation Workaround
Fix layout issues during device rotation:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
if #available(iOS 18.0, *) {
traitOverrides.horizontalSizeClass = .regular
traitOverrides.horizontalSizeClass = .compact
}
}
Comprehensive Solution
This combines all best practices with side-effect prevention:
class OptimizedTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
configureForiPadOS18()
}
private func configureForiPadOS18() {
guard #available(iOS 18.0, *),
UIDevice.current.userInterfaceIdiom == .pad else { return }
traitOverrides.horizontalSizeClass = .compact
// Restore original traits for children
viewControllers?.forEach {
$0.traitOverrides.horizontalSizeClass = UIApplication.shared
.firstKeyWindow?
.traitCollection.horizontalSizeClass
}
// macOS-specific fixes
if ProcessInfo.processInfo.isiOSAppOnMac {
mode = .tabSidebar
sidebar.isHidden = true
}
}
}
// Helper extension
extension UIApplication {
var firstKeyWindow: UIWindow? {
connectedScenes
.compactMap { $0 as? UIWindowScene }
.first { $0.windows.count > 0 }
.flatMap { $0.windows.first(where: \.isKeyWindow) }
}
}
Important Considerations
Xcode 16+ Compatibility:
Add runtime checks for newer SDKs:swift#if compiler(>=6.0) if #available(iOS 18.0, *) { /* ... */ } #endif
Trait Inheritance:
Always restore original traits in child view controllers to avoid unexpected layout issues
Future Updates:
These solutions rely on current UIKit behavior. Test with each new iOS beta and be prepared to adjust implementations as Apple's APIs evolve.
By implementing these solutions, your app will maintain the classic tab bar appearance while remaining compatible with iPadOS 18+ and future OS versions.