版本記錄
版本號 | 時間 |
---|---|
V1.0 | 2020.12.17 星期四 |
前言
今天翻閱蘋果的API文檔,發現多了一個框架SwiftUI,這里我們就一起來看一下這個框架。感興趣的看下面幾篇文章。
1. SwiftUI框架詳細解析 (一) —— 基本概覽(一)
2. SwiftUI框架詳細解析 (二) —— 基于SwiftUI的閃屏頁的創建(一)
3. SwiftUI框架詳細解析 (三) —— 基于SwiftUI的閃屏頁的創建(二)
4. SwiftUI框架詳細解析 (四) —— 使用SwiftUI進行蘋果登錄(一)
5. SwiftUI框架詳細解析 (五) —— 使用SwiftUI進行蘋果登錄(二)
6. SwiftUI框架詳細解析 (六) —— 基于SwiftUI的導航的實現(一)
7. SwiftUI框架詳細解析 (七) —— 基于SwiftUI的導航的實現(二)
8. SwiftUI框架詳細解析 (八) —— 基于SwiftUI的動畫的實現(一)
9. SwiftUI框架詳細解析 (九) —— 基于SwiftUI的動畫的實現(二)
10. SwiftUI框架詳細解析 (十) —— 基于SwiftUI構建各種自定義圖表(一)
11. SwiftUI框架詳細解析 (十一) —— 基于SwiftUI構建各種自定義圖表(二)
12. SwiftUI框架詳細解析 (十二) —— 基于SwiftUI創建Mind-Map UI(一)
13. SwiftUI框架詳細解析 (十三) —— 基于SwiftUI創建Mind-Map UI(二)
開始
首先看下主要內容:
在本教程中,您將學習如何使用
Firebase Cloud Firestore
將持久性添加到SwiftUI iOS
應用程序。內容來自翻譯。
接著看下主要內容:
Swift 5, iOS 14, Xcode 12
接著就是主要內容:
Google
的移動后端服務,即Firebase
,為應用程序開發人員提供了從分析到分發(Analytics to Distribution)
,再到數據庫和身份驗證(Databases and Authentication)
的所有功能。在本教程中,您將了解Cloud Firestore(該服務套件的一部分)以及如何將其與SwiftUI
結合使用。
Cloud Firestore
是一個靈活的NoSQL
云數據庫,開發人員可以使用它實時存儲和同步應用程序數據。您將使用它為FireCard
提供數據和服務,該應用程序可通過創建卡片來幫助用戶記住概念。
在此過程中,您將學習如何:
Set up Firestore.
Use MVVM to structure an scalable code base.
Manipulate data using Combine and Firestore.
Use anonymous authentication.
打開入門項目。
Firecards
是一個簡單的工具,可讓用戶通過提供問題和答案來創建卡片。稍后,他們可以通過閱讀問題并輕按卡片上的內容來測試自己的記憶力,以查看其答案是否正確。
目前,該應用沒有持久性存儲(persistence)
,因此用戶無法對其做太多事情。但是您可以通過將Firestore
添加到此SwiftUI
應用中來解決此問題。
您將不會使用它幾個步驟,而是在Xcode
中打開FireCards.xcodeproj
。
注意:該項目使用
Swift Package Manager
來管理依賴項。由于Firebase SDK
很大,因此建議您在閱讀本教程的同時在后臺打開項目,并讓其獲取并解決所有依賴項。
1. Setting Up Firebase
您必須先創建一個Firebase
帳戶,然后才能使用Cloud Firestore
。轉到Firebase網站。在右上角,單擊Go to console
。然后提供您的Google帳戶憑據(Google account credentials)
;如果您還沒有憑據,請創建一個。
接下來,單擊+ Add project
。將會出現一個modal
,詢問您的項目名稱。類型FireCard
:
下一步要求您為項目啟用Google Analytics
(分析)。 由于本教程沒有介紹Analytics
(分析),因此請單擊底部的切換按鈕將其禁用。 然后單擊Create project
:
幾秒鐘后,您會看到一條消息,上面寫著Your new project is ready
。 點擊Continue
,您將看到項目的儀表板:
您可以在此處訪問所有Firebase
服務。 選擇Add an app to get started
的圓圈iOS按鈕開始使用。 在iOS Bundle ID
字段中輸入com.raywenderlich.firecards
,然后單擊Register app
:
按照概述的說明下載GoogleService-Info.plist
并將其拖到FireCards Xcode
項目中:
當Xcode
提示時,請確保選中Copy Items if needed
。
下一步要求您將Firebase SDK
添加到您的iOS應用中,這已經為您完成。 單擊Next
轉到Add initialization code
步驟。
打開AppDelegate.swift
。 通過添加以下導入語句,確保包括Firebase
:
import Firebase
接下來,將此代碼添加到application(_:didFinishLaunchingWithOptions :)
中的return
語句之前:
FirebaseApp.configure()
在Firebase
項目的網頁上,單擊Next
,然后單擊Continue to console
:
這會將您帶回到項目的概述頁面:
您完成了Firebase
的設置,并為您的應用授予了訪問所有Firebase
服務的權限。 接下來,您將配置Cloud Firestore
。
2. Setting Up Cloud Firestore
在左側菜單的Develop
下,單擊Cloud Firestore
。 然后,單擊Create database
:
將出現一個modal
,向您顯示下一步:
- 為
Cloud Firestore
設置安全規則。 - 設置
Cloud Firestore
位置。
Firebase
使用安全規則來處理數據訪問授權。 選擇Start in test mode
,這將使您的數據在未經授權的情況下可訪問30天。
盡管這對于測試項目是可以的,但是您應該始終設置適當的Security Rules
。 幸運的是,您稍后將在Adding Authorization Using Security Rules
中進行介紹。
點擊Next
。 現在,助手將詢問您要將數據存儲在何處。
請注意,該位置可能會影響您的帳單,以后將無法更改。 現在,選擇nam5(us-central)
,然后單擊Enable
。
現在,Firebase
將為您配置所有內容。 然后,它將把您轉到Firebase
項目的Cloud Firestore
部分:
在這里,您將了解如何實時插入,刪除或更新數據。 您也可以根據需要手動操作它。
Architecting the App Using MVVM
對于此項目,您將使用Model-View-View Model
或MVVM
來構建應用程序的組件。
MVVM
是一種結構設計模式,它將構成應用程序的元素分為Views, View Models and Models
。 這種設計模式有助于開發人員從視圖中分離業務邏輯,并保持必要的關注點分離,以使視圖和模型與數據源和業務邏輯不可知。
由于您將Cloud Firestore
用于數據持久性,因此將添加一個層來處理與數據源進行交互所需的邏輯。 對于此項目,您將使用存儲庫模式。 下圖顯示了應用程序體系結構的最終表示形式:
-
Models
保存應用程序數據。它們代表了您的應用需要管理的實體。 -
Views
構成構成應用程序的視覺元素,并負責顯示模型中數據。 -
View Model
通過轉換模型中的數據使其可以顯示在視圖中,從而使模型與視圖之間的關系成為可能。 -
Repository
表示處理數據源data source
通信的抽象。在這種情況下,數據源是Cloud Firestore
。當View Model
需要對數據進行任何操作時,它會與Repository
進行通信,并通知有關數據更改的視圖。
1. Thinking in Collections and Documents
Cloud Firestore
是NoSQL
數據庫。它使用集合和文檔(collections and document)
來構造數據。
集合保存文檔。這些文檔documents
的字段構成了您應用程序的實體,在本例中為卡片。因此,卡片是文檔,卡片組是集合。
這是應用程序數據結構的直觀表示:
您可以編寫查詢(queries)
以從集合中獲取數據,或插入,更新或刪除文檔。 為此,您需要使用唯一標識符創建對集合或特定文檔的引用。 創建新文檔時,您可以手動傳遞此標識符,否則Cloud Firestore
會為您創建一個。
聊夠了,該寫代碼了!
2. Adding New Cards
首先創建Repository
以訪問數據。
在項目導航器中,右鍵單擊Repositories
,然后單擊New file…
。 創建一個名為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) 導入
FirebaseFirestore,FirebaseFirestoreSwift
和Combine
。FirebaseFirestore
使您可以訪問Firestore API
,而Combine
為Swift提供了一組聲明性API。
FirebaseFirestoreSwift
添加了一些很酷的功能來幫助您將Firestore
與模型集成。它使您可以將Cards
轉換為文檔,并將文檔轉換為Cards
。
- 2) 定義
CardRepository
并使遵循ObservableObject
。ObservableObject
使用發布者幫助此類發出更改,因此其他對象可以偵聽并做出相應的反應。 - 3) 然后,聲明
path
并分配cards
的值。這是Firestore
中的集合名稱。 - 4) 聲明
store
并分配對Firestore
實例的引用。 - 5) 接下來,定義
add(_ :)
并使用do-catch
塊捕獲由代碼引發的任何錯誤。如果在更新文檔時出了點問題,則會因致命錯誤終止應用的執行。 - 6) 使用
path
創建對cards
集合的引用,然后將card
傳遞到addDocument(from:encoder:completion :)
。這會將新卡添加到集合中。
使用上面的代碼,編譯器將報addDocument(from:encoder:completion :)
要求Card
符合Encodable
。要解決此問題,請打開Card.swift
并將類定義更改為此:
struct Card: Identifiable, Codable {
通過添加Codable
,Swift
可以無縫地對Cards
進行序列化和反序列化。 其中包括導致錯誤的Encodable
和將Firestore
文檔轉換為Swift
對象時將使用的Decodable
。
3. Adding the View Model
您需要一個視圖模型才能將模型與視圖連接。 在Project
導航器的ViewModels
組下,創建一個名為CardListViewModel.swift
的新Swift
文件。
將此添加到新文件:
// 1
import Combine
// 2
class CardListViewModel: ObservableObject {
// 3
@Published var cardRepository = CardRepository()
// 4
func add(_ card: Card) {
cardRepository.add(card)
}
}
下面進行細分:
- 1)
Combine
為您提供了處理異步代碼的API。 - 2) 您聲明
CardListViewModel
并使它符合ObservableObject
。 這使您可以偵聽此類型對象發出的更改。 - 3)
@Published
為此屬性創建一個發布者,以便您可以訂閱它。 - 4) 您將
card
傳遞到存儲庫,以便可以將其添加到集合中。
打開NewCardForm.swift
并為您創建的view model
添加一個屬性,緊隨NewCardForm
中的其他屬性之后:
@ObservedObject var cardListViewModel: CardListViewModel
之前的更改將使Xcode Preview
停止工作,因為現在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) 使用已經在頂部聲明的
question
和answer
屬性創建Card
。 - 2) 使用
view model
添加新card
。 - 3) 關閉當前視圖。
然后,通過將Button(action:{}){
替換為下面代碼,將此新方法稱為Add New Card
的操作。
Button(action: addCard) {
最后,打開CardListView.swift
,找到.sheet
修飾符,并通過現在傳遞一個新的視圖模型實例來修復編譯器錯誤。 您稍后將使用共享實例。
.sheet(isPresented: $showForm) {
NewCardForm(cardListViewModel: CardListViewModel())
}
構建并運行。
點擊右上角的+
。 填寫question and answer
字段,然后點擊Add New Card
。
嗯,什么都沒發生。卡片未顯示在主屏幕中:
在網絡瀏覽器中打開Firebase Console
,然后轉到Cloud Firestore
部分。 Firestore
自動創建了cards
收藏。 單擊標識符以導航到新文檔:
您的數據存儲在Firebase
中,但您仍未實現檢索和顯示卡片的邏輯。
Retrieving and Displaying Cards
現在該展示您的卡片了! 首先,您需要創建一個view model
來代表一張Card
。
在項目導航器的ViewModels
下,創建一個名為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
兼容,以便它可以發出更改和Identifiable
,從而保證您可以迭代CardViewModels
的數組。 - 2) 這為實際的
card
模型提供了引用。@Published
為此屬性創建一個發布者,以便您可以訂閱它。 - 3)
cancellables
用于存儲您的訂閱,因此您以后可以取消訂閱。 - 4)
id
是要求遵循Identifiable
的屬性。 它應該是唯一的標識符。 - 5) 在卡的
ID
和視圖模型的ID
之間為card
設置綁定。 然后將對象存儲在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
為此屬性創建一個發布者,以便您可以訂閱它。 每次修改此數組時,所有偵聽者都會做出相應的反應。 - 2) 創建初始化方法并調用
get()
。 - 3) 使用
path
獲取對集合根目錄的引用,并添加一個偵聽器以接收集合中的更改。 - 4) 檢查是否發生錯誤,打印錯誤消息并返回。
- 5) 在
querySnapshot.documents
上使用compactMap(_ :)
遍歷所有元素。 如果querySnapshot
為nil
,則改為設置一個空數組。 - 6) 使用
data(as:decoder :)
將每個文檔映射為Card
。 您可以這樣做,這要歸功于您在頂部導入的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
數組。 - 2) 創建一組
AnyCancellables
。 它將用于存儲您的訂閱,以便您以后可以取消訂閱。
仍在視圖模型中時,添加以下初始化程序:
init() {
// 1
cardRepository.$cards.map { cards in
cards.map(CardViewModel.init)
}
// 2
.assign(to: \.cardViewModels, on: self)
// 3
.store(in: &cancellables)
}
您添加的代碼:
- 1) 監聽
cards
,并將數組的每個Card
元素映射到CardViewModel
中。 這將創建一個CardViewModels
數組。 - 2) 將前一個映射操作的結果分配給
cardViewModels
。 - 3) 將此預訂的實例存儲在
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))
}
}
進行了這些更改后,您現在可以直接傳遞預期的CardViewModel
而不是Card
模型。 但是,您需要再進行一次更新才能再次使用預覽。
4. Setting Up CardListView
您還需要更改包裝清單視圖,以便它與card view model
一起使用。
打開CardListView.swift
并將cards array
屬性替換為:
@ObservedObject var cardListViewModel = CardListViewModel()
有了這一更改,CardListView
現在期望使用CardListViewModel
而不是Cards
數組。 @ObservedObject
將訂閱該屬性,以便它可以偵聽視圖模型中的更改。
在主體中查找ForEach
語句,并將其更改為如下所示:
ForEach(cardListViewModel.cardViewModels) { cardViewModel in
CardView(cardViewModel: cardViewModel)
.padding([.leading, .trailing])
}
現在,您將遍歷cardListViewModel
的各個卡片視圖模型,并為每個模型創建一個CardView
。
由于CardListView
現在需要CardListViewModel
而不是Cards
數組,因此將CardListView_Previews
更改為:
CardListView(cardListViewModel: CardListViewModel())
構建并運行
根據需要添加任意數量的卡片,并查看它們如何立即顯示在主屏幕上。
Updating Cards
該應用程序可讓用戶在獲得正確答案時進行標記。 如果不是這種情況,則會彈出一條消息,告訴他們上次嘗試失敗。
打開Card.swift
并修改id
,如下所示:
@DocumentID var id: String?
注意:這樣做會更改您的數據模型,因為
id
不會包含在其中。 下次您運行該應用程序時,以前的模型將不會顯示。
在頂部添加此導入語句:
import FirebaseFirestoreSwift
使用此代碼,您可以確保當Firebase
的SDK
將文檔轉換為Card
時,Cloud Firestore
中使用的Document Id
會映射到id
。 要對單個文檔執行操作,您需要使用其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) 捕獲代碼生成的任何異常。 如果在更新文檔時出現問題,則該應用程序將終止并顯示致命錯誤。
- 3) 使用
path
和cardId
,它獲取對cards
集合中文檔的引用,然后通過將card
傳遞給setData(from:encoder:completion :)
來更新字段。
現在,您需要更新視圖模型。 打開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
,則此代碼顯示一條消息。
在繼續之前,將以下三種方法添加到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
復制到updatedCard
并將successful
設置為false
。 然后調用update(card :)
。 - 2) 將
cardViewModel.card
復制到UpdatedCard
并將successful
設置為true
。 然后調用update(card :)
。 - 3) 將更新的卡片傳遞給
update(card :)
,以便視圖模型可以更新模型。 然后在showContent
上調用toggle()
來觸發翻轉動畫。
接下來,將backView
替換為以下內容:
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))
}
在這里,您添加了兩個新按鈕,以便用戶可以指示他們是否正確回答了問題。
構建并運行。
點擊任意卡,然后點擊拇指向下thumb-down
圖標。 在底部,前視圖顯示一條消息,提示You answered this one incorrectly before
:
Removing Cards
用戶應能夠在需要時取出卡。
打開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
集合中文檔的引用,然后調用delete
。 這將從Cloud Firestore
中的集合中刪除文檔。
delete(completion :)
還提供了一個閉包,您可以在其中處理任何錯誤。 閉包中的代碼檢查是否有錯誤,并將其打印到控制臺。
打開CardViewModel.swift
并向其中添加此方法,以便您的視圖模型可以調用CardRepository
上的remove(_ :)
,并傳遞實際的Card
:
func remove() {
cardRepository.remove(card)
}
最后,打開CardView.swift
并在Alert
內部的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
上調用remove()
。 然后,視圖模型執行邏輯以從數據庫中刪除卡。
構建并運行。
將您的任何卡片拖到頂部。 出現alert
,要求您確認操作。 點擊Remove
,您的卡將消失。
Securing the Data
安全性對于任何應用程序都是必不可少的。 Firebase
提供了一組身份驗證方法,可用于讓用戶對您的應用程序進行身份驗證。 對于本項目,您將實現匿名身份驗證Anonymous Authentication
。
匿名身份驗證(Anonymous Authentication)
是一種身份驗證類型,可讓您為尚未注冊應用的用戶創建臨時帳戶,從而為他們提供了一層安全保護。 與安全規則(Security Rules)
結合使用,匿名身份驗證為此應用程序提供了足夠的安全性。
要激活此身份驗證模式,請轉到Firebase
控制臺,在左側邊欄中選擇Authentication
,然后在頂部導航欄上選擇Sign-in method
。 轉到Providers List
的底部,選擇Anonymous
,然后單擊右側的開關將其啟用。 最后,單擊Save
。
注意:如果您沒有看到頂部的導航欄,請單擊
Get Started
以跳過介紹性屏幕。
現在,您需要創建一個身份驗證服務。
1. Creating an authentication service
在項目導航器中,在Services
下創建一個新的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) 定義在身份驗證過程發生時將包含
User
對象的用戶。它還定義了一個authenticationStateHandler
屬性,以捕獲用戶對象中的更改,例如,當用戶登錄或注銷時。 - 3) 實現
init()
并調用addListeners()
,以便在實例化該類時調用它。 - 4) 添加
signIn()
,用于登錄Firebase
。Auth
將Firebase
用戶對象存儲在currentUser
中。
通過檢查它是否為nil
,可以避免不必要的調用。此值存儲在本地,因此在第一次使用后,該應用使用同一用戶。
- 5) 檢查是否已實例化處理程序,如果已實例化,則將其刪除。
- 6) 將
addStateDidChangeListener(_ :)
監聽器分配給authenticationStateHandler
。
好的,您已經設置了身份驗證服務(Authentication Service)
!
2. Using the authentication service
打開AppDelegate.swift
并在application(_:didFinishLaunchingWithOptions:)
的FirebaseApp.configure()
之后添加以下行:
AuthenticationService.signIn()
此代碼可確保用戶在應用啟動時登錄。
接下來,打開CardRepository.swift
并將這些屬性添加到類的頂部:
// 1
var userId = ""
// 2
private let authenticationService = AuthenticationService()
// 3
private var cancellables: Set<AnyCancellable> = []
這段代碼:
- 1) 聲明
userId
,您將使用該ID
存儲Firebase
生成的當前用戶ID。 - 2) 創建
AuthenticationService
的實例。 - 3) 創建一組
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 :)
設置代碼執行的線程,然后使用sink(receiveValue:)
附加訂閱者。 這樣可以保證,當您從AuthenticationService
獲取用戶user
時,閉包中的代碼將在主線程中執行。 - 3) 像在原始的初始化程序中一樣,調用
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
的值。 現在,每次創建新卡時,它都會包含Firebase
生成的實際用戶id
。
最后,在get()
的.addSnapshotListener(_ :)
之前添加以下行:
.whereField("userId", isEqualTo: userId)
此代碼使您可以按userId
過濾卡片。
3. Adding Authorization Using Security Rules
在網絡瀏覽器中打開Firebase
項目。 然后轉到Cloud Firestore
,然后單擊頂部水平導航欄上的Rules
。 您會看到類似以下內容:
此類似于JavaScript
的代碼是Firestore Security Rules
。 這些規則定義用戶是否有權訪問或修改文檔。 用以下代碼替換現有代碼:
// 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
設置為“ 2”
。 目前,這是最新版本,并確定如何解釋以下代碼。 - 2) 指示這些規則適用于哪些服務。 在這種情況下,請使用
Cloud Firestore
。 - 3) 指定規則應匹配項目中的任何
Cloud Firestore
數據庫。 - 4) 指定僅經過身份驗證的用戶可以讀取或寫入文檔。
這些規則確定了應用程序的授權部分。 通過身份驗證,您可以了解用戶是誰。 通過授權,您可以確定該用戶可以做什么。
單擊Publish
以保存更改。 然后回到項目并構建并運行。
該應用目前已按用戶userId
進行過濾,因此不會顯示任何卡片。如果您添加一個新的,它將出現。您甚至可以關閉并重新打開該應用程序,僅顯示從現在開始創建的卡片。
Understanding Firestore Pricing
了解Cloud Firestore
的定價可以節省您花費過多的金錢。請記住以下幾點:
-
Cloud Firestore
向您收取您執行的操作數:讀取,寫入和刪除。 - 價格從一個地點到另一個地點有所不同。
- 您還必須支付數據庫使用的存儲空間和網絡帶寬。
- 如果更改單個字段或完整的文檔,則視為一次操作。
- 讀取次數是返回的記錄數。因此,如果您的查詢返回了十個文檔,那么您將有十次讀取。如果可能,請使用
limit
限制查詢可以返回的文檔數。 - 您可以使用
Alerts
監控當前預算。您可以使用Google Cloud Console
對其進行配置,這也可以讓您檢查以前的發票并設置所需的每日支出。 -
Google Cloud Operation
可讓您監控效果并獲取指標,這些指標也可以幫助您制定預算。
如果可能,您還應該在本地緩存數據,以避免從Firestore
請求數據。
您可以在the Firestore documentation文檔中找到更多信息。
在本教程中,您學習了如何使用Cloud Firestore
持久存儲數據以及如何使用MVVM
將其與SwiftUI
視圖集成。 您還從頭開始學習了如何使用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
程序的持久性添加,感興趣的給個贊或者關注~~~