SwiftUI之深入解析如何處理特定的數(shù)據(jù)

一、前言

當(dāng)我們需要去標(biāo)記相關(guān)的信息,亦或者過濾信息列表,只需要展示必要的相關(guān)信息,該怎么繼續(xù)處理呢?

要增加這些特性,就比如以地圖上的地理位置和地點(diǎn)為例,我們需要在地點(diǎn)列表上添加一個開關(guān),用來過濾用戶喜歡的地標(biāo),還需要在地標(biāo)上添加一個星標(biāo)按鈕,用戶可以點(diǎn)擊它來標(biāo)記這個地標(biāo)為自己喜歡的地點(diǎn)。

二、標(biāo)記特定的數(shù)據(jù)

標(biāo)記用戶最喜歡的地標(biāo),給地標(biāo)列表的每一行添加一個星標(biāo)用來表示用戶是否標(biāo)記該地標(biāo)為自己喜歡的:


打開工程項目,在項目導(dǎo)航下選擇 LandmarkRow.swift 文件,在空白占位后面添加一個 if 表達(dá)式,if 表達(dá)式判斷是否當(dāng)前地標(biāo)是用戶喜歡的,如果用戶標(biāo)記當(dāng)前地標(biāo)為喜歡就顯示星標(biāo),可以在 SwitUI 的代碼塊中使用 if 語句來條件包含視圖;由于系統(tǒng)圖片是矢量類型的,可以使用 foregroundColor( _: ) 來改變它的顏色,當(dāng)?shù)貥?biāo) landmark 的 isFavorite 屬性為真時,星標(biāo)顯示;

struct LandmarkRow: View {


? ? var landmark : Landmark


? ? var body: some View {

? ? ? ? HStack {

? ? ? ? ? ? landmark.image

? ? ? ? ? ? ? ? .resizable()

? ? ? ? ? ? ? ? .frame(width: 50, height: 50)

? ? ? ? ? ? Text(landmark.name)

? ? ? ? ? ? Spacer()


? ? ? ? ? ? if (landmark.isFavorite) {

? ? ? ? ? ? ? ? Image(systemName: "star.fill")

? ? ? ? ? ? ? ? ? ? .imageScale(.medium)

? ? ? ? ? ? ? ? ? ? .foregroundColor(.yellow)

? ? ? ? ? ? }

? ? ? ? }

? ? }

}

struct LandmarkRow_Previews: PreviewProvider {

? ? static var previews: some View {

? ? ? ? Group {

? ? ? ? ? ? LandmarkRow(landmark: landmarkData[0])

? ? ? ? ? ? LandmarkRow(landmark: landmarkData[1])

? ? ? ? }

? ? ? ? .previewLayout(.fixed(width: 300, height: 70))

? ? }

}


三、過濾數(shù)據(jù)列表

可以定制地標(biāo)列表,讓它只顯示用戶喜歡的地標(biāo),或者顯示所有的地標(biāo),要實現(xiàn)這個功能,需要給 LandmarkList 視圖類型添加一些狀態(tài)變量,狀態(tài)(State)是一個值或者一個值的集合,會隨著時間而改變,同時會影響視圖的內(nèi)容、行為或布局,在屬性前面加上 @State 修飾詞就是給視圖添加了一個狀態(tài)值:


選擇 LandmarkList.swift 文件,并給 LandmarkList 添加一個名為 showFavoritesOnly 的狀態(tài),初始值設(shè)置為 false;點(diǎn)擊 Resume 按鈕或快捷鍵 Command+Option+P 刷新畫布,當(dāng)對視圖進(jìn)行添加或修改屬性等結(jié)構(gòu)性改變時,需要手動刷新畫布;

代碼中通過檢查 showFavoritesOnly 屬性和每一個地標(biāo)的 isFavorite 屬性值來過濾地標(biāo)列表所展示的內(nèi)容:

