版本记录
版本号 | 时间 |
---|---|
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
程序的持久性添加,感兴趣的给个赞或者关注~~~