在 iPad 上替代“底部 TabBar 主导航”、实现「侧边栏 + 顶部导航」的多层级高效导航,核心是利用 iOS 原生的 UISplitViewController(拆分视图控制器)结合 UINavigationController,适配大屏交互逻辑的同时兼顾小尺寸/竖屏的折叠场景。以下是具体实现步骤 + 关键代码 + 体验优化,以 Swift 为例:
一、核心思路
iPad 横屏时:左侧展示「侧边栏(菜单)」,右侧展示「顶部导航 + 内容区」,侧边栏可收起/展开;
iPad 竖屏/分屏小尺寸时:侧边栏自动折叠为「顶部导航的下拉菜单」或「全屏时的侧边抽屉」,保持操作一致性;
多层级管理:侧边栏控制一级分类,顶部导航控制二级/三级功能,内容区展示具体页面。
二、具体实现步骤
1. 基础架构搭建(SplitViewController + NavigationController)
UISplitViewController 是 iPad 侧边栏的核心组件,默认分为「主视图(Primary)」和「详情视图(Secondary)」,主视图放侧边栏,详情视图嵌套 UINavigationController 承载顶部导航 + 内容。
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// 1. 创建侧边栏(主视图)
let sidebarVC = SidebarViewController()
let sidebarNav = UINavigationController(rootViewController: sidebarVC)
// 2. 创建详情区(顶部导航 + 初始内容)
let homeVC = HomeViewController()
let detailNav = UINavigationController(rootViewController: homeVC)
// 3. 初始化 SplitViewController
let splitVC = UISplitViewController(style: .doubleColumn)
splitVC.preferredStyle = .sidebar // 强制侧边栏样式(iPad 优先)
splitVC.setViewController(sidebarNav, for: .primary)
splitVC.setViewController(detailNav, for: .secondary)
// 4. 配置 SplitView 行为
splitVC.preferredSplitBehavior = .tile // 横屏时侧边栏固定,不自动隐藏
splitVC.presentsWithGesture = true // 支持滑动呼出/收起侧边栏
splitVC.delegate = self // 监听折叠/展开状态
// 5. 设置根视图
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = splitVC
window?.makeKeyAndVisible()
return true
}
}
// MARK: - SplitViewController 代理(处理折叠/展开)
extension AppDelegate: UISplitViewControllerDelegate {
func splitViewController(_ svc: UISplitViewController, topColumnForCollapsingToProposedTopColumn proposedTopColumn: UISplitViewController.Column) -> UISplitViewController.Column {
// 折叠时优先保留详情区(顶部导航)
return .secondary
}
}
2. 实现侧边栏(SidebarViewController)
侧边栏用 UITableView 实现一级菜单,选中项时触发详情区的页面跳转,适配 iPad 触控尺寸(行高、间距放大)。
class SidebarViewController: UITableViewController {
// 一级菜单数据
let menuItems = [
("首页", UIImage(systemName: "house")),
("文档", UIImage(systemName: "doc.text")),
("设置", UIImage(systemName: "gear")),
("关于", UIImage(systemName: "info.circle"))
]
override func viewDidLoad() {
super.viewDidLoad()
// 适配 iPad 样式
tableView.rowHeight = 60 // 行高比手机端大(手机端通常44-50pt)
tableView.separatorInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)
tableView.backgroundColor = .systemBackground
// 隐藏侧边栏自身的导航栏(仅保留详情区顶部导航)
navigationController?.setNavigationBarHidden(true, animated: false)
}
// MARK: - TableView 代理
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return menuItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: "SidebarCell")
let (title, icon) = menuItems[indexPath.row]
cell.textLabel?.text = title
cell.textLabel?.font = UIFont.systemFont(ofSize: 16, weight: .medium) // 字号放大
cell.imageView?.image = icon
cell.imageView?.tintColor = .systemBlue
cell.accessoryType = .disclosureIndicator // 指示可跳转
// 最小点击区域确保 44pt×44pt(iPad 触控更友好)
cell.contentView.layoutMargins = UIEdgeInsets(top: 10, left: 16, bottom: 10, right: 16)
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
// 获取 SplitView 的详情区导航控制器
guard let splitVC = self.splitViewController,
let detailNav = splitVC.viewController(for: .secondary) as? UINavigationController else {
return
}
// 根据选中项跳转对应页面(二级内容)
switch indexPath.row {
case 0:
detailNav.setViewControllers([HomeViewController()], animated: true)
case 1:
detailNav.setViewControllers([DocumentViewController()], animated: true)
case 2:
detailNav.setViewControllers([SettingViewController()], animated: true)
case 3:
detailNav.setViewControllers([AboutViewController()], animated: true)
default:
break
}
}
}
3. 详情区顶部导航(UINavigationController)
详情区的每个页面嵌套在 UINavigationController 中,实现二级/三级导航,同时适配 iPad 顶部导航的样式:
class HomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
// 1. 配置顶部导航栏(iPad 样式)
navigationItem.title = "首页"
navigationController?.navigationBar.prefersLargeTitles = true // 大标题(iPad 更醒目)
navigationItem.largeTitleDisplayMode = .always
// 2. 添加二级操作按钮(顶部导航右侧)
let addBtn = UIBarButtonItem(
barButtonSystemItem: .add,
target: self,
action: #selector(addAction)
)
let filterBtn = UIBarButtonItem(
image: UIImage(systemName: "slider.horizontal.3"),
style: .plain,
target: self,
action: #selector(filterAction)
)
navigationItem.rightBarButtonItems = [addBtn, filterBtn]
// 3. 适配 iPad 内容区布局(示例:添加一个列表)
let listView = UITableView(frame: view.bounds)
listView.rowHeight = 60
listView.dataSource = self
view.addSubview(listView)
listView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
listView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
listView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
listView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
listView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
}
@objc func addAction() {
// 跳转到三级页面(示例:新建文档)
let createVC = CreateDocumentViewController()
navigationController?.pushViewController(createVC, animated: true)
}
@objc func filterAction() {
// 顶部导航的二级操作(示例:筛选)
let filterVC = FilterViewController()
filterVC.modalPresentationStyle = .popover // iPad 弹出式弹窗(不占满屏)
filterVC.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItems?[1]
present(filterVC, animated: true)
}
}
// MARK: - 内容区列表适配
extension HomeViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 20
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "HomeCell")
cell.textLabel?.text = "内容项 \(indexPath.row + 1)"
cell.detailTextLabel?.text = "iPad 适配的详情描述"
cell.detailTextLabel?.font = UIFont.systemFont(ofSize: 14)
return cell
}
}
4. 适配折叠/分屏场景(关键优化)
当 iPad 竖屏、分屏小尺寸时,侧边栏会自动折叠,需在详情区顶部导航添加「侧边栏呼出按钮」,保证操作入口不丢失:
// 在详情区的基类 ViewController 中统一处理
class BaseDetailViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// 添加侧边栏呼出按钮(仅折叠时显示)
let sidebarBtn = UIBarButtonItem(
image: UIImage(systemName: "sidebar.left"),
style: .plain,
target: self,
action: #selector(toggleSidebar)
)
navigationItem.leftBarButtonItem = sidebarBtn
}
@objc func toggleSidebar() {
// 触发 SplitView 展开/收起侧边栏
splitViewController?.show(.primary)
}
// 监听 SplitView 折叠状态,动态显示/隐藏呼出按钮
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let splitVC = splitViewController else { return }
// 折叠状态(竖屏/小尺寸)显示呼出按钮,展开状态隐藏
navigationItem.leftBarButtonItem?.isHidden = !splitVC.isCollapsed
}
}
// 让所有详情页面继承 BaseDetailViewController
class HomeViewController: BaseDetailViewController { /* ... */ }
class DocumentViewController: BaseDetailViewController { /* ... */ }
三、体验优化要点(iPad 专属)
-
控件尺寸适配
- 侧边栏行高 ≥ 60pt,顶部导航按钮点击区域 ≥ 44pt×44pt;
- 内容区文本字号比手机端大 2-4pt(如正文 16pt,副标题 14pt);
- 弹窗优先使用
UIPopoverPresentationController(iPad 弹出式),而非占满屏的modal。
-
横竖屏切换适配
- 重写
viewWillTransition(to:with:)监听屏幕旋转,调整 SplitView 布局:override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate { _ in self.tableView.reloadData() // 刷新内容区布局 self.navigationItem.leftBarButtonItem?.isHidden = !self.splitViewController?.isCollapsed ?? false } }
- 重写
-
台前调度(Stage Manager)适配
- 支持多窗口:在
Info.plist中开启Application Scene Manifest→Enable Multiple Windows= YES; - 确保每个窗口的 SplitView 独立工作,不共享状态。
- 支持多窗口:在
-
键盘/指针交互适配
- 顶部导航支持硬件键盘快捷键(如
Cmd+1跳转到首页):override var keyCommands: [UIKeyCommand]? { return [ UIKeyCommand(input: "1", modifierFlags: .command, action: #selector(jumpToHome)), UIKeyCommand(input: "2", modifierFlags: .command, action: #selector(jumpToDocument)) ] } - 为侧边栏/顶部导航按钮添加指针悬停效果:
let pointerInteraction = UIPointerInteraction(delegate: self) addBtn.addInteraction(pointerInteraction)
- 顶部导航支持硬件键盘快捷键(如
四、替代方案(非原生)
如果需要更自定义的侧边栏(如可拖拽宽度、自定义样式),可使用第三方库:
SideMenu:轻量级自定义侧边栏,支持 iPad 分屏适配;SwiftUI方案:用NavigationSplitView替代UISplitViewController,代码更简洁(iOS 16+ 支持):struct ContentView: View { var body: some View { NavigationSplitView { // 侧边栏 List(menuItems) { item in NavigationLink(item.title, destination: DetailView(item: item)) } .listRowHeight(60) } detail: { // 详情区(顶部导航 + 内容) NavigationStack { HomeView() .navigationTitle("首页") .navigationBarTitleDisplayMode(.large) } } } }
总结
核心是通过 UISplitViewController(或 SwiftUI 的 NavigationSplitView)实现「侧边栏(一级)+ 顶部导航(二级/三级)」的结构,兼顾 iPad 大屏的空间优势和分屏/竖屏的折叠场景。关键是适配不同尺寸下的导航入口不丢失、控件尺寸符合 iPad 触控习惯、交互逻辑贴近桌面端效率。