struct LandmarkList: View {


? ? @State var showFavoritesOnly : Bool = false


? ? var body: some View {

? ? ? ? NavigationView {

? ? ? ? ? ? List(landmarkData) { landmark in

? ? ? ? ? ? ? ? if !self.showFavoritesOnly || landmark.isFavorite {

? ? ? ? ? ? ? ? ? ? NavigationLink(destination: ContentView(landmark: landmark)) {

? ? ? ? ? ? ? ? ? ? ? ? LandmarkRow(landmark: landmark)

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? .navigationBarTitle(Text("Landmarks"))

? ? ? ? }

? ? }

}

四、添加控件來切換狀態(tài)

為了讓用戶控制地標(biāo)列表的過濾器,需要添加一個可以修改 showFavoritesOnly 值的控件,傳遞一個綁定關(guān)系給 toggle 控件可以實現(xiàn);一個綁定關(guān)系(binding)是對可變狀態(tài)的引用,當(dāng)用戶點(diǎn)擊 toggle 控件,從開到關(guān)或從關(guān)到開,toggle 控件會通過綁定關(guān)系對應(yīng)的更新視圖的狀態(tài):


創(chuàng)建一個嵌套的 ForEach 組來把地標(biāo)數(shù)據(jù)轉(zhuǎn)換成地標(biāo)行視圖,在一個列表中組合靜態(tài)和動態(tài)視圖,或者組合兩個甚至多個不同的動態(tài)視圖組,使用 ForEach 類型動態(tài)生成而不是給列表傳入數(shù)據(jù)集合生成列表視圖;

添加一個 Toggle 視圖作為列表的每一個子視圖,傳入一個 showFavoritesOnly 的綁定關(guān)系,使用 $ 前綴來獲得一個狀態(tài)變量或?qū)傩缘慕壎P(guān)系;

struct LandmarkList: View {


? ? @State var showFavoritesOnly : Bool = false


? ? var body: some View {

? ? ? ? NavigationView {

? ? ? ? ? ? List {

? ? ? ? ? ? ? ? Toggle(isOn: $showFavoritesOnly) {

? ? ? ? ? ? ? ? ? ? Text("Favorites only")

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ForEach(landmarkData) { landmark in

? ? ? ? ? ? ? ? ? ? if !self.showFavoritesOnly || landmark.isFavorite {

? ? ? ? ? ? ? ? ? ? ? ? NavigationLink(destination: ContentView(landmark: landmark)) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? LandmarkRow(landmark: landmark)

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? .navigationBarTitle(Text("Landmarks"))

? ? ? ? }

? ? }

}

效果如下:


實時預(yù)覽模式下,點(diǎn)擊 Toggle 控件來驗證過濾器的功能:


五、使用可觀察對象來存儲數(shù)據(jù)

要實現(xiàn)用戶標(biāo)記哪個地標(biāo)為自己喜愛的地標(biāo)這個功能,需要使用可觀察對象(observalble object)存放地標(biāo)數(shù)據(jù)??捎^察對象是一種可以綁定到具體 SwifUI 視圖環(huán)境中的數(shù)據(jù)對象,SwiftUI 可以察覺它影響視圖展示的任何變化,并在這種變化發(fā)生后及時更新對應(yīng)視圖的展示內(nèi)容:


創(chuàng)建一個名為 UserData.swift 的文件,聲明一個遵循 ObservableObject 協(xié)議的新數(shù)據(jù)模型,ObservableObject 協(xié)議來自響應(yīng)式框架 Combine,SwiftUI 可以訂閱可觀察對象,并在數(shù)據(jù)發(fā)生改變時更新視圖的顯示內(nèi)容;

添加存儲屬性 showFavoritesOnly 和 landmarks,并賦予初始值,可觀察對象需要對外公布內(nèi)部數(shù)據(jù)的任何改動,因此訂閱此可觀察對象的訂閱者就可以獲得對應(yīng)的數(shù)據(jù)改動信息;

給新建的數(shù)據(jù)模型的每一個屬性添加 @Published 屬性修飾詞:

import Combine

import SwiftUI

final class UserData: ObservableObject {

? ? @Published var showFavoritesOnly = false

? ? @Published var landmarks = landmarkData

}

六、視圖中適配數(shù)據(jù)模型對象

已經(jīng)創(chuàng)建 UserData 可觀察對象,現(xiàn)在要改造視圖,讓它使用這個新的數(shù)據(jù)模型來存儲視圖內(nèi)容數(shù)據(jù):


在 LandmarkList.swift 文件中,使用 @EnvironmentObject 修飾的 userData 屬性來替換原來的 showFavoritesOnly 狀態(tài)屬性,并對預(yù)覽視圖調(diào)用 environmentObject( _: ) 修改器,只要 environmentObject( _: ) 修改器應(yīng)用在視圖的父視圖上,userData 就能夠自動獲取它的值;

替換原來使用 showFavoritesOnly 狀態(tài)屬性的地方,改為使用 userData 中的對應(yīng)屬性,與 @State 修飾的屬性一樣,也可以使用 $ 前綴訪問 userData 對象的成員綁定引用;

創(chuàng)建 ForEach 實例時使用 userData.landmarks 做為數(shù)據(jù)源:

struct LandmarkList: View {

? ? @EnvironmentObject private var userData: UserData


? ? var body: some View {

? ? ? ? NavigationView {

? ? ? ? ? ? List {

? ? ? ? ? ? ? ? Toggle(isOn: $userData.showFavoritesOnly) {

? ? ? ? ? ? ? ? ? ? Text("Show Favorites Only")

? ? ? ? ? ? ? ? }


? ? ? ? ? ? ? ? ForEach(userData.landmarks) { landmark in

? ? ? ? ? ? ? ? ? ? if !self.userData.showFavoritesOnly || landmark.isFavorite {

? ? ? ? ? ? ? ? ? ? ? ? NavigationLink(

? ? ? ? ? ? ? ? ? ? ? ? ? ? destination: LandmarkDetail(landmark: landmark)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? .environmentObject(self.userData)

? ? ? ? ? ? ? ? ? ? ? ? ) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? LandmarkRow(landmark: landmark)

? ? ? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? .navigationBarTitle(Text("Landmarks"))

? ? ? ? }

? ? }

}

struct LandmarksList_Previews: PreviewProvider {

? ? static var previews: some View {

? ? ? ? LandmarkList()

? ? ? ? ? ? .environmentObject(UserData())

? ? }

}


在程序入口,對 LandmarkList 視圖調(diào)用 environmentObject 修改器,這樣可以把 UserData 的數(shù)據(jù)對象綁定到 LandmarkList 視圖的環(huán)境變量中,子視圖可以獲得父視圖環(huán)境中的變量,此時如果在模擬器或者真機(jī)上運(yùn)行應(yīng)用,也可以正常展示視圖內(nèi)容:

@main

struct HandlingUserInputApp: App {

? ? var body: some Scene {

? ? ? ? WindowGroup {

? ? ? ? ? ? LandmarkList().environmentObject(UserData())

? ? ? ? }

? ? }

}

更新 ContentView 視圖,讓它從父視圖的環(huán)境變量中取要展示的數(shù)據(jù),之后在更新地標(biāo)的用戶喜愛狀態(tài)時,會用到 landmarkIndex 這個變量:

struct ContentView: View {


? ? @EnvironmentObject var userData: UserData

? ? var landmark: Landmark


? ? var landmarkIndex: Int {

? ? ? ? userData.landmarks.firstIndex(where: { $0.id == landmark.id })!

? ? }


? ? var body: some View {

? ? ? ? VStack {

? ? ? ? ? ? MapView(coordinate: landmark.locationCoordinate)

? ? ? ? ? ? ? ? .edgesIgnoringSafeArea(.top)

? ? ? ? ? ? ? ? .frame(height: 300)


? ? ? ? ? ? CircleImage(image: landmark.image)

? ? ? ? ? ? ? ? .offset(x: 0, y: -130)

? ? ? ? ? ? ? ? .padding(.bottom, -130)


? ? ? ? ? ? VStack(alignment: .leading) {

? ? ? ? ? ? ? ? HStack {

? ? ? ? ? ? ? ? ? ? Text(landmark.name)

? ? ? ? ? ? ? ? ? ? ? ? .font(.title)

? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? HStack(alignment: .top) {

? ? ? ? ? ? ? ? ? ? Text(landmark.park)

? ? ? ? ? ? ? ? ? ? ? ? .font(.subheadline)

? ? ? ? ? ? ? ? ? ? Spacer()

? ? ? ? ? ? ? ? ? ? Text(landmark.state)

? ? ? ? ? ? ? ? ? ? ? ? .font(.subheadline)

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? ? ? .padding()

? ? ? ? ? ? Spacer()

? ? ? ? }

? ? }

}

struct ContentView_Previews: PreviewProvider {

? ? static var previews: some View {

? ? ? ? let userData = UserData()

? ? ? ? return ContentView(landmark: landmarkData[0])

? ? ? ? ? ? .environmentObject(userData)

? ? }

}

切換到 LandmarkList.swift 文件,并打開實時預(yù)覽視圖去驗證所添加的功能是否正常工作。

七、為每一個地標(biāo)創(chuàng)建一個喜愛按鈕

可以在喜歡和不喜歡的地標(biāo)列表間進(jìn)行切換,但喜歡的地標(biāo)列表還是硬編碼形成的,為了讓用戶可以自己標(biāo)記哪個地標(biāo)是自己喜歡的,需要在地標(biāo)詳情頁添加一個標(biāo)記喜歡的按鈕:


在 ContentView.swift 的 HStack 中添加地標(biāo)名稱的 Text,在地標(biāo)名稱的 Text 控件旁邊添加一個新的按鈕控件,使用 if-else 條件語句設(shè)置不同的圖片顯示狀態(tài)表示這個地標(biāo)是否被用戶標(biāo)記為喜歡,在 Button 的動作閉包中,使用 landmarkIndex 去修改 userData 中對應(yīng)地標(biāo)的數(shù)據(jù):


切換到 landmarkList.swift,并開啟實時預(yù)覽模式,當(dāng)從列表頁導(dǎo)航進(jìn)入詳情頁后,點(diǎn)擊喜歡按鈕,喜歡的狀態(tài)會在返回列表頁后與列表中對應(yīng)的地標(biāo)喜歡狀態(tài)保持一致,因為列表頁和詳情頁的地標(biāo)數(shù)據(jù)使用的是同一份,所以可以在不同頁面間保持狀態(tài)同步。

完整示例:SwiftUI之如何處理特定的數(shù)據(jù)和如何在視圖中適配數(shù)據(jù)模型對象。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容