版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2020.12.17 星期四 |
前言
今天翻閱蘋果的API文檔,發(fā)現(xiàn)多了一個框架SwiftUI粱栖,這里我們就一起來看一下這個框架。感興趣的看下面幾篇文章。
1. SwiftUI框架詳細(xì)解析 (一) —— 基本概覽(一)
2. SwiftUI框架詳細(xì)解析 (二) —— 基于SwiftUI的閃屏頁的創(chuàng)建(一)
3. SwiftUI框架詳細(xì)解析 (三) —— 基于SwiftUI的閃屏頁的創(chuàng)建(二)
4. SwiftUI框架詳細(xì)解析 (四) —— 使用SwiftUI進行蘋果登錄(一)
5. SwiftUI框架詳細(xì)解析 (五) —— 使用SwiftUI進行蘋果登錄(二)
6. SwiftUI框架詳細(xì)解析 (六) —— 基于SwiftUI的導(dǎo)航的實現(xiàn)(一)
7. SwiftUI框架詳細(xì)解析 (七) —— 基于SwiftUI的導(dǎo)航的實現(xiàn)(二)
8. SwiftUI框架詳細(xì)解析 (八) —— 基于SwiftUI的動畫的實現(xiàn)(一)
9. SwiftUI框架詳細(xì)解析 (九) —— 基于SwiftUI的動畫的實現(xiàn)(二)
10. SwiftUI框架詳細(xì)解析 (十) —— 基于SwiftUI構(gòu)建各種自定義圖表(一)
11. SwiftUI框架詳細(xì)解析 (十一) —— 基于SwiftUI構(gòu)建各種自定義圖表(二)
12. SwiftUI框架詳細(xì)解析 (十二) —— 基于SwiftUI創(chuàng)建Mind-Map UI(一)
13. SwiftUI框架詳細(xì)解析 (十三) —— 基于SwiftUI創(chuàng)建Mind-Map UI(二)
開始
首先看下主要內(nèi)容:
在本教程中,您將學(xué)習(xí)如何使用
Firebase Cloud Firestore
將持久性添加到SwiftUI iOS
應(yīng)用程序篱蝇。內(nèi)容來自翻譯。
接著看下主要內(nèi)容:
Swift 5, iOS 14, Xcode 12
接著就是主要內(nèi)容:
Google
的移動后端服務(wù)徽曲,即Firebase
零截,為應(yīng)用程序開發(fā)人員提供了從分析到分發(fā)(Analytics to Distribution)
,再到數(shù)據(jù)庫和身份驗證(Databases and Authentication)
的所有功能秃臣。在本教程中涧衙,您將了解Cloud Firestore(該服務(wù)套件的一部分)以及如何將其與SwiftUI
結(jié)合使用。
Cloud Firestore
是一個靈活的NoSQL
云數(shù)據(jù)庫,開發(fā)人員可以使用它實時存儲和同步應(yīng)用程序數(shù)據(jù)弧哎。您將使用它為FireCard
提供數(shù)據(jù)和服務(wù)雁比,該應(yīng)用程序可通過創(chuàng)建卡片來幫助用戶記住概念。
在此過程中傻铣,您將學(xué)習(xí)如何:
Set up Firestore.
Use MVVM to structure an scalable code base.
Manipulate data using Combine and Firestore.
Use anonymous authentication.
打開入門項目章贞。
Firecards
是一個簡單的工具,可讓用戶通過提供問題和答案來創(chuàng)建卡片非洲。稍后,他們可以通過閱讀問題并輕按卡片上的內(nèi)容來測試自己的記憶力蜕径,以查看其答案是否正確两踏。
目前,該應(yīng)用沒有持久性存儲(persistence)
兜喻,因此用戶無法對其做太多事情梦染。但是您可以通過將Firestore
添加到此SwiftUI
應(yīng)用中來解決此問題。
您將不會使用它幾個步驟朴皆,而是在Xcode
中打開FireCards.xcodeproj
帕识。
注意:該項目使用
Swift Package Manager
來管理依賴項。由于Firebase SDK
很大遂铡,因此建議您在閱讀本教程的同時在后臺打開項目肮疗,并讓其獲取并解決所有依賴項。
1. Setting Up Firebase
您必須先創(chuàng)建一個Firebase
帳戶扒接,然后才能使用Cloud Firestore
伪货。轉(zhuǎn)到Firebase網(wǎng)站。在右上角钾怔,單擊Go to console
碱呼。然后提供您的Google帳戶憑據(jù)(Google account credentials)
;如果您還沒有憑據(jù)宗侦,請創(chuàng)建一個愚臀。
接下來,單擊+ Add project
矾利。將會出現(xiàn)一個modal
姑裂,詢問您的項目名稱。類型FireCard
:
下一步要求您為項目啟用Google Analytics
(分析)梦皮。 由于本教程沒有介紹Analytics
(分析)炭分,因此請單擊底部的切換按鈕將其禁用。 然后單擊Create project
:
幾秒鐘后剑肯,您會看到一條消息捧毛,上面寫著Your new project is ready
。 點擊Continue
,您將看到項目的儀表板:
您可以在此處訪問所有Firebase
服務(wù)呀忧。 選擇Add an app to get started
的圓圈iOS按鈕開始使用师痕。 在iOS Bundle ID
字段中輸入com.raywenderlich.firecards
,然后單擊Register app
:
按照概述的說明下載GoogleService-Info.plist
并將其拖到FireCards Xcode
項目中:
當(dāng)Xcode
提示時而账,請確保選中Copy Items if needed
胰坟。
下一步要求您將Firebase SDK
添加到您的iOS應(yīng)用中,這已經(jīng)為您完成泞辐。 單擊Next
轉(zhuǎn)到Add initialization code
步驟笔横。
打開AppDelegate.swift
。 通過添加以下導(dǎo)入語句咐吼,確保包括Firebase
:
import Firebase
接下來吹缔,將此代碼添加到application(_:didFinishLaunchingWithOptions :)
中的return
語句之前:
FirebaseApp.configure()
在Firebase
項目的網(wǎng)頁上,單擊Next
锯茄,然后單擊Continue to console
:
這會將您帶回到項目的概述頁面:
您完成了Firebase
的設(shè)置厢塘,并為您的應(yīng)用授予了訪問所有Firebase
服務(wù)的權(quán)限。 接下來肌幽,您將配置Cloud Firestore
晚碾。
2. Setting Up Cloud Firestore
在左側(cè)菜單的Develop
下,單擊Cloud Firestore
喂急。 然后格嘁,單擊Create database
:
將出現(xiàn)一個modal
,向您顯示下一步:
- 為
Cloud Firestore
設(shè)置安全規(guī)則煮岁。 - 設(shè)置
Cloud Firestore
位置讥蔽。
Firebase
使用安全規(guī)則來處理數(shù)據(jù)訪問授權(quán)。 選擇Start in test mode
画机,這將使您的數(shù)據(jù)在未經(jīng)授權(quán)的情況下可訪問30天冶伞。
盡管這對于測試項目是可以的,但是您應(yīng)該始終設(shè)置適當(dāng)?shù)?code>Security Rules步氏。 幸運的是响禽,您稍后將在Adding Authorization Using Security Rules
中進行介紹。
點擊Next
荚醒。 現(xiàn)在芋类,助手將詢問您要將數(shù)據(jù)存儲在何處。
請注意界阁,該位置可能會影響您的帳單侯繁,以后將無法更改。 現(xiàn)在泡躯,選擇nam5(us-central)
贮竟,然后單擊Enable
丽焊。
現(xiàn)在,Firebase
將為您配置所有內(nèi)容咕别。 然后技健,它將把您轉(zhuǎn)到Firebase
項目的Cloud Firestore
部分:
在這里,您將了解如何實時插入惰拱,刪除或更新數(shù)據(jù)雌贱。 您也可以根據(jù)需要手動操作它。
Architecting the App Using MVVM
對于此項目偿短,您將使用Model-View-View Model
或MVVM
來構(gòu)建應(yīng)用程序的組件欣孤。
MVVM
是一種結(jié)構(gòu)設(shè)計模式,它將構(gòu)成應(yīng)用程序的元素分為Views, View Models and Models
翔冀。 這種設(shè)計模式有助于開發(fā)人員從視圖中分離業(yè)務(wù)邏輯导街,并保持必要的關(guān)注點分離,以使視圖和模型與數(shù)據(jù)源和業(yè)務(wù)邏輯不可知纤子。
由于您將Cloud Firestore
用于數(shù)據(jù)持久性,因此將添加一個層來處理與數(shù)據(jù)源進行交互所需的邏輯款票。 對于此項目控硼,您將使用存儲庫模式。 下圖顯示了應(yīng)用程序體系結(jié)構(gòu)的最終表示形式:
-
Models
保存應(yīng)用程序數(shù)據(jù)艾少。它們代表了您的應(yīng)用需要管理的實體卡乾。 -
Views
構(gòu)成構(gòu)成應(yīng)用程序的視覺元素,并負(fù)責(zé)顯示模型中數(shù)據(jù)缚够。 -
View Model
通過轉(zhuǎn)換模型中的數(shù)據(jù)使其可以顯示在視圖中幔妨,從而使模型與視圖之間的關(guān)系成為可能。 -
Repository
表示處理數(shù)據(jù)源data source
通信的抽象谍椅。在這種情況下误堡,數(shù)據(jù)源是Cloud Firestore
雏吭。當(dāng)View Model
需要對數(shù)據(jù)進行任何操作時锁施,它會與Repository
進行通信,并通知有關(guān)數(shù)據(jù)更改的視圖杖们。
1. Thinking in Collections and Documents
Cloud Firestore
是NoSQL
數(shù)據(jù)庫悉抵。它使用集合和文檔(collections and document)
來構(gòu)造數(shù)據(jù)。
集合保存文檔摘完。這些文檔documents
的字段構(gòu)成了您應(yīng)用程序的實體姥饰,在本例中為卡片。因此孝治,卡片是文檔列粪,卡片組是集合审磁。
這是應(yīng)用程序數(shù)據(jù)結(jié)構(gòu)的直觀表示:
您可以編寫查詢(queries)
以從集合中獲取數(shù)據(jù),或插入篱竭,更新或刪除文檔力图。 為此,您需要使用唯一標(biāo)識符創(chuàng)建對集合或特定文檔的引用掺逼。 創(chuàng)建新文檔時吃媒,您可以手動傳遞此標(biāo)識符,否則Cloud Firestore
會為您創(chuàng)建一個吕喘。
聊夠了赘那,該寫代碼了!
2. Adding New Cards
首先創(chuàng)建Repository
以訪問數(shù)據(jù)氯质。
在項目導(dǎo)航器中募舟,右鍵單擊Repositories
,然后單擊New file…
闻察。 創(chuàng)建一個名為CardRepository.swift
的新Swift文件拱礁,并向其中添加以下代碼:
// 1
import FirebaseFirestore
import FirebaseFirestoreSwift
import Combine
// 2
class CardRepository: ObservableObject {
// 3
private let path: String = "cards"
// 4
private let store = Firestore.firestore()
// 5
func add(_ card: Card) {
do {
// 6
_ = try store.collection(path).addDocument(from: card)
} catch {
fatalError("Unable to add card: \(error.localizedDescription).")
}
}
}
在這里:
- 1) 導(dǎo)入
FirebaseFirestore,F(xiàn)irebaseFirestoreSwift
和Combine
辕漂。FirebaseFirestore
使您可以訪問Firestore API
呢灶,而Combine
為Swift提供了一組聲明性API。
FirebaseFirestoreSwift
添加了一些很酷的功能來幫助您將Firestore
與模型集成钉嘹。它使您可以將Cards
轉(zhuǎn)換為文檔鸯乃,并將文檔轉(zhuǎn)換為Cards
。
- 2) 定義
CardRepository
并使遵循ObservableObject
跋涣。ObservableObject
使用發(fā)布者幫助此類發(fā)出更改缨睡,因此其他對象可以偵聽并做出相應(yīng)的反應(yīng)。 - 3) 然后陈辱,聲明
path
并分配cards
的值奖年。這是Firestore
中的集合名稱。 - 4) 聲明
store
并分配對Firestore
實例的引用性置。 - 5) 接下來拾并,定義
add(_ :)
并使用do-catch
塊捕獲由代碼引發(fā)的任何錯誤。如果在更新文檔時出了點問題鹏浅,則會因致命錯誤終止應(yīng)用的執(zhí)行嗅义。 - 6) 使用
path
創(chuàng)建對cards
集合的引用,然后將card
傳遞到addDocument(from:encoder:completion :)
隐砸。這會將新卡添加到集合中之碗。
使用上面的代碼,編譯器將報addDocument(from:encoder:completion :)
要求Card
符合Encodable
季希。要解決此問題褪那,請打開Card.swift
并將類定義更改為此:
struct Card: Identifiable, Codable {
通過添加Codable
幽纷,Swift
可以無縫地對Cards
進行序列化和反序列化。 其中包括導(dǎo)致錯誤的Encodable
和將Firestore
文檔轉(zhuǎn)換為Swift
對象時將使用的Decodable
博敬。
3. Adding the View Model
您需要一個視圖模型才能將模型與視圖連接友浸。 在Project
導(dǎo)航器的ViewModels
組下,創(chuàng)建一個名為CardListViewModel.swift
的新Swift
文件偏窝。
將此添加到新文件:
// 1
import Combine
// 2
class CardListViewModel: ObservableObject {
// 3
@Published var cardRepository = CardRepository()
// 4
func add(_ card: Card) {
cardRepository.add(card)
}
}
下面進行細(xì)分:
- 1)
Combine
為您提供了處理異步代碼的API收恢。 - 2) 您聲明
CardListViewModel
并使它符合ObservableObject
。 這使您可以偵聽此類型對象發(fā)出的更改祭往。 - 3)
@Published
為此屬性創(chuàng)建一個發(fā)布者伦意,以便您可以訂閱它。 - 4) 您將
card
傳遞到存儲庫硼补,以便可以將其添加到集合中驮肉。
打開NewCardForm.swift
并為您創(chuàng)建的view model
添加一個屬性,緊隨NewCardForm
中的其他屬性之后:
@ObservedObject var cardListViewModel: CardListViewModel
之前的更改將使Xcode Preview
停止工作已骇,因為現(xiàn)在NewCardForm
需要一個CardListViewModel
离钝。 要解決此問題,請更新NewCardForm_Previews
:
static var previews: some View {
NewCardForm(cardListViewModel: CardListViewModel())
}
在NewCardForm
的底部添加以下addCard()
方法:
private func addCard() {
// 1
let card = Card(question: question, answer: answer)
// 2
cardListViewModel.add(card)
// 3
presentationMode.wrappedValue.dismiss()
}
這段代碼:
- 1) 使用已經(jīng)在頂部聲明的
question
和answer
屬性創(chuàng)建Card
褪储。 - 2) 使用
view model
添加新card
奈辰。 - 3) 關(guān)閉當(dāng)前視圖。
然后乱豆,通過將Button(action:{}){
替換為下面代碼,將此新方法稱為Add New Card
的操作吊趾。
Button(action: addCard) {
最后宛裕,打開CardListView.swift
,找到.sheet
修飾符论泛,并通過現(xiàn)在傳遞一個新的視圖模型實例來修復(fù)編譯器錯誤揩尸。 您稍后將使用共享實例。
.sheet(isPresented: $showForm) {
NewCardForm(cardListViewModel: CardListViewModel())
}
構(gòu)建并運行屁奏。
點擊右上角的+
岩榆。 填寫question and answer
字段,然后點擊Add New Card
坟瓢。
嗯勇边,什么都沒發(fā)生≌哿卡片未顯示在主屏幕中:
在網(wǎng)絡(luò)瀏覽器中打開Firebase Console
粒褒,然后轉(zhuǎn)到Cloud Firestore
部分。 Firestore
自動創(chuàng)建了cards
收藏诚镰。 單擊標(biāo)識符以導(dǎo)航到新文檔:
您的數(shù)據(jù)存儲在Firebase
中奕坟,但您仍未實現(xiàn)檢索和顯示卡片的邏輯祥款。
Retrieving and Displaying Cards
現(xiàn)在該展示您的卡片了! 首先月杉,您需要創(chuàng)建一個view model
來代表一張Card
刃跛。
在項目導(dǎo)航器的ViewModels
下柱告,創(chuàng)建一個名為CardViewModel.swift
的新Swift文件幽勒。
將此添加到新文件:
import Combine
// 1
class CardViewModel: ObservableObject, Identifiable {
// 2
private let cardRepository = CardRepository()
@Published var card: Card
// 3
private var cancellables: Set<AnyCancellable> = []
// 4
var id = ""
init(card: Card) {
self.card = card
// 5
$card
.compactMap { $0.id }
.assign(to: \.id, on: self)
.store(in: &cancellables)
}
}
在這里:
- 1) 聲明
CardViewModel
并使它與ObservableObject
兼容,以便它可以發(fā)出更改和Identifiable
护戳,從而保證您可以迭代CardViewModels
的數(shù)組首懈。 - 2) 這為實際的
card
模型提供了引用绊率。@Published
為此屬性創(chuàng)建一個發(fā)布者,以便您可以訂閱它究履。 - 3)
cancellables
用于存儲您的訂閱滤否,因此您以后可以取消訂閱。 - 4)
id
是要求遵循Identifiable
的屬性最仑。 它應(yīng)該是唯一的標(biāo)識符藐俺。 - 5) 在卡的
ID
和視圖模型的ID
之間為card
設(shè)置綁定。 然后將對象存儲在cancellables
對象中泥彤,以便以后可以取消欲芹。
1. Setting Up the Repository
您的存儲庫需要處理獲取卡的邏輯。 打開CardRepository.swift
并在屬性定義下方的頂部添加以下代碼:
// 1
@Published var cards: [Card] = []
// 2
init() {
get()
}
func get() {
// 3
store.collection(path)
.addSnapshotListener { querySnapshot, error in
// 4
if let error = error {
print("Error getting cards: \(error.localizedDescription)")
return
}
// 5
self.cards = querySnapshot?.documents.compactMap { document in
// 6
try? document.data(as: Card.self)
} ?? []
}
}
在上面的代碼中吟吝,您:
- 1) 定義
cards
菱父。@Published
為此屬性創(chuàng)建一個發(fā)布者,以便您可以訂閱它剑逃。 每次修改此數(shù)組時浙宜,所有偵聽者都會做出相應(yīng)的反應(yīng)。 - 2) 創(chuàng)建初始化方法并調(diào)用
get()
蛹磺。 - 3) 使用
path
獲取對集合根目錄的引用粟瞬,并添加一個偵聽器以接收集合中的更改。 - 4) 檢查是否發(fā)生錯誤萤捆,打印錯誤消息并返回裙品。
- 5) 在
querySnapshot.documents
上使用compactMap(_ :)
遍歷所有元素。 如果querySnapshot
為nil
俗或,則改為設(shè)置一個空數(shù)組市怎。 - 6) 使用
data(as:decoder :)
將每個文檔映射為Card
。 您可以這樣做蕴侣,這要歸功于您在頂部導(dǎo)入的FirebaseFirestoreSwift
焰轻,并且Card
符合Codable
。
2. Setting Up CardListViewModel
接下來昆雀,打開CardListViewModel.swift
并將這兩個屬性添加到CardListViewModel
:
// 1
@Published var cardViewModels: [CardViewModel] = []
// 2
private var cancellables: Set<AnyCancellable> = []
在此代碼中辱志,您:
- 1) 使用
@Published
屬性包裝器定義cardViewModels
蝠筑,以便您可以訂閱它。 它將包含CardViewModels
數(shù)組揩懒。 - 2) 創(chuàng)建一組
AnyCancellables
什乙。 它將用于存儲您的訂閱,以便您以后可以取消訂閱已球。
仍在視圖模型中時臣镣,添加以下初始化程序:
init() {
// 1
cardRepository.$cards.map { cards in
cards.map(CardViewModel.init)
}
// 2
.assign(to: \.cardViewModels, on: self)
// 3
.store(in: &cancellables)
}
您添加的代碼:
- 1) 監(jiān)聽
cards
,并將數(shù)組的每個Card
元素映射到CardViewModel
中智亮。 這將創(chuàng)建一個CardViewModels
數(shù)組忆某。 - 2) 將前一個映射操作的結(jié)果分配給
cardViewModels
。 - 3) 將此預(yù)訂的實例存儲在
cancellables
的對象中阔蛉,以便在取消初始化CardListViewModel
時自動將其取消弃舒。
3. Setting Up CardView
打開CardView.swift
并進行以下更改。
將var card: Card
替換為:
var cardViewModel: CardViewModel
這使視圖可以直接使用視圖模型而不是Card
模型状原。
然后聋呢,在frontView
中,將card.question
替換為:
cardViewModel.card.question
接下來颠区,在backView
中削锰,將card.answer
替換為:
cardViewModel.card.answer
最后,將CardView_Previews
更改為此:
struct CardView_Previews: PreviewProvider {
static var previews: some View {
let card = testData[0]
return CardView(cardViewModel: CardViewModel(card: card))
}
}
進行了這些更改后毕莱,您現(xiàn)在可以直接傳遞預(yù)期的CardViewModel
而不是Card
模型器贩。 但是,您需要再進行一次更新才能再次使用預(yù)覽朋截。
4. Setting Up CardListView
您還需要更改包裝清單視圖磨澡,以便它與card view model
一起使用。
打開CardListView.swift
并將cards array
屬性替換為:
@ObservedObject var cardListViewModel = CardListViewModel()
有了這一更改质和,CardListView
現(xiàn)在期望使用CardListViewModel
而不是Cards
數(shù)組。 @ObservedObject
將訂閱該屬性稚字,以便它可以偵聽視圖模型中的更改饲宿。
在主體中查找ForEach
語句,并將其更改為如下所示:
ForEach(cardListViewModel.cardViewModels) { cardViewModel in
CardView(cardViewModel: cardViewModel)
.padding([.leading, .trailing])
}
現(xiàn)在胆描,您將遍歷cardListViewModel
的各個卡片視圖模型瘫想,并為每個模型創(chuàng)建一個CardView
。
由于CardListView
現(xiàn)在需要CardListViewModel
而不是Cards
數(shù)組昌讲,因此將CardListView_Previews
更改為:
CardListView(cardListViewModel: CardListViewModel())
構(gòu)建并運行
根據(jù)需要添加任意數(shù)量的卡片国夜,并查看它們?nèi)绾瘟⒓达@示在主屏幕上。
Updating Cards
該應(yīng)用程序可讓用戶在獲得正確答案時進行標(biāo)記短绸。 如果不是這種情況车吹,則會彈出一條消息筹裕,告訴他們上次嘗試失敗。
打開Card.swift
并修改id
窄驹,如下所示:
@DocumentID var id: String?
注意:這樣做會更改您的數(shù)據(jù)模型朝卒,因為
id
不會包含在其中。 下次您運行該應(yīng)用程序時乐埠,以前的模型將不會顯示抗斤。
在頂部添加此導(dǎo)入語句:
import FirebaseFirestoreSwift
使用此代碼,您可以確保當(dāng)Firebase
的SDK
將文檔轉(zhuǎn)換為Card
時丈咐,Cloud Firestore
中使用的Document Id
會映射到id
瑞眼。 要對單個文檔執(zhí)行操作,您需要使用其document id
對其進行引用棵逊。
打開CardRepository.swift
并將下一個方法添加到CardRepository
:
func update(_ card: Card) {
// 1
guard let cardId = card.id else { return }
// 2
do {
// 3
try store.collection(path).document(cardId).setData(from: card)
} catch {
fatalError("Unable to update card: \(error.localizedDescription).")
}
}
這段代碼:
- 1) 檢查
card.id
是否具有值伤疙。 - 2) 捕獲代碼生成的任何異常。 如果在更新文檔時出現(xiàn)問題歹河,則該應(yīng)用程序?qū)⒔K止并顯示致命錯誤掩浙。
- 3) 使用
path
和cardId
,它獲取對cards
集合中文檔的引用秸歧,然后通過將card
傳遞給setData(from:encoder:completion :)
來更新字段厨姚。
現(xiàn)在,您需要更新視圖模型键菱。 打開CardViewModel.swift
并將以下方法添加到CardViewModel
:
func update(card: Card) {
cardRepository.update(card)
}
打開CardView.swift
谬墙。 在frontView
中的第二個Spacer()
之后添加以下代碼:
if !cardViewModel.card.successful {
Text("You answered this one incorrectly before")
.foregroundColor(.white)
.font(.system(size: 11.0))
.fontWeight(.bold)
.padding()
}
如果card
的屬性successful
等于false
,則此代碼顯示一條消息经备。
在繼續(xù)之前拭抬,將以下三種方法添加到CardView
:
// 1
private func markCardAsUnsuccesful() {
var updatedCard = cardViewModel.card
updatedCard.successful = false
update(card: updatedCard)
}
// 2
private func markCardAsSuccesful() {
var updatedCard = cardViewModel.card
updatedCard.successful = true
update(card: updatedCard)
}
// 3
func update(card: Card) {
cardViewModel.update(card: card)
showContent.toggle()
}
該代碼提供了兩種方法來處理成功和失敗的case
,以及一種用于更新card
的方法侵蒙。
每種方法的作用如下:
- 1) 將
cardViewModel.card
復(fù)制到updatedCard
并將successful
設(shè)置為false
造虎。 然后調(diào)用update(card :)
。 - 2) 將
cardViewModel.card
復(fù)制到UpdatedCard
并將successful
設(shè)置為true
纷闺。 然后調(diào)用update(card :)
算凿。 - 3) 將更新的卡片傳遞給
update(card :)
,以便視圖模型可以更新模型犁功。 然后在showContent
上調(diào)用toggle()
來觸發(fā)翻轉(zhuǎn)動畫氓轰。
接下來,將backView
替換為以下內(nèi)容:
var backView: some View {
VStack {
// 1
Spacer()
Text(cardViewModel.card.answer)
.foregroundColor(.white)
.font(.body)
.padding(20.0)
.multilineTextAlignment(.center)
.animation(.easeInOut)
Spacer()
// 2
HStack(spacing: 40) {
Button(action: markCardAsSuccesful) {
Image(systemName: "hand.thumbsup.fill")
.padding()
.background(Color.green)
.font(.title)
.foregroundColor(.white)
.clipShape(Circle())
}
Button(action: markCardAsUnsuccesful) {
Image(systemName: "hand.thumbsdown.fill")
.padding()
.background(Color.blue)
.font(.title)
.foregroundColor(.white)
.clipShape(Circle())
}
}
.padding()
}
.rotation3DEffect(.degrees(180), axis: (x: 0.0, y: 1.0, z: 0.0))
}
在這里浸卦,您添加了兩個新按鈕署鸡,以便用戶可以指示他們是否正確回答了問題。
構(gòu)建并運行。
點擊任意卡靴庆,然后點擊拇指向下thumb-down
圖標(biāo)时捌。 在底部,前視圖顯示一條消息撒穷,提示You answered this one incorrectly before
:
Removing Cards
用戶應(yīng)能夠在需要時取出卡匣椰。
打開CardRepository.swift
并在CardRepository
的底部定義remove(_ :)
,如下所示:
func remove(_ card: Card) {
// 1
guard let cardId = card.id else { return }
// 2
store.collection(path).document(cardId).delete { error in
if let error = error {
print("Unable to remove card: \(error.localizedDescription)")
}
}
}
這段代碼:
- 1) 檢查
card.id
是否具有值并將其存儲在cardId
中端礼。 - 2) 使用
path
和cardId
獲取對Cards
集合中文檔的引用禽笑,然后調(diào)用delete
。 這將從Cloud Firestore
中的集合中刪除文檔蛤奥。
delete(completion :)
還提供了一個閉包佳镜,您可以在其中處理任何錯誤。 閉包中的代碼檢查是否有錯誤凡桥,并將其打印到控制臺蟀伸。
打開CardViewModel.swift
并向其中添加此方法,以便您的視圖模型可以調(diào)用CardRepository
上的remove(_ :)
缅刽,并傳遞實際的Card
:
func remove() {
cardRepository.remove(card)
}
最后啊掏,打開CardView.swift
并在Alert
內(nèi)部的primaryButton
的尾隨閉包中添加cardViewModel.remove()
,因此如下所示:
Alert(
title: Text("Remove Card"),
message: Text("Are you sure you want to remove this card?"),
primaryButton: .destructive(Text("Remove")) {
cardViewModel.remove()
},
secondaryButton: .cancel())
這將在cardViewModel
上調(diào)用remove()
衰猛。 然后迟蜜,視圖模型執(zhí)行邏輯以從數(shù)據(jù)庫中刪除卡。
構(gòu)建并運行啡省。
將您的任何卡片拖到頂部娜睛。 出現(xiàn)alert
,要求您確認(rèn)操作卦睹。 點擊Remove
畦戒,您的卡將消失。
Securing the Data
安全性對于任何應(yīng)用程序都是必不可少的结序。 Firebase
提供了一組身份驗證方法障斋,可用于讓用戶對您的應(yīng)用程序進行身份驗證。 對于本項目徐鹤,您將實現(xiàn)匿名身份驗證Anonymous Authentication
配喳。
匿名身份驗證(Anonymous Authentication)
是一種身份驗證類型,可讓您為尚未注冊應(yīng)用的用戶創(chuàng)建臨時帳戶凳干,從而為他們提供了一層安全保護。 與安全規(guī)則(Security Rules)
結(jié)合使用被济,匿名身份驗證為此應(yīng)用程序提供了足夠的安全性救赐。
要激活此身份驗證模式,請轉(zhuǎn)到Firebase
控制臺,在左側(cè)邊欄中選擇Authentication
经磅,然后在頂部導(dǎo)航欄上選擇Sign-in method
泌绣。 轉(zhuǎn)到Providers List
的底部,選擇Anonymous
预厌,然后單擊右側(cè)的開關(guān)將其啟用阿迈。 最后,單擊Save
轧叽。
注意:如果您沒有看到頂部的導(dǎo)航欄苗沧,請單擊
Get Started
以跳過介紹性屏幕。
現(xiàn)在炭晒,您需要創(chuàng)建一個身份驗證服務(wù)待逞。
1. Creating an authentication service
在項目導(dǎo)航器中,在Services
下創(chuàng)建一個新的Swift
文件网严,并將其命名為AuthenticationService.swift
识樱。
將以下代碼添加到新文件中:
import Firebase
// 1
class AuthenticationService: ObservableObject {
// 2
@Published var user: User?
private var authenticationStateHandler: AuthStateDidChangeListenerHandle?
// 3
init() {
addListeners()
}
// 4
static func signIn() {
if Auth.auth().currentUser == nil {
Auth.auth().signInAnonymously()
}
}
private func addListeners() {
// 5
if let handle = authenticationStateHandler {
Auth.auth().removeStateDidChangeListener(handle)
}
// 6
authenticationStateHandler = Auth.auth()
.addStateDidChangeListener { _, user in
self.user = user
}
}
}
這段代碼:
- 1) 聲明
AuthenticationService
并將其符合ObservableObject
。 - 2) 定義在身份驗證過程發(fā)生時將包含
User
對象的用戶震束。它還定義了一個authenticationStateHandler
屬性怜庸,以捕獲用戶對象中的更改,例如垢村,當(dāng)用戶登錄或注銷時割疾。 - 3) 實現(xiàn)
init()
并調(diào)用addListeners()
,以便在實例化該類時調(diào)用它肝断。 - 4) 添加
signIn()
杈曲,用于登錄Firebase
。Auth
將Firebase
用戶對象存儲在currentUser
中胸懈。
通過檢查它是否為nil
担扑,可以避免不必要的調(diào)用。此值存儲在本地趣钱,因此在第一次使用后涌献,該應(yīng)用使用同一用戶。
- 5) 檢查是否已實例化處理程序首有,如果已實例化燕垃,則將其刪除。
- 6) 將
addStateDidChangeListener(_ :)
監(jiān)聽器分配給authenticationStateHandler
井联。
好的卜壕,您已經(jīng)設(shè)置了身份驗證服務(wù)(Authentication Service)
!
2. Using the authentication service
打開AppDelegate.swift
并在application(_:didFinishLaunchingWithOptions:)
的FirebaseApp.configure()
之后添加以下行:
AuthenticationService.signIn()
此代碼可確保用戶在應(yīng)用啟動時登錄烙常。
接下來轴捎,打開CardRepository.swift
并將這些屬性添加到類的頂部:
// 1
var userId = ""
// 2
private let authenticationService = AuthenticationService()
// 3
private var cancellables: Set<AnyCancellable> = []
這段代碼:
- 1) 聲明
userId
,您將使用該ID
存儲Firebase
生成的當(dāng)前用戶ID。 - 2) 創(chuàng)建
AuthenticationService
的實例侦副。 - 3) 創(chuàng)建一組
AnyCancellables
侦锯。 此屬性存儲您的訂閱,因此您以后可以取消訂閱秦驯。
接下來尺碰,將init()
更改為此:
init() {
// 1
authenticationService.$user
.compactMap { user in
user?.uid
}
.assign(to: \.userId, on: self)
.store(in: &cancellables)
// 2
authenticationService.$user
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
// 3
self?.get()
}
.store(in: &cancellables)
}
在這里:
- 1) 將用戶ID從
AuthenticationService
綁定到存儲庫的userId
。 它還將對象存儲在cancellables
中译隘,以便以后可以取消亲桥。 - 2) 該代碼觀察用戶
user
的變化,使用receive(on:options :)
設(shè)置代碼執(zhí)行的線程细燎,然后使用sink(receiveValue:)
附加訂閱者两曼。 這樣可以保證,當(dāng)您從AuthenticationService
獲取用戶user
時玻驻,閉包中的代碼將在主線程中執(zhí)行悼凑。 - 3) 像在原始的初始化程序中一樣,調(diào)用
get()
璧瞬。
將add(_ :)
更改為此:
func add(_ card: Card) {
do {
var newCard = card
newCard.userId = userId
_ = try store.collection(path).addDocument(from: newCard)
} catch {
fatalError("Unable to add card: \(error.localizedDescription).")
}
}
在這里户辫,您制作了card
的副本,并將其userId
更改為存儲庫的userId
的值嗤锉。 現(xiàn)在渔欢,每次創(chuàng)建新卡時,它都會包含Firebase
生成的實際用戶id
瘟忱。
最后奥额,在get()
的.addSnapshotListener(_ :)
之前添加以下行:
.whereField("userId", isEqualTo: userId)
此代碼使您可以按userId
過濾卡片。
3. Adding Authorization Using Security Rules
在網(wǎng)絡(luò)瀏覽器中打開Firebase
項目访诱。 然后轉(zhuǎn)到Cloud Firestore
垫挨,然后單擊頂部水平導(dǎo)航欄上的Rules
。 您會看到類似以下內(nèi)容:
此類似于JavaScript
的代碼是Firestore Security Rules
触菜。 這些規(guī)則定義用戶是否有權(quán)訪問或修改文檔九榔。 用以下代碼替換現(xiàn)有代碼:
// 1
rules_version = '2';
// 2
service cloud.firestore {
// 3
match /databases/{database}/documents {
// 4
match /{document=**} {
allow read, write: if request.auth != null;
}
}
}
這是做什么的:
- 1) 將
rules_version
設(shè)置為“ 2”
。 目前涡相,這是最新版本哲泊,并確定如何解釋以下代碼。 - 2) 指示這些規(guī)則適用于哪些服務(wù)催蝗。 在這種情況下切威,請使用
Cloud Firestore
。 - 3) 指定規(guī)則應(yīng)匹配項目中的任何
Cloud Firestore
數(shù)據(jù)庫丙号。 - 4) 指定僅經(jīng)過身份驗證的用戶可以讀取或?qū)懭胛臋n先朦。
這些規(guī)則確定了應(yīng)用程序的授權(quán)部分且预。 通過身份驗證,您可以了解用戶是誰烙无。 通過授權(quán),您可以確定該用戶可以做什么遍尺。
單擊Publish
以保存更改截酷。 然后回到項目并構(gòu)建并運行。
該應(yīng)用目前已按用戶userId
進行過濾乾戏,因此不會顯示任何卡片迂苛。如果您添加一個新的,它將出現(xiàn)鼓择。您甚至可以關(guān)閉并重新打開該應(yīng)用程序三幻,僅顯示從現(xiàn)在開始創(chuàng)建的卡片。
Understanding Firestore Pricing
了解Cloud Firestore
的定價可以節(jié)省您花費過多的金錢呐能。請記住以下幾點:
-
Cloud Firestore
向您收取您執(zhí)行的操作數(shù):讀取念搬,寫入和刪除。 - 價格從一個地點到另一個地點有所不同摆出。
- 您還必須支付數(shù)據(jù)庫使用的存儲空間和網(wǎng)絡(luò)帶寬朗徊。
- 如果更改單個字段或完整的文檔,則視為一次操作偎漫。
- 讀取次數(shù)是返回的記錄數(shù)爷恳。因此,如果您的查詢返回了十個文檔象踊,那么您將有十次讀取温亲。如果可能,請使用
limit
限制查詢可以返回的文檔數(shù)杯矩。 - 您可以使用
Alerts
監(jiān)控當(dāng)前預(yù)算栈虚。您可以使用Google Cloud Console
對其進行配置,這也可以讓您檢查以前的發(fā)票并設(shè)置所需的每日支出菊碟。 -
Google Cloud Operation
可讓您監(jiān)控效果并獲取指標(biāo)节芥,這些指標(biāo)也可以幫助您制定預(yù)算。
如果可能逆害,您還應(yīng)該在本地緩存數(shù)據(jù)头镊,以避免從Firestore
請求數(shù)據(jù)。
您可以在the Firestore documentation文檔中找到更多信息魄幕。
在本教程中相艇,您學(xué)習(xí)了如何使用Cloud Firestore
持久存儲數(shù)據(jù)以及如何使用MVVM
將其與SwiftUI
視圖集成。 您還從頭開始學(xué)習(xí)了如何使用Firebase
實施Anonymous Authentication
纯陨。
Firebase
和Cloud Firestore
提供了更多功能坛芽。 如果您想更深入地了解Cloud Firestore
留储,請查看官方的official Cloud Firestore documentation。 或查看Firebase Tutorial: Getting Started咙轩,Firebase Tutorial: Real-time Chat获讳,Video Tutorial: Beginning Firebase。
后記
本篇主要講述了基于
Firebase Cloud Firestore
的SwiftUI iOS
程序的持久性添加活喊,感興趣的給個贊或者關(guān)注~~~