邏輯寫完了我們就可以來將畫面使用到畫面上了!
我們在 Day26
定義了一個 ConceptHomeView
,要把它拿來用!
首先先在 UI
資料夾新增一個 HomeView.swift
,定義一個 HomeView
結構,並把 ConceptHomeView
的內容都複製過去。
因為我們需要在 Preview
內注入 DIContainer
,但每個 View
都建立一個新的 DIContainer
很麻煩,所以我們可以在 extension
額外定義一個。
在 DependencyInjector.swift
內加入以下程式碼:
extension DIContainer {
static let preview = DIContainer(isMock: true)
}
這樣就可以把 HomeView_Previews
裡的注入這樣寫:
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
HomeView()
.inject(.preview)
}
}
別忘了
import Ditto
首先我們從 Environment
取得 DIContainer
:
@Environment(\.injected) private var container: DIContainer
接下來把 data
改成我們需要的 [TodoEvent]
型別:
@State private var data = [TodoEvent]()
然後把對應的地方修改一下。
在 adderView
中,新增待辦事項的「新增按鈕」行為原本是:
showAdderView = false
if input.count != 0 {
withAnimation {
data.append(input)
}
}
我們把它改成調用 Interactor
的新增待辦事項:
showAdderView = false
if input.count != 0 {
container.interactor.todoEvent.createEvent(
TodoEvent(id: 0, title: input, createAt: .now, complete: false)
)
}
在 listView
顯示資料的地方,原本是 Text(d)
,改為:
Text(d.title)
這時 ForEach
會噴錯,我們需要把 TodoEvent
實作 Identifiable
:
extension TodoEvent: Identifiable {}
然後將 ForEach
的 id: \.self
拿掉:
ForEach(data) { d in
另外還要把 swipeActions
內的刪除按鈕改為調用 Interactor
的刪除待辦事項:
.swipeActions(edge: .trailing, allowsFullSwipe: false) {
Button(role: .destructive) {
container.interactor.todoEvent.deleteEvent(d)
} label: {
Image(systemName: "trash.fill")
}
}
最後在 body
內加上 onReceive
、onAppear
來獲取資料:
var body: some View {
VStack {
titleView()
listView()
Spacer()
adderButtonView()
}
.sheet(isPresented: $showAdderView) {
input = ""
} content: {
adderView()
}
.onReceive(container.appState.todoEvents) { data = $0 }
.onAppear {
container.interactor.todoEvent.listEvents()
}
}
然後記得在 TodoListApp.swift
初始化及注入 DIContainer
,還有將 ContentView
替換成 HomeView
:
import SwiftUI
import Ditto
@main
struct TodoListApp: App {
let container = DIContainer(isMock: false)
var body: some Scene {
WindowGroup {
HomeView()
.inject(container)
}
}
}
記得我們有在 AppState
內定義一個 todoEventError
嗎?
我們可以試試看使用它來顯示警告。
首先在 HomeView
內定義我們需要的變數:
@State private var error: Error? = nil
@State private var showError = false
並且在 body
內加上 onReceive
來訂閱 todoEventError
:
.onReceive(container.appState.todoEventError) {
error = $0
if error != nil {
showError = true
}
}
然後在 body
內加上 alert
:
.alert(error?.localizedDescription ?? "", isPresented: $showError) {}
這樣如果有 Error
訊息,就會跳出警告了。
一樣我們可以簡單寫個測試按鈕:
@ViewBuilder
private func testError() -> some View {
Button("Send Error") {
container.appState.todoEventError.send(TodoEvent.Errors.recordNotFound)
}
.buttonStyle(.borderedProminent)
}
隨便塞到 body
內做測試:
APP 完成後可以進模擬器跑跑看。
也可以把你的手機接上,在手機上安裝你的 APP:
詳細這邊就不多做說明了,網路上很多資源自己去找吧!
本章我們將前幾章定義的邏輯運用在 View
上。
未來可以根據需要,把不同的組件額外獨立寫成一個 View
。
比如說
listView
可以額外寫成另一個View
結構
TodoList
專案的程式碼放在 github,有興趣可以去看看。