← Back to list

飲料訂購 APP IOS SwiftUI — HomeView 設計

HomeView 現在的定位是「首頁入口 + 導航控制」,不是負責每個功能頁面的內容。這樣設計的核心原因是讓每個頁面各自獨立,避免首頁變成什麼都塞的大型 View。

AndyLin · 2026-06-11 03:12 · 0 claps · 8.2 min read
#swiftui #swift #mvvm #ios-app-development #ios-apps
Open on Medium ↗
Wiki topics: 📱 · Mobile Development

飲料訂購 APP IOS SwiftUI — HomeView 設計

HomeView 現在的定位是「首頁入口 + 導航控制」,不是負責每個功能頁面的內容。這樣設計的核心原因是讓每個頁面各自獨立,避免首頁變成什麼都塞的大型 View。

設計原因

HomeView 使用 NavigationStack 作為整個首頁流程的導航容器。底部五個功能按鈕使用 NavigationLink(value:),點擊後會 push 到對應頁面,例如 MallView、NewsView、DrinkListView、NearbyStoresView、MemberView。

這樣做的好處是每個功能頁面都是獨立 View,進入後會有系統提供的返回鍵,可以自然回到首頁。這比在 HomeView 裡用 selectedTab 切換畫面更符合「首頁跳轉到功能頁」的需求。

架構重點

HomeView 只負責三件事:

  1. 顯示首頁內容
  2. 顯示底部五個功能入口
  3. 根據使用者點擊,導航到對應 View

每個功能頁面都有自己的 View 和 ViewModel:

MallView(viewModel: mallViewModel)
NewsView(viewModel: newsViewModel)
DrinkListView(viewModel: drinkListViewModel, showsNavigationStack: false)
NearbyStoresView(viewModel: nearbyStoresViewModel)
MemberView(viewModel: memberViewModel)

這樣符合 MVVM 的分工:

HomeView
只負責首頁 UI 與導航

MallView / NewsView / NearbyStoresView / MemberView / DrinkListView
各自負責自己的畫面內容

MallViewModel / NewsViewModel / NearbyStoresViewModel / MemberViewModel / DrinkListViewModel
各自負責自己的狀態與資料邏輯

每一個頁面的流程

首頁 -> 點擊功能按鈕 -> 進入功能頁 -> 返回首頁

1.App 啟動後進入 HomeView

WindowGroup {
    HomeView()
}

App 啟動後,第一個畫面就是首頁。

2.HomeView 建立首頁用的 NavigationStack

NavigationStack {
    HomeLandingView()
        .safeAreaInset(edge: .bottom, spacing: 0) {
            HomeShortcutBar()
        }
        .navigationDestination(for: HomeTab.self) { tab in
            destination(for: tab)
        }
}

重點整理:

  • NavigationStack 負責頁面 push / pop
  • HomeLandingView 負責首頁內容
  • HomeShortcutBar 負責底部五個功能按鈕
  • navigationDestination 負責定義每個按鈕要跳去哪個頁面

3. 使用 HomeTab 管理五個功能入口

private enum HomeTab: CaseIterable, Identifiable {
    case exchange
    case news
    case order
    case nearby
    case member
}

這樣可以把五個功能入口集中管理。

每個 case 對應一個功能:

exchange -> 商城兌換
news -> 最新消息
order -> 點餐
nearby -> 附近門市
member -> 會員專區

好處是:

  • 不用在 UI 裡到處寫字串
  • 新增功能時只要擴充 enum
  • 可以統一管理 title、icon、跳轉目標

4. HomeShortcutBar 用 ForEach 產生五個按鈕

ForEach(HomeTab.allCases) { tab in
    NavigationLink(value: tab) {
        item(for: tab)
            .frame(maxWidth: .infinity)
    }
}

這段流程是:

讀取 HomeTab.allCases
-> 產生五個 NavigationLink
-> 每個按鈕綁定一個 HomeTab
-> 點擊後交給 navigationDestination 處理

5. 按鈕使用 NavigationLink

用 NavigationLink(value:),代表這個按鈕本身就是導航行為

使用者點擊按鈕
-> NavigationLink 傳出 HomeTab
-> NavigationStack 接收
-> navigationDestination 找到對應 View
-> push 到新頁面

6. navigationDestination 定義跳轉規則

.navigationDestination(for: HomeTab.self) { tab in
    destination(for: tab)
}

