實戰教程·元宇宙來了,準備好你的電子名片了嗎?(五)

語言: CN / TW / HK

theme: smartblue

前提回顧

在前幾個章節中,我們已經完成了Linkworld的基本功能,但要達到最小MVP產品還有些距離。完成的功能目前只能在模擬器中使用,缺少本地化儲存、網路請求和編輯、刪除等功能,這尚未達到最小MVP的要求。

MVP是最小的可用產品,其核心在於最小和可用。“最小”代表著該產品可以反覆迭代且迭代週期短,“可用”代表該產品是可以滿足使用者預期的可以交付的產品,而不僅僅是demo。

因此我們繼續深入完善Linkworld這款產品,實現最小MVP產品。

MVVM架構模式:Model-View-ViewModel

通常在SwiftUi開發過程中,開發者們會使用MVVM開發架構模式進行專案搭建。MVVM全稱為Model-View-ViewModel,即模型-檢視-檢視模型。在該架構模式中,我們建立資料模型將資料進行抽象和分離,然後單獨建立檢視,最後使用ViewModel進行雙向繫結連結資料和檢視。

在本專案中,我們建立的Model檔案便是Model模型部分,而ContentView、NewView、HomePageView是View檢視部分,而我們實現的單獨抽離ViewModel模型檢視部分內容,因此在檢視與檢視之間,檢視與模型之間都建立了多次的變數宣告和雙向繫結,示例:indexURL。

下面我們來建立ViewModel檢視模型並在該專案中使用它,建立一個新的Swift檔案,命名為ViewModel,如下圖所示:

為了便於檔案管理,我們可以建立對應的資料夾用於存放不同內容的檔案,使得檔案目錄更加清晰明瞭,如下圖所示:

ViewModel模型檢視檔案作為連結資料模型和檢視之前的“聯絡官”,需要給檢視傳遞來自於Model的資料,因此我們可以建立一個結構體,遵循ObservableObject協議,ObservableObject協議可以在資料更新時通知檢視更新內容。

然後在ViewModel模型檢視內,我們使用建立釋出器來建立來自Model的陣列結構,如下程式碼所示:

``` import Foundation import SwiftUI

class ViewModel:ObservableObject { @Published var models = Model

} ```

URLSession例項:建立網路請求

建立好ViewModel模型檢視後,我們來學習使用URLSession建立一個網路請求,在學習之前我們先來了解一個JSON檔案的相關知識。

JSON是一種檔案格式,和TXT文字一樣,JSON使用帶有固定格式的純文字建立資料集。我們在搭建NewView新增身份卡檢視時曾實現了新增身份卡的方法,即賦值給符合Model格式的變數,然後將Model格式的資料新增到個人主頁的List中。

JSON檔案則是包含了所有符合Model格式的資料的陣列集合,我們可以建立好一個JSON檔案及其內容,並通過網路請求將JSON檔案請求到本地並解析載入到List中。

我們先來看看JSON檔案的格式,如下圖所示:

[ { "platformIcon": "icon_juejin", "title": "掘金簽約作者", "platformName": "稀土掘金技術社群", "indexURL": "juejin.cn/user/3897092103223517" }, { "platformIcon": "icon_aliyun", "title": "專家博主", "platformName": "阿里雲社群", "indexURL": "developer.aliyun.com/profile/expert/376pj7xeqgqjy" } ]

上述JSON檔案是通過一個線上JSON檔案生成器(如npoint.io、fastmock.site等等)生成的準備程式碼,其中由“[]”包裹的是陣列,而“{}”包裹的是陣列中的物件,我們建立了2個物件,且資料模型格式都符合Model資料模型定義的引數。

通過第三方網站可以生成一個API介面的連結地址,我們宣告一個常量引數儲存它,如下程式碼所示:

let JsonURL = "http://api.npoint.io/c51968c99a2a087dac5e"

當然,我們也可以使用測試工具測試建立的JSON檔案地址返回的結果是否正確,可以使用postman或者apifox測試軟體進行測試,避免在專案開發時請求資料失敗而導致開發卡點的問題,如下圖所示:

上圖中,使用的測試工具是apifox。使用方法也很簡單,建立一個介面並將剛剛生成的地輸入進去,並設定好對應的Query引數,點選執行,便可檢視該介面返回狀態及結果。熟練使用介面測試工具,可以幫助我們節省開發時間。

迴歸正題,我們在ViewModel檢視模型中使用URLSession例項建立一個網路請求的方法,如下程式碼所示:

//網路請求 func getData() { let session = URLSession(configuration: .default) session.dataTask(with: URL(string: JsonURL)!) { data, _, _ in guard let jsonData = data else { return } do { let data = try JSONDecoder().decode([Model].self, from: jsonData) self.models = data } catch { print(error) } } .resume() }

上述程式碼中,我們建立了一個網路請求方法getData,在方法裡,我們使用URLSession建立網路請求,聲明瞭常量session來使用URLSession API,然後載入的目標地址為宣告的JsonURL,並執行下載資料,當下載成功後,將按照Model資料模型格式的陣列載入到models數組裡,若載入失敗則列印錯誤資訊。

然後我們可以在初始化時呼叫getData獲得資料方法來載入資料,如下程式碼所示:

init(){ getData() }

由於使用JSONDecoder編碼器,因此Model資料模型還需要設定遵循可編碼性 Codable 協議,才能避免報錯。又因為id在JSON檔案中不存在,因此需要告訴編譯器使用某些CodingKeys,不包括id。如下程式碼所示:

enum CodingKeys : String, CodingKey { case platformIcon, title, platformName, indexURL }

