iT邦幫忙

2025 iThome 鐵人賽

DAY 11
0
生成式 AI

iOS? AI-Yes!:用 Vide Coding 加速我的 Swift 學習曲線系列 第 11

Day 11 - 【鬧鐘實戰 II】App 框架搭建:用 TabBarController 打造我們的 App 骨架

  • 分享至 

  • xImage
  •  

昨天,我們為鬧鐘 App 打好了所有地基工程:一個乾淨的專案、安裝好的 Realm 資料庫、以及核心的 AlarmData 資料模型。

然而,要復刻鬧鐘 App,底下會有多個功能分頁,例如「世界時鐘」、「鬧鐘」、「碼錶」等等。

  • 困惑點:我需要在 App 底部做出可以切換的頁籤,這在 iOS 開發中,最標準的做法是什麼?我應該從何處著手?

  • AI 協作

    • 我的 Prompt:我正在開發一個鬧鐘 App,需要有多個主要功能分頁,像是『世界時鐘』、『鬧鐘』、『碼錶』等。在 iOS 中,實現這種底部頁籤介面的最佳實踐是什麼?請簡單描述且不需要給程式碼

    AI 的回答 :

    好的,針對您在 iOS App 中要實現具備『世界時鐘』、『鬧鐘』、『碼錶』等多個主要功能分頁的需求,採用底部頁籤介面 (Tab Bar Interface) 是最符合使用者習慣且蘋果官方推薦的做法。

    以下是在 iOS 中實現這種介面的最佳實踐:

    核心元件:

    在 iOS 開發中,實現底部頁籤介面的核心元件是 UITabBarController (在 UIKit 框架中) 或 TabView (在 SwiftUI 框架中)。這兩者都是蘋果官方提供,用來管理多個獨立子視圖控制器 (View Controller) 或視圖 (View) 的容器。

    最佳實踐與建議:

    1. 使用原生元件
      • 優先選擇 UITabBarControllerTabView:這是最重要的一點。使用蘋果內建的元件可以確保您的 App 與 iOS 系統的行為、外觀和感覺保持一致。使用者將會立刻知道如何與您的 App 互動,並且您的介面能自動獲得系統級的更新與支援 (例如:深色模式、動態字體大小等)。
    2. 符合人機介面指南 (Human Interface Guidelines, HIG)
      • 分頁數量:底部頁籤的數量建議維持在 3 到 5 個 之間。這個數量能讓使用者快速點擊,且圖示和文字有足夠的空間顯示。如果超過 5 個,系統會自動將最後一個顯示為「更多」,點擊後會以列表方式呈現其餘項目。
      • 功能對等:確保每個分頁代表的是 App 中一個獨立、平級的主要功能。避免將一個工作流程中的某個步驟放在分頁中。例如,『鬧鐘列表』和『新增鬧鐘』應該在同一個鬧鐘分頁下,而不是分成兩個獨立的分頁。
      • 保持一致性:頁籤應在 App 的所有主要層級中保持可見且一致。當使用者在不同分頁間切換時,他們不應該感到困惑。除非進入到某個功能的深層頁面 (例如編輯鬧鐘的詳細設定),否則 Tab Bar 不應隨意隱藏。
    3. 設計清晰的圖示與標題
      • 圖示 (Icon):為每個分頁設計一個簡潔、易於辨識的圖示。建議使用蘋果提供的 SF Symbols,它能與系統字體完美對齊,並支援多種粗細和尺寸,確保視覺上的一致性。
      • 標題 (Title):每個圖示下方應搭配一個簡短、描述性的標題,讓使用者能一眼看出該分頁的功能。例如:「世界時鐘」、「鬧鐘」。
    4. 確保狀態保存
      • 當使用者從一個分頁切換到另一個再切換回來時,應當保存前一個分頁的狀態。例如,如果使用者在「碼錶」分頁啟動了計時,切換到「鬧鐘」後再回來,「碼錶」應當繼續顯示正在計時的狀態,而不是被重置。UITabBarControllerTabView 本身就能很好地處理這一點,因為它們會持有各個分頁的視圖控制器實例。

    總結:

    簡單來說,最佳實踐就是使用蘋果官方提供的 UITabBarController (UIKit) 或 TabView (SwiftUI),並遵循其 Human Interface Guidelines 來設計您的分頁結構、數量和視覺元素。這樣做不僅能降低開發複雜度,更能確保您的使用者獲得流暢、直觀且符合期望的原生 iOS 操作體驗。

    • 我的理解與選擇

      AI 的回答非常清晰,直接點名了核心元件 UITabBarController。考量到我們這個系列是從 UIKit 和 XIB 入手的,我決定先專心學好 UITabBarController。

    • 根據 AI 的建議,我把詳細的實作步驟整理如下:

      1. 整理檔案夾:按照 Day 7 規劃的 MVC 結構整理專案。
      2. 建立 TabBarController.swift:建立一個繼承自 UITabBarController 的自定義 Class。
      3. 用程式碼配置分頁:向 AI 求助,生成設定四個分頁的函式。
      4. 修改 SceneDelegate 設為根基:將 App 的入口,從原本的 MainViewController 更換為我們新建的 TabBarController

