iT邦幫忙

2025 iThome 鐵人賽

DAY 29
0
Mobile Development

從零開始學習 iOS系列 第 29

從零開始學習 iOS Day28 - 專案實作 詳細紀錄頁

  • 分享至 

  • xImage
  •  

在前一篇中,我們已經完成了「飲食紀錄清單頁」,使用者能夠查看已儲存的紀錄。

今天,我們要讓使用者點擊任一餐點後,進入該筆紀錄的「詳細資訊頁面」,並能進行「編輯」或「刪除」操作。


今日目標

  1. 建立詳細紀錄頁(MealDetailView
  2. 顯示單筆餐點的詳細內容(名稱、份量、熱量、營養素)
  3. 加入 編輯 / 刪除 按鈕
  4. 使用 NavigationLink 從清單頁跳轉至詳細頁

建立ViewModel

ViewModel 負責儲存目前查看的餐點,並提供編輯、刪除的功能。

import SwiftUI
import SwiftData
import Combine

class MealDetailViewModel: ObservableObject {
    @Published var meal: MealRecord
    @Published var showEditView = false
    @Published var showDeleteAlert = false
    @Published var deleteSuccess = false
    
    private let repository = MealRepository.shared
    
    init(meal: MealRecord, ) {
        self.meal = meal
    }
    
    func onAppear(modelContext: ModelContext) {
        Task {
            await repository.configure(context: modelContext)
        }
    }
    
    func deleteMeal() {
        Task {
            await repository.delete(meal)
        }
        deleteSuccess = true
    }
    
    func editMeal() {
        showEditView = true
    }
    
    func dismissEditView() {
        showEditView = false
    }
}

建立詳細記錄頁

接著,我們建立新的畫面來顯示該筆餐點的完整資料。

import SwiftUI
import SwiftData

struct MealDetailView: View {
    @Environment(\.modelContext) private var modelContext
    @Environment(\.dismiss) private var dismiss
    
    @StateObject private var viewModel: MealDetailViewModel
    
    init(meal: MealRecord) {
        _viewModel = StateObject(wrappedValue: MealDetailViewModel(
            meal: meal,
        ))
    }
    
    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text(viewModel.meal.name)
                .font(.largeTitle)
                .bold()
            
            Divider()
            
            VStack(alignment: .leading, spacing: 8) {
                Text("熱量:\(viewModel.meal.calories) kcal")
                Text("餐別:\(viewModel.meal.category.rawValue)")
                
            }
            
            Spacer()
            
            HStack {
                Button("編輯") {
                    viewModel.editMeal()
                }
                .buttonStyle(.borderedProminent)
                
                Button("刪除", role: .destructive) {
                    viewModel.showDeleteAlert = true
                }
                .buttonStyle(.bordered)
            }
        }
        .padding()
        .navigationTitle("詳細資訊")
        .toolbar(.hidden, for: .tabBar)
        .alert("確定要刪除此紀錄?", isPresented: $viewModel.showDeleteAlert) {
            Button("刪除", role: .destructive) {
                viewModel.deleteMeal()
            }
            Button("取消", role: .cancel) { }
        }
        .sheet(isPresented: $viewModel.showEditView) {
            EditMealView(viewModel: viewModel)
        }
        .onAppear() {
            viewModel.onAppear(modelContext: modelContext)
        }
        .onChange(of: viewModel.deleteSuccess) { _, success in
            if success {
                viewModel.deleteSuccess = false
                dismiss()
            }
        }
    }
}

#Preview {
    MealDetailView(meal: MealRecord(name: "燕麥牛奶", calories: 200, category: .breakfast, date: Date()))
}

建立編輯頁

在編輯頁中,我們與 MealDetailView 共用相同的 ViewModel,直接修改同一筆餐點資料。

import SwiftUI
import SwiftData

struct EditMealView: View {
    
    @ObservedObject var viewModel: MealDetailViewModel
    
    @Environment(\.dismiss) private var dismiss
    
    
    var body: some View {
        Form {
            // 餐點資訊
            Section("餐點資訊") {
                TextField("餐點名稱", text: $viewModel.meal.name)
                
                // 卡路里
                TextField("卡路里", value: $viewModel.meal.calories, format: .number)
                    .keyboardType(.numberPad)
                
                // 餐別 Picker
                Picker("餐別", selection: $viewModel.meal.category) {
                    ForEach(MealCategory.allCases, id: \.self) { category in
                        Text(category.rawValue)
                    }
                }
            }
            
            // 儲存按鈕
            Section {
                Button("儲存") {
                    viewModel.showEditView = false
                }
                .frame(maxWidth: .infinity, alignment: .center)
            }
        }
        .navigationTitle("編輯餐點")
    }
}

#Preview {
    EditMealView(viewModel: MealDetailViewModel(meal: MealRecord(name: "燕麥牛奶", calories: 200, category: .breakfast, date: Date())))
}

建立導頁

最後,我們在清單畫面中加入 NavigationLink,讓使用者可以點擊跳轉至詳細頁。

ForEach(viewModel.records) { meal in
    NavigationLink(destination: MealDetailView(meal: meal)) {
        VStack(alignment: .leading, spacing: 6) {
            Text(meal.name)
                .font(.headline)
            Text("\(meal.calories) 大卡")
                .foregroundColor(.gray)
            Text(meal.category.rawValue)
                .font(.caption)
                .foregroundColor(.secondary)
        }
        .padding(.vertical, 4)
    }
}

預覽成果

https://github.com/jian-fu-hung/ithelp-2025/blob/main/image/Day28/%E8%9E%A2%E5%B9%95%E9%8C%84%E5%BD%B1%202025-10-13%20%E5%87%8C%E6%99%A82.03.50.gif?raw=true

  • 建立詳細頁面
  • 顯示單筆資料完整內容
  • 可進行編輯 / 刪除
  • 使用 NavigationLink 從清單跳轉

今日小結

今天我們完成了飲食記錄 App 的詳細記錄頁

  • 顯示詳細記錄
  • 支援編輯以及刪除

上一篇
從零開始學習 iOS Day27 - 專案實作 飲食紀錄清單頁
下一篇
從零開始學習 iOS Day29 - 參賽感想
系列文
從零開始學習 iOS30
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言