iOS基础-UIViewController-NavigationBar-TabBar

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.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)
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
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)
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)