08 | 视图的控制器 UIViewController
8.1 UIViewController 是什么
UIViewController 是 iOS 中所有页面控制器的基类。它的职责:
- 管理一个 UIView 视图层级 (通过
view属性) - 处理用户交互事件
- 管理页面间的数据传递和跳转
- 响应系统事件(旋转、内存警告、状态栏变化等)
8.2 UIViewController 的创建方式
swift
// 方式一:纯代码
class MyViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
title = "我的页面"
}
}
// 方式二:从 Storyboard 加载
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(withIdentifier: "MyVC")
// 方式三:从 XIB 加载
let vc = MyViewController(nibName: "MyViewController", bundle: nil)
8.3 UIViewController 生命周期(核心重点)
┌──────────────┐
│ init │ ← 初始化控制器对象
└──────┬───────┘
▼
┌─────────────────┐
│ loadView() │ ← 创建/加载 view(一般不手动调用)
└────────┬────────┘
▼
┌─────────────────┐
│ viewDidLoad() │ ← ⭐ 最常用!view 加载完成,只调用一次
└────────┬────────┘
▼
┌─────────────────────┐
│ viewWillAppear() │ ← 即将显示(每次显示前都调用)
└────────┬────────────┘
▼
┌──────────────────────┐
│ viewWillLayoutSubviews│ ← 即将布局子视图
└────────┬─────────────┘
▼
┌───────────────────────┐
│ viewDidLayoutSubviews │ ← 子视图布局完成
└────────┬──────────────┘
▼
┌─────────────────────┐
│ viewDidAppear() │ ← 已显示(每次显示后都调用)
└────────┬────────────┘
│
[用户可见]
│
▼
┌─────────────────────┐
│ viewWillDisappear() │ ← 即将消失(离开页面前)
└────────┬────────────┘
▼
┌─────────────────────┐
│ viewDidDisappear() │ ← 已消失(离开页面后)
└────────┬────────────┘
▼
┌─────────────────┐
│ dealloc │ ← 控制器释放
└─────────────────┘
8.4 各生命周期方法的典型用途
swift
class MyViewController: UIViewController {
// ✅ 初始化 UI 组件、设置数据、发起网络请求
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
fetchData()
}
// ✅ 每次显示前刷新数据、检查登录状态
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
refreshData()
}
// ✅ 启动动画、开始监听(如键盘通知)
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
startAnimation()
}
// ✅ 暂停动画、取消监听、保存临时数据
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
pauseAnimation()
}
// ✅ 停止耗时操作、释放资源
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
stopTimer()
}
}
8.5 系统事件响应
swift
// 内存警告 ------ 释放不必要的缓存
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
imageCache.removeAll()
}
// 设备旋转
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
// 处理横竖屏切换
}
// 状态栏样式
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
8.6 页面跳转方式
swift
// 1. Push(需要 NavigationController)
navigationController?.pushViewController(detailVC, animated: true)
// 2. Present(模态弹出)
present(modalVC, animated: true, completion: nil)
// 3. Segue(Storyboard 连线)
performSegue(withIdentifier: "showDetail", sender: self)
09 | 结合视图和控制器构建 TabBar 样式页面
9.1 什么是 TabBar
TabBar(标签栏)是 iOS 应用最常见的导航模式之一,位于屏幕底部,允许用户在多个主要功能模块之间切换。
典型应用: 微信(聊天/通讯录/发现/我)、支付宝、淘宝
┌─────────────────────────┐
│ NavigationBar │
├─────────────────────────┤
│ │
│ 当前选中的页面内容 │
│ │
├─────────────────────────┤
│ 💬 📱 🔍 👤 │ ← UITabBar
│ 聊天 通讯录 发现 我 │
└─────────────────────────┘
9.2 UITabBarController
UITabBarController 是管理 TabBar 页面的容器控制器:
swift
class MainTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
setupTabs()
customizeTabBar()
}
private func setupTabs() {
// 创建各个页面的控制器
let homeVC = HomeViewController()
let homeNav = UINavigationController(rootViewController: homeVC)
homeNav.tabBarItem = UITabBarItem(
title: "首页",
image: UIImage(systemName: "house"),
selectedImage: UIImage(systemName: "house.fill")
)
let searchVC = SearchViewController()
let searchNav = UINavigationController(rootViewController: searchVC)
searchNav.tabBarItem = UITabBarItem(
title: "搜索",
image: UIImage(systemName: "magnifyingglass"),
selectedImage: UIImage(systemName: "magnifyingglass")
)
let profileVC = ProfileViewController()
let profileNav = UINavigationController(rootViewController: profileVC)
profileNav.tabBarItem = UITabBarItem(
title: "我的",
image: UIImage(systemName: "person"),
selectedImage: UIImage(systemName: "person.fill")
)
// 设置 TabBar 的子控制器
viewControllers = [homeNav, searchNav, profileNav]
}
}
9.3 UITabBarItem 配置
swift
// 方式一:初始化时设置
let item = UITabBarItem(
title: "标题",
image: UIImage(named: "icon_normal"),
selectedImage: UIImage(named: "icon_selected")
)
// 方式二:使用系统样式
let item = UITabBarItem(tabBarSystemItem: .search, tag: 0)
// 方式三:使用 SF Symbols(推荐,iOS 13+)
let item = UITabBarItem(
title: "设置",
image: UIImage(systemName: "gearshape"),
tag: 0
)
// 设置角标(小红点 / 数字)
tabBarItem.badgeValue = "3" // 显示数字
tabBarItem.badgeValue = nil // 隐藏
tabBarItem.badgeColor = .red // 角标颜色
9.4 自定义 TabBar 外观
swift
private func customizeTabBar() {
// 背景色
tabBar.barTintColor = .white
// 选中时的颜色(tintColor)
tabBar.tintColor = .systemBlue
// 未选中时的颜色
tabBar.unselectedItemTintColor = .gray
// 透明效果
tabBar.isTranslucent = false
// iOS 15+ 使用 UITabBarAppearance
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .white
// 设置图标和文字的颜色
let itemAppearance = UITabBarItemAppearance()
itemAppearance.normal.iconColor = .gray
itemAppearance.normal.titleTextAttributes = [.foregroundColor: UIColor.gray]
itemAppearance.selected.iconColor = .systemBlue
itemAppearance.selected.titleTextAttributes = [.foregroundColor: UIColor.systemBlue]
appearance.stackedLayoutAppearance = itemAppearance
tabBar.standardAppearance = appearance
tabBar.scrollEdgeAppearance = appearance
}
9.5 每个 Tab 页面的结构
swift
class HomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
title = "首页"
// 添加内容视图
let label = UILabel()
label.text = "这是首页"
label.font = .systemFont(ofSize: 24, weight: .bold)
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
}
9.6 TabBar 的事件监听
swift
class MainTabBarController: UITabBarController, UITabBarControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self // 设置代理
}
// 点击 Tab 时触发
func tabBarController(_ tabBarController: UITabBarController,
didSelect viewController: UIViewController) {
print("切换到: \(viewController.title ?? "")")
}
// 是否允许切换到某个 Tab
func tabBarController(_ tabBarController: UITabBarController,
shouldSelect viewController: UIViewController) -> Bool {
return true // 返回 false 可以禁止切换
}
}
9.7 在 AppDelegate 中设置
swift
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
window?.rootViewController = MainTabBarController()
window?.makeKeyAndVisible()
return true
}
}
9.8 常见问题
| 问题 | 解决方案 |
|---|---|
| 图标颜色被渲染为蓝色 | 使用 image.withRenderingMode(.alwaysOriginal) |
| 内容被 TabBar 遮挡 | 使用 view.safeAreaLayoutGuide 或设置 additionalSafeAreaInsets |
| 旋转时 TabBar 行为异常 | 在 VC 中限制方向 supportedInterfaceOrientations |
10 | 使用 Navigation 构建 App 的基础交互
10.1 什么是 UINavigationController
UINavigationController 是 iOS 中实现层级导航 的核心组件。它管理一个视图控制器栈(Stack),支持 Push/Pop 操作。
栈顶 ← Push ← [ViewController C] ← 当前显示的
[ViewController B]
栈底 [ViewController A] ← rootViewController
典型应用: 设置页(逐层深入)、邮件详情、商品详情
10.2 核心组件
| 组件 | 说明 |
|---|---|
| UINavigationBar | 顶部导航栏(标题、左右按钮) |
| UINavigationItem | 每个 VC 的导航栏配置(title、leftBarButtonItems、rightBarButtonItems) |
| UIToolbar | 底部工具栏(默认隐藏) |
| viewControllers 栈 | 管理页面层级 |
10.3 Push 和 Pop
swift
// Push:压入新页面(前进)
let detailVC = DetailViewController()
navigationController?.pushViewController(detailVC, animated: true)
// Pop:弹出当前页面(后退一步)
navigationController?.popViewController(animated: true)
// Pop 到指定页面
navigationController?.popToViewController(targetVC, animated: true)
// Pop 到根页面(回到首页)
navigationController?.popToRootViewController(animated: true)
10.4 配置 NavigationBar
swift
// === 在 ViewController 中配置 ===
// 标题
title = "商品详情"
// 或者使用自定义标题视图
navigationItem.titleView = customTitleLabel
// 左侧按钮
navigationItem.leftBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .cancel,
target: self,
action: #selector(cancelTapped)
)
// 右侧按钮
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "编辑",
style: .plain,
target: self,
action: #selector(editTapped)
)
// 多个右侧按钮
navigationItem.rightBarButtonItems = [
UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addTapped)),
UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(searchTapped))
]
// 隐藏返回按钮
navigationItem.hidesBackButton = true
// 自定义返回按钮
let backItem = UIBarButtonItem(
title: "返回",
style: .plain,
target: self,
action: #selector(goBack)
)
navigationItem.leftBarButtonItem = backItem
10.5 自定义 NavigationBar 外观
swift
// iOS 15+ 使用 UINavigationBarAppearance
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .systemBlue
appearance.titleTextAttributes = [.foregroundColor: UIColor.white]
// 大标题样式
navigationItem.largeTitleDisplayMode = .always // .never / .automatic
appearance.largeTitleTextAttributes = [.foregroundColor: UIColor.white]
// 应用到 NavigationBar
navigationController?.navigationBar.standardAppearance = appearance
navigationController?.navigationBar.scrollEdgeAppearance = appearance
// 隐藏导航栏
navigationController?.setNavigationBarHidden(true, animated: true)
// 隐藏底部的阴影线
appearance.shadowColor = nil
10.6 数据传递
正向传值(Push 时传递)
swift
// 方式一:属性传值
let detailVC = DetailViewController()
detailVC.productId = "12345"
detailVC.productName = "iPhone 16"
navigationController?.pushViewController(detailVC, animated: true)
// 方式二:初始化方法传值
class DetailViewController: UIViewController {
let productId: String
init(productId: String) {
self.productId = productId
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError() }
}
反向传值(Pop 时回传)
swift
// 方式一:代理(Delegate)模式 --- 最经典
protocol DetailDelegate: AnyObject {
func detailDidUpdate(data: String)
}
class DetailViewController: UIViewController {
weak var delegate: DetailDelegate?
@objc func saveTapped() {
delegate?.detailDidUpdate(data: "更新的数据")
navigationController?.popViewController(animated: true)
}
}
// 方式二:闭包(Closure)
class DetailViewController: UIViewController {
var onDismiss: ((String) -> Void)?
@objc func saveTapped() {
onDismiss?("更新的数据")
navigationController?.popViewController(animated: true)
}
}
// 调用
let detailVC = DetailViewController()
detailVC.onDismiss = { [weak self] data in
self?.label.text = data
}
navigationController?.pushViewController(detailVC, animated: true)
10.7 Present vs Push 的区别
| 特性 | Push | Present |
|---|---|---|
| 依赖 | 必须在 NavigationController 中 | 任何 VC 都可以 |
| 效果 | 从右侧滑入 | 从底部弹出(默认) |
| 导航栏 | 自动管理 | 需要手动包一层 Nav |
| 适用场景 | 层级导航(列表→详情) | 模态操作(登录、设置、全屏覆盖) |
| 返回方式 | Pop / 左滑返回 | Dismiss |
swift
// Present 示例
let settingsVC = SettingsViewController()
let nav = UINavigationController(rootViewController: settingsVC)
nav.modalPresentationStyle = .pageSheet // iOS 13+ 卡片样式
present(nav, animated: true)
// Dismiss
dismiss(animated: true)
10.8 完整的 Navigation + TabBar 组合
swift
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
window = UIWindow(frame: UIScreen.main.bounds)
// TabBar 包含多个 NavigationController
let tabBarController = UITabBarController()
let homeNav = UINavigationController(rootViewController: HomeViewController())
homeNav.tabBarItem = UITabBarItem(title: "首页",
image: UIImage(systemName: "house"),
tag: 0)
let listNav = UINavigationController(rootViewController: ListViewController())
listNav.tabBarItem = UITabBarItem(title: "列表",
image: UIImage(systemName: "list.bullet"),
tag: 1)
let meNav = UINavigationController(rootViewController: MeViewController())
meNav.tabBarItem = UITabBarItem(title: "我的",
image: UIImage(systemName: "person"),
tag: 2)
tabBarController.viewControllers = [homeNav, listNav, meNav]
window?.rootViewController = tabBarController
window?.makeKeyAndVisible()
return true
}
}
架构关系图:
UIWindow
└── UITabBarController
├── UINavigationController (Tab 1: 首页)
│ ├── HomeViewController (root)
│ └── DetailViewController (pushed)
├── UINavigationController (Tab 2: 列表)
│ ├── ListViewController (root)
│ └── ItemViewController (pushed)
└── UINavigationController (Tab 3: 我的)
└── MeViewController (root)
10.9 实用技巧
swift
// 获取 NavigationController 中的 VC 数量
let count = navigationController?.viewControllers.count ?? 0
// 获取上一个 VC
if let count = navigationController?.viewControllers.count, count >= 2 {
let previousVC = navigationController?.viewControllers[count - 2]
}
// 交互式 Pop 手势(左滑返回)
navigationController?.interactivePopGestureRecognizer?.delegate = self
// 替换整个栈
navigationController?.setViewControllers([newRootVC, newDetailVC], animated: true)