昨天,我們為鬧鐘 App 打好了所有地基工程:一個乾淨的專案、安裝好的 Realm 資料庫、以及核心的 AlarmData
資料模型。
然而,要復刻鬧鐘 App,底下會有多個功能分頁,例如「世界時鐘」、「鬧鐘」、「碼錶」等等。
困惑點:我需要在 App 底部做出可以切換的頁籤,這在 iOS 開發中,最標準的做法是什麼?我應該從何處著手?
AI 協作
AI 的回答 :
好的,針對您在 iOS App 中要實現具備『世界時鐘』、『鬧鐘』、『碼錶』等多個主要功能分頁的需求,採用底部頁籤介面 (Tab Bar Interface) 是最符合使用者習慣且蘋果官方推薦的做法。
以下是在 iOS 中實現這種介面的最佳實踐:
核心元件:
在 iOS 開發中,實現底部頁籤介面的核心元件是
UITabBarController
(在 UIKit 框架中) 或TabView
(在 SwiftUI 框架中)。這兩者都是蘋果官方提供,用來管理多個獨立子視圖控制器 (View Controller) 或視圖 (View) 的容器。最佳實踐與建議:
- 使用原生元件:
- 優先選擇
UITabBarController
或TabView
:這是最重要的一點。使用蘋果內建的元件可以確保您的 App 與 iOS 系統的行為、外觀和感覺保持一致。使用者將會立刻知道如何與您的 App 互動,並且您的介面能自動獲得系統級的更新與支援 (例如:深色模式、動態字體大小等)。- 符合人機介面指南 (Human Interface Guidelines, HIG):
- 分頁數量:底部頁籤的數量建議維持在 3 到 5 個 之間。這個數量能讓使用者快速點擊,且圖示和文字有足夠的空間顯示。如果超過 5 個,系統會自動將最後一個顯示為「更多」,點擊後會以列表方式呈現其餘項目。
- 功能對等:確保每個分頁代表的是 App 中一個獨立、平級的主要功能。避免將一個工作流程中的某個步驟放在分頁中。例如,『鬧鐘列表』和『新增鬧鐘』應該在同一個鬧鐘分頁下,而不是分成兩個獨立的分頁。
- 保持一致性:頁籤應在 App 的所有主要層級中保持可見且一致。當使用者在不同分頁間切換時,他們不應該感到困惑。除非進入到某個功能的深層頁面 (例如編輯鬧鐘的詳細設定),否則 Tab Bar 不應隨意隱藏。
- 設計清晰的圖示與標題:
- 圖示 (Icon):為每個分頁設計一個簡潔、易於辨識的圖示。建議使用蘋果提供的 SF Symbols,它能與系統字體完美對齊,並支援多種粗細和尺寸,確保視覺上的一致性。
- 標題 (Title):每個圖示下方應搭配一個簡短、描述性的標題,讓使用者能一眼看出該分頁的功能。例如:「世界時鐘」、「鬧鐘」。
- 確保狀態保存:
- 當使用者從一個分頁切換到另一個再切換回來時,應當保存前一個分頁的狀態。例如,如果使用者在「碼錶」分頁啟動了計時,切換到「鬧鐘」後再回來,「碼錶」應當繼續顯示正在計時的狀態,而不是被重置。
UITabBarController
和TabView
本身就能很好地處理這一點,因為它們會持有各個分頁的視圖控制器實例。總結:
簡單來說,最佳實踐就是使用蘋果官方提供的
UITabBarController
(UIKit) 或TabView
(SwiftUI),並遵循其 Human Interface Guidelines 來設計您的分頁結構、數量和視覺元素。這樣做不僅能降低開發複雜度,更能確保您的使用者獲得流暢、直觀且符合期望的原生 iOS 操作體驗。
我的理解與選擇
AI 的回答非常清晰,直接點名了核心元件 UITabBarController。考量到我們這個系列是從 UIKit 和 XIB 入手的,我決定先專心學好 UITabBarController。
根據 AI 的建議,我把詳細的實作步驟整理如下:
TabBarController.swift
:建立一個繼承自 UITabBarController
的自定義 Class。SceneDelegate
設為根基:將 App 的入口,從原本的 MainViewController
更換為我們新建的 TabBarController
。在動手寫程式碼前,我先按照 Day 7 規劃的 MVC 結構,將專案的檔案夾 Controller
, View
, Model
都建立好,並把 AlarmData.swift
移到 Model
資料夾中。
接著,我在 Controller
資料夾下,新增了一個 Cocoa Touch Class,設定如下:
TabBarController
UITabBarController
設定四個分頁需要不少重複的程式碼。我再次向 AI 求助,給了它一個更具體的任務:「請幫我寫一個 TabBarController
的 setupViewControllers
函式,包含『世界時鐘』、『鬧鐘』、『碼錶』、『計時器』四個分頁。請幫我用 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
}
}
最關鍵的一步來了。我需要告訴 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
打造出一個可以顯示鬧鐘列表的介面。