上述程式碼中,我們使用列舉類CodingKeys,用來指定Model資料模型中應該接收哪些資料,用於排除請求JSON時缺少ID的問題。另外CodingKey也可以作為關鍵字替換的方法,當實際開發過程中後端與前端定義的欄位名稱不一樣時,也可是使用該方法進行欄位對照對映,這點後面有時間會講到。

ViewModel模型檢視建立完成後,我們來到ContentView檢視,要使用ViewModel模型檢視連結Model和View,我們需要使用新增一個狀態變數,如下程式碼所示:

@StateObject var viewModel = ViewModel()

宣告狀態變數viewModel作用是初始化ViewModel並交由SwiftUI進行管理儲存,這樣我們就可以在View檢視中使用ViewModel模式檢視中的所有方法和變數。

緊接著,我們將原來在List使用的用@State宣告的models陣列替換成viewModel中的陣列models,如下圖所示:

如此,通過URLSession API,我們從網路請求了JSON檔案資料,並把它解析到了LIst中並展示出來了。如下圖所示:

方法優化:優化“新增身份卡”方法

接下來我們來優化新增身份卡的方法,在ViewModel模型檢視中,我們使用@Published聲明瞭資料模型陣列models,並在ContentView頁面中替換了使用@State宣告的models陣列。

因此我們可以將所有網路請求、新增、編輯、刪除的方法都放在ViewModel模型視圖裡,然後在所有頁面建立連結,新增方法如下程式碼所示:

// 建立身份卡 func addCard(newItem: Model) { models.append(newItem) }

上述程式碼中,我們建立了一個方法addCard,通過傳入符合Model資料模型格式的常量newItem中,然後將newItem新增到陣列models中,完成新增資料的動作。

接下來回到 NewView中,我們引用ViewModel檢視模型,並替換原來使用@Binding雙向繫結宣告的陣列models,如下程式碼所示:

var viewModel:ViewModel

這時候可能會報錯,沒有關係,這是因為原來@Binding雙向繫結宣告的陣列models建立的關係被破壞掉了,而為什麼不像ContentView那樣使用@StateObject var viewModel = ViewModel()宣告,這是因為在新增頁面,新增的資料要載入到ContentView的List列表中,進行資料之間的傳遞。

新增身份卡的方法需要替換原有的models,換成viewModel檢視模型中的addCard方法,如下程式碼所示:

self.viewModel.addCard(newItem: newItem)

並且NewView頁面預覽時,需要賦予變數viewModel值,如下程式碼所示:

NewView(viewModel: ViewModel())

上述程式碼中,我們使用ViewModel檢視模型代替純Model資料模型的陣列,並將原來的新增身份卡方法替換為ViewModel建立好的addCard方法,為了預覽當前NewView頁面,還需要給NewView宣告的viewModel變數賦予預設值ViewModel()。

在NewView頁面調整後,由於之前我們還在引用NewView檢視的相關頁面進行了繫結,這時候要回到ContentView檢視中,解除之前的繫結,如下圖所示:

NewView(viewModel: viewModel)

如此,我們便實現了使用Model-View-ViewModel架構模式調整原有專案程式碼,並實現引用ViewModel檢視模型中的方法來建立檢視,做到將頁面和資料分隔開,使得程式碼進一步精煉。

方法新增:增加“刪除身份卡”的方法

調整完“新增身份卡”方法後,我們來補充“刪除身份卡”方法。刪除身份卡的方法和新增身份卡方法一樣,我們可以在ViewModel檢視模型中建立,如下程式碼所示:

// 刪除身份卡片 func deleteItem(itemId: UUID) { models.removeAll(where: {$0.id == itemId}) }

上述程式碼中,我們建立了一個刪除身份卡片的方法deleteItem,傳入身份卡片資料的UUID,作為索引找到對應的身份卡片,然後呼叫removeAll方法,刪除models陣列中對應UUID對傳入的UUID的資料,實現刪除資料的操作。

回到ContentView頁面,我們建立一個簡單的刪除身份卡的互動,使用contextMenu上下文選單按鈕搭建刪除互動,如下程式碼所示:

// 卡片檢視 CardView(platformIcon: item.platformIcon, title: item.title, platformName: item.platformName, indexURL: item.indexURL) // 長按喚起刪除 .contextMenu { Button(action: { self.viewModel.deleteItem(itemId: item.id) }, label: { Text("刪除") }) }

上述程式碼中,我們給CardView卡片檢視添加了一個上下文選單的互動操作,當點選CardView卡片時,喚起上下文選單,點選選單按鈕呼叫viewModel檢視模型的deleteItem刪除方法,指定刪除的卡片id為點選卡片的UUID。

我們長按點選卡片預覽下效果,如下圖所示:

上面的方法是使用contextMenu上下文選單實現身份卡片的方法,比較簡單。

專案預覽

完成後,我們在模擬器中預覽下效果,如下圖所示:

e50ac57a-bdfb-4c7a-9163-cc77edd21d4d.gif

專案小結

在本章中,我們學習了Model-View-ViewModel模型-檢視-模型檢視架構模式進行專案的開發,將使用URLSession API實現網路請求,將JSON資料檔案從雲端請求到本地渲染,其中對於JSON檔案缺少UUID的處理至關重要。

功能方法方面,我們在ViewModel檢視模型中建立了addCard新增身份卡方法替換了原有在View檢視中實現的操作邏輯,進一步地,我們建立了deleteItem刪除身份卡方法,通過識別當前卡片的UUID,指定卡片進行刪除操作。

自此,Linkworld專案的功能越來越完善了,但還不夠好。接下來的章節,我們介紹另一種刪除資料項的互動,以及編輯頁面、排序等功能的實現,請保持期待吧~

版權宣告

本文為稀土掘金技術社群首發簽約文章,14天內禁止轉載,14天后未獲授權禁止轉載,侵權必究!

「其他文章」