使用者點擊某個 NavigationLink(value:) 時,SwiftUI 會把 HomeTab 傳給這裡。

然後 destination(for:) 根據 tab 回傳不同頁面:

private func destination(for tab: HomeTab) -> some View {
    switch tab {
    case .exchange:
        MallView(viewModel: mallViewModel)
    case .news:
        NewsView(viewModel: newsViewModel)
    case .order:
        DrinkListView(viewModel: drinkListViewModel, showsNavigationStack: false)
    case .nearby:
        NearbyStoresView(viewModel: nearbyStoresViewModel)
    case .member:
        MemberView(viewModel: memberViewModel)
    }
}

這樣導覽邏輯集中在同一個地方,之後很好維護。

7.每個功能頁拆成獨立 View

目前拆成:

MallView
NewsView
DrinkListView
NearbyStoresView
MemberView

這樣符合單一職責原則,每個 View 只處理自己的 UI。

8. 每個頁面都有自己的 ViewModel

@StateObject private var mallViewModel = MallViewModel(title: "商城兌換")
@StateObject private var newsViewModel = NewsViewModel(title: "最新消息")
@StateObject private var drinkListViewModel = DrinkListViewModel()
@StateObject private var nearbyStoresViewModel = NearbyStoresViewModel(title: "附近門市")
@StateObject private var memberViewModel = MemberViewModel(title: "會員專區")

這些 ViewModel 由 HomeView 持有,原因是:

  • 首頁負責建立要傳入功能頁的狀態物件
  • 當使用者進出頁面時,資料狀態可以保留
  • 不會每次 push 頁面都重新建立資料

例如點餐頁如果有購物車資料,回首頁再進去時,不會因為重建 ViewModel 而消失。

這樣未來可以各自擴充資料邏輯,例如:

MallViewModel -> 載入兌換商品、點數資料
NewsViewModel -> 載入最新消息列表
DrinkListViewModel -> 載入飲品、購物車、送出訂單
NearbyStoresViewModel -> 載入門市、定位、距離排序
MemberViewModel -> 登入、註冊、會員資料

9. 子頁面使用 @ObservedObject 接收 ViewModel

例如

struct MallView: View {
    @ObservedObject var viewModel: MallViewModel
}

原因是:

HomeView 擁有 ViewModel
子頁面只是觀察這個 ViewModel

所以子頁面使用 @ObservedObject,不是 @StateObject。

自己建立並持有物件 -> @StateObject
外部傳進來的物件 -> @ObservedObject

MVVM 資料流

HomeView 建立 ViewModel
-> NavigationLink 觸發跳轉
-> navigationDestination 建立對應 View
-> 將 ViewModel 傳入子 View
-> 子 View 使用 @ObservedObject 觀察狀態

10.首頁底部 bar 使用 safeAreaInset

.safeAreaInset(edge: .bottom, spacing: 0) {
    HomeShortcutBar()
}

這樣底部選單會貼在安全區域底部,不會被 iPhone Home Indicator 擋住

比直接用 VStack { Spacer(); HomeShortcutBar() } 更適合做固定底部工具列。

11. 底部五個入口用 HStack

HStack(alignment: .bottom, spacing: 0)

原因是這個設計不是標準系統 Tab Bar,而是客製化底部入口。

使用 HStack 的好處:

  • 可以自訂五個按鈕寬度
  • 可以讓每個按鈕平均分配
  • 可以特別處理中間「點餐」凸起按鈕
  • 可以自訂 icon、文字、顏色、陰影

12.中間點餐按鈕獨立設計

if tab == .order {
    // 中間凸起按鈕
} else {
    // 一般按鈕
}

這是因為中間按鈕視覺上和其他按鈕不同,技術上是同一個 NavigationLink,只是 label 長得不一樣。


메타데이터
post_id
fd565fc68435
slug
飲料訂購-app-ios-homeview-設計-fd565fc68435
url
https://medium.com/@croc0909/%E9%A3%B2%E6%96%99%E8%A8%82%E8%B3%BC-app-ios-homeview-%E8%A8%AD%E8%A8%88-fd565fc68435
canonical_url
https://medium.com/@croc0909/%E9%A3%B2%E6%96%99%E8%A8%82%E8%B3%BC-app-ios-homeview-%E8%A8%AD%E8%A8%88-fd565fc68435
author_url
https://medium.com/@croc0909
status
ok
fetched_at
2026-06-12 18:14:10