步驟一:整理檔案夾

在動手寫程式碼前,我先按照 Day 7 規劃的 MVC 結構,將專案的檔案夾 Controller, View, Model 都建立好,並把 AlarmData.swift 移到 Model 資料夾中。

步驟二:建立 TabBarController.swift

接著,我在 Controller 資料夾下,新增了一個 Cocoa Touch Class,設定如下:

  • Class: TabBarController
  • Subclass of: UITabBarController
  • Also create XIB file: 不勾選

步驟三:用程式碼配置分頁 (AI 輔助)

設定四個分頁需要不少重複的程式碼。我再次向 AI 求助,給了它一個更具體的任務:「請幫我寫一個 TabBarControllersetupViewControllers 函式,包含『世界時鐘』、『鬧鐘』、『碼錶』、『計時器』四個分頁。請幫我用 SF Symbols 設定好它們的圖示,並預設選中『鬧鐘』分頁。另外,請為每個分頁都包裹一層 UINavigationController。」

AI 很快就給了我一份非常完整的程式碼,我只需要稍作整理和加上自己的理解註解:

檔案:TabBarController.swift

import UIKit

class TabBarController: UITabBarController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 統一設定 Tab Bar 的顏色
        tabBar.tintColor = .orange
        tabBar.unselectedItemTintColor = .gray
        
        // 我習慣把 viewDidLoad 保持乾淨,所以把複雜的設定抽離成一個獨立的函式
        setupViewControllers()
    }
    
    // 定義設定分頁的函式
    private func setupViewControllers() {
        
        // --- 第一個分頁:世界時鐘 (佔位) ---
        let worldClockVC = UIViewController()
        worldClockVC.view.backgroundColor = .systemBackground
        worldClockVC.title = "世界時鐘"
        let worldClockNavController = UINavigationController(rootViewController: worldClockVC)
        worldClockNavController.navigationBar.prefersLargeTitles = true
        worldClockNavController.tabBarItem = UITabBarItem(title: "世界時鐘", image: UIImage(systemName: "globe"), tag: 0)

        // --- 第二個分頁:鬧鐘 (我們的主角) ---
        let alarmVC = MainViewController(nibName: "MainViewController", bundle: nil)
        let alarmNavController = UINavigationController(rootViewController: alarmVC)
        alarmNavController.tabBarItem = UITabBarItem(title: "鬧鐘", image: UIImage(systemName: "alarm.fill"), tag: 1)
        
        // --- 第三個分頁:碼錶 (佔位) ---
        let stopwatchVC = UIViewController()
        stopwatchVC.view.backgroundColor = .systemBackground
        stopwatchVC.title = "碼錶"
        let stopwatchNavController = UINavigationController(rootViewController: stopwatchVC)
        stopwatchNavController.navigationBar.prefersLargeTitles = true
        stopwatchNavController.tabBarItem = UITabBarItem(title: "碼錶", image: UIImage(systemName: "stopwatch.fill"), tag: 2)
        
        // --- 第四個分頁:計時器 (佔位) ---
        let timerVC = UIViewController()
        timerVC.view.backgroundColor = .systemBackground
        timerVC.title = "計時器"
        let timerNavController = UINavigationController(rootViewController: timerVC)
        timerNavController.navigationBar.prefersLargeTitles = true
        timerNavController.tabBarItem = UITabBarItem(title: "計時器", image: UIImage(systemName: "timer"), tag: 3)

        // 將所有分頁的 Navigation Controller 加入到 Tab Bar Controller 中
        viewControllers = [worldClockNavController, alarmNavController, stopwatchNavController, timerNavController]
        
        // 預設選中第二個分頁 (鬧鐘)
        self.selectedIndex = 1
    }
}

步驟四:修改 SceneDelegate 設為根基

最關鍵的一步來了。我需要告訴 App,別再看著之前那個空白的 MainViewController 了,新的 App 入口是我們剛建好的 TabBarController

我打開 SceneDelegate.swift,找到 scene(_:willConnectTo:options:) 函式,並將它修改如下:

檔案:SceneDelegate.swift

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    guard let windowScene = (scene as? UIWindowScene) else { return }
    let window = UIWindow(windowScene: windowScene)
    
    // 【最關鍵的一行】告訴視窗,這個 App 的「根」就是我們剛剛建立的 TabBarController
    window.rootViewController = TabBarController()
    
    self.window = window
    window.makeKeyAndVisible()
}

今日總結

今天,我們把鬧鐘 App 的TabBar 做完了,明天我們就要開始對最重要的鬧鐘分頁進行設計。我們將正式在 MainViewController 上,使用 UITableView 打造出一個可以顯示鬧鐘列表的介面。


上一篇
Day 10 - 【鬧鐘實戰 I】打好地基:專案設定、Realm 模型與 App 框架
系列文
iOS? AI-Yes!:用 Vide Coding 加速我的 Swift 學習曲線11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言