Skip to content

在 iPadOS 18 中恢复底部标签栏

问题描述

iPadOS 18 引入了全新的 UITabBarController 设计样式,默认使用侧边栏风格的悬浮标签栏(如图所示)。
iPadOS 18 新标签栏样式

许多开发者希望恢复 iOS 17 及之前版本的底部标签栏设计,主要因为:

  • 用户习惯原有底部导航布局
  • 新样式与现有界面设计不协调
  • 部分应用功能依赖底部标签栏实现

兼容性说明

以下解决方案针对 iPadOS 18+ 设备生效,iOS 17 及以下系统不需要进行特殊处理

推荐解决方案

方法一:覆盖水平尺寸类(推荐)

SwiftUI 实现

swift
TabView {
    Tab("功能1", systemImage: "star") {
        ContentView1()
            .environment(\.horizontalSizeClass, originalSizeClass) // 恢复原始尺寸类
    }

    Tab("功能2", systemImage: "heart") {
        ContentView2()
            .environment(\.horizontalSizeClass, originalSizeClass)
    }
}
.environment(\.horizontalSizeClass, .compact) // 👈 关键修改

UIKit 实现

swift
class CustomTabBarController: UITabBarController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 核心设置
        if #available(iOS 18.0, *) {
            traitOverrides.horizontalSizeClass = .compact // 👈 恢复底部标签栏
        }
    }
}

处理超过 5 个标签

如果需要显示超过 5 个标签项,使用 .unspecified 替代 .compact

swift
traitOverrides.horizontalSizeClass = .unspecified

解决子视图副作用

覆盖尺寸类会影响全部子视图控制器,需单独处理每个子控制器:

swift
class ChildViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        if #available(iOS 18.0, *) {
            // 从窗口获取原始尺寸类
            if let windowSize = view.window?.traitCollection.horizontalSizeClass {
                traitOverrides.horizontalSizeClass = windowSize
            }
        }
    }
}

屏幕旋转处理

添加以下代码避免旋转后布局异常:

swift
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
    }
}

macOS 特殊场景处理

对于在 macOS (Apple Silicon) 上运行的 iPad 应用:

swift
class MacCompatibleTabController: UITabBarController {
    override func viewDidLoad() {
        super.viewDidLoad()
        if #available(iOS 18, *), ProcessInfo.processInfo.isiOSAppOnMac {
            mode = .tabSidebar
            sidebar?.isHidden = true // 隐藏顶部标签栏
            traitOverrides.horizontalSizeClass = .compact // 启用底部标签栏
        }
    }
}

替代方案(不推荐)

通过 UserDefaults 禁用新样式

swift
@main
struct MyApp: App {
    init() {
        UserDefaults(suiteName: "com.apple.UIKit")?.set(false, forKey: "UseFloatingTabBar")
    }
}

不推荐原因

  1. 使用私有 API 可能导致 App Store 审核拒绝
  2. 需在 UI 初始化前设置
  3. 系统升级可能导致失效

兼容未来系统

Info.plist 中添加兼容性标志:

xml
<key>UIDesignRequiresCompatibility</key>
<false/>

最佳实践总结

  1. 首选方案:使用 traitOverrides.horizontalSizeClass = .compact
  2. 所有子控制器:必须恢复原始尺寸类
  3. macOS 应用:需额外设置 modeisHidden
  4. 避免方法:UserDefaults 和私有 API 可能带来长期维护问题
  5. 持续关注:苹果可能在未来版本提供官方 API

长期建议

苹果在 iPadOS 18+ 中推广新设计模式,长期维护应优先考虑适配新设计,而非恢复旧样式