備注: 本教程已由 Attila Hegedüs 更新適配 iOS 10 和 Swift 3,原教程由David East 創(chuàng)作。
原文:https://www.raywenderlich.com/139322/firebase-tutorial-getting-started-2
翻譯:JoeyChang 轉(zhuǎn)載請標(biāo)明出處
Firebase 是一個移動后臺服務(wù),它可以幫助我們創(chuàng)建具有優(yōu)秀特性的移動 apps。Firebase 提供以下三個主要服務(wù): a realtime database, user authentication and hosting。通過集成 Firebase iOS SDK, 你幾乎不用寫一行代碼就能創(chuàng)建出非常棒的應(yīng)用。
Firebase 具有數(shù)據(jù)庫實時性這樣的獨特性能。
你曾經(jīng)使用過 pull-to-refresh 去拉新數(shù)據(jù)么?有了 Firebase,現(xiàn)在你可以忽略那種刷新數(shù)據(jù)方法了。
當(dāng) Firebase 數(shù)據(jù)庫更新時,所有的連接者可以實時獲取更新,這就意味著你的 app 可以不用用戶交互就能獲取數(shù)據(jù)庫當(dāng)前最新值。
本篇 Firebase 教程中,我們將通過創(chuàng)建一個名叫 Grocr 的具有協(xié)作性的grocery list app , 來學(xué)習(xí)Firebase 的一些基本原理。當(dāng)我們添加一個項目到列表時,它將實時出現(xiàn)在用戶的其它設(shè)備中,但是我們并不滿足于此,我們還將調(diào)整 Grocr 讓它可以離線工作,以致即使僅有一個 grocery 數(shù)據(jù)連接,列表也能保持同步。
通過本文,你將學(xué)習(xí)到以下技能:
- 保存數(shù)據(jù)到Firebase數(shù)據(jù)庫
- 從 Firebase 實時同步數(shù)據(jù)
- 驗證 users
- 在線監(jiān)控 users
- 實現(xiàn)離線支持
開始,下載初始項目 Grocr-starter. 它使用 CocoaPods 管理 Firebase 。
在 Xcode 中打開 Grocr.xcworkspace,該項目包含三個view controllers:
LoginViewController.swift.
現(xiàn)在登錄功能還是使用的硬編碼 user credentials,稍后我們將優(yōu)化它。GroceryListTableViewController.swift.
這個 controller 是 UITableViewController 子類,它通過 UIAlertController 添加 items 到本地數(shù)據(jù)庫的 list 表格。OnlineUsersTableViewController.swift.
該 controller 使用 Firebase’s presence feature 展示所有當(dāng)前在線 users。
此外,還有兩個模型類 GroceryItem.swift 和 User.swift 。它們做為 app 的數(shù)據(jù)模型。
Build and run, 你將看到如下這樣效果:
注: 當(dāng) build 工程時,我們將看到一些 ‘nullability’ 編譯警告。它們來自Firebase,暫時我們先忽略它們,稍后解決。
我們可以點擊 Login 進(jìn)行登錄,這將使用一個寫死的 user 數(shù)據(jù),現(xiàn)在該 app 還只能使用本地數(shù)據(jù)。接下來我們將調(diào)用 Firebase 數(shù)據(jù)使 app 生動起來。
創(chuàng)建 Firebase 賬號
有兩個重要步驟:
- 創(chuàng)建免費(fèi) Firebase 賬號
- 獲取你第一個 app 的 URL
我們可以訪問 Getting Started page 進(jìn)行注冊。當(dāng)我們使用我們谷歌賬號共享登錄進(jìn)入 firebase, 我們將看到一個干凈的 Firebase 控制臺。不要擔(dān)心費(fèi)用問題,現(xiàn)在 Firebase 免費(fèi)版本已經(jīng)足夠強(qiáng)大,夠用了。
創(chuàng)建我們的第一個工程,點擊 CREATE NEW PROJECT 。在彈出的對話框中輸入項目名稱以及你的首選 國家/地區(qū):
點擊 CREATE PROJECT, 我們就可以通過控制面板來管理我們的項目了。
這將作為所有 Firebase 服務(wù)的容器,我們用它存儲數(shù)據(jù)和授權(quán)用戶。
選擇 Add Firebase to your iOS app 開始我們的項目。本項目的 bundle ID 是 rw.firebase.gettingstarted,所以添加此 id 到 iOS bundle ID 文本框。
點擊 ADD APP ,將下載一個 GoogleService-Info.plist 文件。將該文件拖拽到 Xcode 中的 Grocr 項目。
點擊 CONTINUE. 接下來一頁描述怎樣安裝 Firebase SDK。
本項目已經(jīng)替我們集成好了,所以點擊 CONTINUE 繼續(xù)。最后一頁說明當(dāng) app 啟動時怎樣連接到 Firebase。
點擊 FINISH ,查看新項目細(xì)節(jié)。
在 Xcode 打開 GroceryListTableViewController.swift ,添加如下代碼,創(chuàng)建 Firebase 連接。
let ref = FIRDatabase.database().reference(withPath: "grocery-items")
這個 Firebase 連接使用已提供的 path。在 documentation 中,這些 Firebase 屬性被稱為 references ,它們指定 Firebase 的位置。
簡言之,這些屬性可以實現(xiàn)保存和同步數(shù)據(jù)到給定的位置。
我們發(fā)現(xiàn),base URL 不是必須的,相反,它使用 grocery-items 的 child path。Firebase 數(shù)據(jù)庫是 JSON NoSQL 數(shù)據(jù)庫,所以數(shù)據(jù)都是保存為 JSON 格式。
JSON 是分等級的 key-value 數(shù)據(jù)結(jié)構(gòu) -- keys 指的是可以根據(jù)它獲取其它對象格式的 values 值。JSON data 是一個簡單的 key value 對兒樹形結(jié)構(gòu)。
在 Firebase 中,key 是一個 URL,value是形如 number, string, boolean , object 的隨意的數(shù)據(jù)。
Structuring Data
無論客戶端是什么數(shù)據(jù)格式,保存到 Firebase 的是 JSON 格式。下面是一個 JSON 示例:
// The root of the tree
{ // grocery-items
"grocery-items": {
// grocery-items/milk
"milk": {
// grocery-items/milk/name
"name": "Milk",
// grocery-items/milk/addedByUser
"addedByUser": "David"
},
"pizza": {
"name": "Pizza",
"addedByUser": "Alice"
},
}
}
在上面的 JSON 中,你可以看到每對兒數(shù)據(jù)都是以鍵值對兒形式出現(xiàn)的。我們可以繼續(xù)遍歷樹并在更深的位置檢索數(shù)據(jù)。
在上面的例子中,我們可以通過路徑檢索所有的 grocery item。
grocery-items
如果你想獲取第一個 grocery item ,你可以通過以下路徑獲取:
grocery-items/milk
因為所有的 Firebase keys 對應(yīng)paths,所以 key 的名字選擇很重要。
Understanding Firebase References
一個基本的原則是,F(xiàn)irebase 引用指向 Firebase 中數(shù)據(jù)存儲的位置。如果我們創(chuàng)建多引用,那么這些引用共享同一個連接。
看如下代碼:
// 1
let rootRef = FIRDatabase.database().reference()
// 2
let childRef = FIRDatabase.database().reference(withPath: "grocery-items")
// 3
let itemsRef = rootRef.child("grocery-items")
// 4
let milkRef = itemsRef.child("milk")
// 5
print(rootRef.key) // prints: ""
print(childRef.key) // prints: "grocery-items"
print(itemsRef.key) // prints: "grocery-items"
print(milkRef.key) // prints: "milk"
下面我們解釋下:
- 我們創(chuàng)建一個到 Firebase 數(shù)據(jù)庫 root 引用。
- 使用一個 URL ,我們可以創(chuàng)建一個引用到 Firebase 數(shù)據(jù)庫的子路徑。
- 通過給 rootRef 傳遞子路徑,我們可以使用 child(_:) 創(chuàng)建子引用,這個引用和上面的引用是一樣意思。
- 使用 itemsRef ,我們可以創(chuàng)建到 milk 的子引用。
- 每個引用都有 key 屬性。這個屬性和 Firebase 數(shù)據(jù)庫關(guān)鍵字的名字一樣。
我們不需要在同一個項目中都添加這樣的代碼,這里只是出于展示目的進(jìn)行列舉。
Adding New Items to the List
在 GroceryListTableViewController.swift 的底部,找到 addButtonDidTouch(_:) 方法。
在這里我們要實現(xiàn)通過 UIAlertController 的方式添加一個新的 item 。
在 saveAction 方法內(nèi),現(xiàn)在僅僅保存數(shù)據(jù)到一個本地 array,因此 saveAction 不能同步不同客戶端的數(shù)據(jù),而且在下次啟動 app 時,保存的數(shù)據(jù)將丟失。
沒有人會使用不能記錄或者同步他們 grocery 清單數(shù)據(jù)的 app ! 讓我們完善 saveAction 方法:
let saveAction = UIAlertAction(title: "Save",
style: .default) { _ in
// 1
guard let textField = alert.textFields?.first,
let text = textField.text else { return }
// 2
let groceryItem = GroceryItem(name: text,
addedByUser: self.user.email,
completed: false)
// 3
let groceryItemRef = self.ref.child(text.lowercased())
// 4
groceryItemRef.setValue(groceryItem.toAnyObject())
}
注釋如下:
從 alert controller 獲取 text field 和它的內(nèi)容。
使用當(dāng)前用戶數(shù)據(jù)創(chuàng)建一個新的 GroceryItem 。
使用 child(_:) 創(chuàng)建一個子引用,這個引用的 key 是 item 的小寫名稱,因此如果我們添加一個復(fù)制的 item (即使使用大寫字母,或者使用混合字母),數(shù)據(jù)庫只保存最后一個。
使用 setValue(_:) 保存數(shù)據(jù)到數(shù)據(jù)庫。這個方法期望一個字典格式。GroceryItem 有個 toAnyObject() 方法,可以轉(zhuǎn)換對象為字典格式。
在你可以連接數(shù)據(jù)庫之前,我們還需要配置它。找到 AppDelegate.swift ,并在 application(_:didFinishLaunchingWithOptions:) 返回 true 之前添加如下代碼:
FIRApp.configure()
默認(rèn)情況,F(xiàn)irebase 數(shù)據(jù)庫需要用戶授權(quán)讀寫權(quán)限。在瀏覽器進(jìn)入 Firebase 控制面板,選中左邊的 Database 選項,設(shè)置 RULES 如下:
{
"rules": {
".read": true,
".write": true
}
}
修改后,選擇 PUBLISH 按鈕進(jìn)行保存設(shè)置。
Build and run. 在 Firebase 控制面板,選擇 DATA 標(biāo)簽,并將瀏覽器窗口緊挨模擬器。當(dāng)我們在模擬器中添加 item ,我們將看到它會出現(xiàn)在控制面板。
現(xiàn)在,我們就有了一個可以實時添加數(shù)據(jù)到 Firebase 的活生生的 grocery list app!但是雖然 key 特性已經(jīng)可以運(yùn)行完好了,但是沒有數(shù)據(jù)添加到table view。
那么我們怎樣才能將數(shù)據(jù)從數(shù)據(jù)庫同步到 table view 呢?
Retrieving Data
我們可以通過 observeEventType(_:withBlock:) 方法異步檢索 Firebase 中的數(shù)據(jù)。
在 GroceryListTableViewController.swift 的 viewDidLoad() 下添加如下方法:
ref.observe(.value, with: { snapshot in
print(snapshot.value)
})
該方法有兩個參數(shù):FIRDataEventType 的一個實例以及一個閉包。
event type 確定我們要監(jiān)聽的事件,.value 監(jiān)聽諸如 add, removed, changed 這樣的 Firebase 數(shù)據(jù)庫重點數(shù)據(jù)改變。
當(dāng)改變發(fā)生,數(shù)據(jù)庫使用最新數(shù)據(jù)更新 app 顯示。
app 在閉包方法中通過接受到的 FIRDataSnapshot 一個實例獲知數(shù)據(jù)改變。snapshot,代表某個特定時間點的數(shù)據(jù)快照。我們可以通過 value 那個屬性獲取到 snapshot 的數(shù)據(jù)。
Build and run,我們將看到,在控制臺會有 items 列表數(shù)據(jù)被打印出來。
Optional({
pizza = {
addedByUser = "hungry@person.food";
completed = 0;
name = Pizza;
};
})
Synchronizing Data to the Table View
注意打印日志--現(xiàn)在在 table view 中可以看到 grocery 列表了。
在 GroceryListTableViewController.swift, 替換之前的代碼片段為如下代碼:
// 1
ref.observe(.value, with: { snapshot in
// 2
var newItems: [GroceryItem] = []
// 3
for item in snapshot.children {
// 4
let groceryItem = GroceryItem(snapshot: item as! FIRDataSnapshot)
newItems.append(groceryItem)
}
// 5
self.items = newItems
self.tableView.reloadData()
})
以上代碼的諸行解釋:
添加一個監(jiān)聽器監(jiān)聽 grocery-items 改變了什么。
存儲最近一次版本數(shù)據(jù)到閉包中本地的一個變量中。
監(jiān)聽者閉包返回最近數(shù)據(jù)的一個 snapshot,這個 snapshot 包含所有的 grocery items,而不是僅僅包含改變的 items。使 snapshot.children ,我們可以循環(huán)獲取 grocery items 。
GroceryItem 結(jié)構(gòu)有一個常用的實例化器,它使用 FIRDataSnapshot
來填充它的屬性。snapshot 的值可以為任意類型,可以是 dictionary, array, number, or string。當(dāng)創(chuàng)建好一個 GroceryItem 實例,它被添加到一個包含最近一次版本數(shù)據(jù)的數(shù)組中。將最新版本的數(shù)據(jù)賦值給 items,然后更新 table view,使它展示最新數(shù)據(jù)。
Build and run. 添加一個 pizza item 怎么樣? 它將顯示到 table view。
不用刷新,就可以及時獲取到更新后的數(shù)據(jù)。
Removing Items From the Table View
table view 將同步我們所有的改變數(shù)據(jù), 但是當(dāng)我們想刪除 pizza 時,現(xiàn)在還不能更新。
為了通知數(shù)據(jù)庫刪除數(shù)據(jù),我們需要設(shè)置一個 Firebase reference,當(dāng)用戶輕掃時候刪除 item。
定位到 tableView(_:commit:forRowAt:)。現(xiàn)在,該方法使用 index 移除 array 中的 grocery item。這可以實現(xiàn)功能,但我們還有更好的解決方法。替換為如下實現(xiàn)方式:
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let groceryItem = items[indexPath.row]
groceryItem.ref?.removeValue()
}
}
Firebase 遵從單向數(shù)據(jù)流模型,因此 viewDidLoad() 的 listener 監(jiān)聽 grocery list 的最新數(shù)據(jù)。清除 item 觸發(fā)數(shù)據(jù)改變。
index path 的 row 被用來獲取相關(guān)的 grocery item。每個 GroceryItem 擁有一個名為 ref 的 Firebase reference property,調(diào)用 它的 removeValue() 將移除我們在 viewDidLoad() 定義的 listener。該listener有一個閉包,它使用最新的數(shù)據(jù)重新加載表視圖。
Build and run. 輕掃 item ,點擊刪除,我們發(fā)現(xiàn) app 和 Firebase 的數(shù)據(jù)都消失了。
Nice work! 我們 items 可以實時刪除了。
Checking Off Items
現(xiàn)在我們知道了怎么添加、刪除以及同步 items ,這很酷。但是當(dāng)我們實際購物時候會怎樣呢?我們會刪除我們剛購買的物品么,或者當(dāng)我們添加購物車時給物品打個標(biāo)記是否更好?
在以前的紙質(zhì)時代,人們過去常常把東西從購物清單上劃掉,因為我們也將在我們的 app 用現(xiàn)代的方式模仿這個行為。
打開 GroceryListTableViewController.swift ,找到 toggleCellCheckbox(_:isCompleted:) 方法,該方法可以根據(jù) item 是否完成來切換UITableViewCell 的必要視圖屬性。
當(dāng) table view 第一次加載后,剛方法在tableView (_:cellForRowAtIndexPath:) 中會被調(diào)用,以及當(dāng)用戶點擊 cell 時也會被調(diào)用。
替換 tableView(_:didSelectRowAt:) 方法為如下:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// 1
guard let cell = tableView.cellForRow(at: indexPath) else { return }
// 2
let groceryItem = items[indexPath.row]
// 3
let toggledCompletion = !groceryItem.completed
// 4
toggleCellCheckbox(cell, isCompleted: toggledCompletion)
// 5
groceryItem.ref?.updateChildValues([
"completed": toggledCompletion
])
}
以下為詳細(xì)注解:
- 使用 cellForRow(at:) 確定用戶點擊的 cell。
- 根據(jù) index path 的 row 獲取對應(yīng)的 GroceryItem。
- 改變 grocery item 的 completed 的狀態(tài)。
- 調(diào)用 toggleCellCheckbox(_:isCompleted:) 更新 cell 的屬性。
- 在 updateChildValues(:) 方法中,通過傳遞字典參數(shù),更新Firebase。該方法與 setValue(:) 不同,因為它只應(yīng)用更新,而setValue(_:) 具有破壞性,并在該引用中替換整個值。
Build and run. 點擊一個 item,我們就可以看到該行被勾號標(biāo)記并排序。
恭喜,我們已經(jīng)完成了一個相當(dāng)漂亮的 grocery list app 。
Sorting the Grocery List
如果把 ice cream 放在未排序的標(biāo)記里面,有時我們可能會忘記它。現(xiàn)在讓我們進(jìn)行些優(yōu)化。
如果可以把已選中的 items 自動移動到列表底部,我們的 app 將更加令人喜歡。這樣,未被標(biāo)記的 items 可以更容易被我們發(fā)現(xiàn)。
使用 Firebase queries, 我們可以根據(jù)任意屬性對列表進(jìn)行排序,在GroceryListTableViewController.swift, 更新 viewDidLoad() 方法:
ref.queryOrdered(byChild: "completed").observe(.value, with: { snapshot in
var newItems: [GroceryItem] = []
for item in snapshot.children {
let groceryItem = GroceryItem(snapshot: item as! FIRDataSnapshot)
newItems.append(groceryItem)
}
self.items = newItems
self.tableView.reloadData()
})
通過關(guān)鍵詞 “ completed”,使用 Firebase 引用 queryOrdered(byChild:) 對數(shù)據(jù)進(jìn)行排序。
由于列表需要完成順序,所以 completed 鍵將傳遞給查詢。然后,queryOrdered(byChild:)返回一個引用,通知服務(wù)器以有序的方式返回數(shù)據(jù)。
Build and run. 點擊一行,使其置換為已完成狀態(tài),我們將看到,它神奇地自動移動到了最后一行。
哇! 我們現(xiàn)在真的讓購物變得更容易了。跨多個用戶同步數(shù)據(jù),似乎應(yīng)該足夠簡單,例如,與一個重要的其他用戶或 housemate。這聽起來像…身份驗證!
Authenticating Users
Firebase 有一個 authentication service,它允許 apps 驗證不同的提供者,我們可以使用 Google, Twitter, Facebook, Github, email & password, 匿名, 甚至 custom backends 這些方式。這里我們使用郵箱和密碼方式進(jìn)行身份認(rèn)證,因為這種方式是最簡單的。
進(jìn)入 Firebase dashboard ,點擊 Auth,激活郵箱密碼認(rèn)證。
選中 SIGN-IN METHOD 標(biāo)簽欄,再在 Sign-in providers 那一節(jié)選中Email/Password 行,切換 Enable 并點擊 SAVE:
Firebase 存儲賬戶信息到 keychain,因此最后一步,在項目中,切換到 target’s Capabilities 打開 Keychain Sharing 開關(guān)。
現(xiàn)在,我們已經(jīng)可以使用郵箱和密碼進(jìn)行身份認(rèn)證了。
Registering Users
在 LoginViewController.swift,找到 signUpDidTouch(_:) 方法,這里會彈出 UIAlertController 讓用戶注冊賬號,定位到 saveAction 方法,添加以下代碼到方法塊兒。
// 1
let emailField = alert.textFields![0]
let passwordField = alert.textFields![1]
// 2
FIRAuth.auth()!.createUser(withEmail: emailField.text!,
password: passwordField.text!) { user, error in
if error == nil {
// 3
FIRAuth.auth()!.signIn(withEmail: self.textFieldLoginEmail.text!,
password: self.textFieldLoginPassword.text!)
}
}
以上代碼解釋:
- 從彈框中獲取郵箱和密碼。
- 調(diào)用 Firebase 方法 createUser(withEmail:password:),傳遞郵箱和密碼給它。
- 如果執(zhí)行沒有錯誤,用戶賬號即被創(chuàng)建。但是,我們還要再進(jìn)行一下登錄操作 signIn(withEmail:password:) ,同樣需要傳遞郵箱和密碼。
Build and run. 點擊 Sign up ,鍵入郵箱和密碼,點擊保存。現(xiàn)在 view controller 還不能在登錄成功后導(dǎo)航到其它地方。我們刷新 Firebase Login & Auth ,我們將看到新建的用戶。
喔!我們的 app 現(xiàn)在可以讓用戶注冊并進(jìn)行登錄了,不過我們先不要慶祝,我們還需要再做些優(yōu)化,好使用戶更好的使用它。
Logging Users In
Sign up 按鈕可以注冊和登錄,然而 Login 現(xiàn)在還什么都做不了,因為我們還沒有給它綁定驗證。
到 LoginViewController.swift, 找到 loginDidTouch(_:) 方法,修改如下:
@IBAction func loginDidTouch(_ sender: AnyObject) {
FIRAuth.auth()!.signIn(withEmail: textFieldLoginEmail.text!,
password: textFieldLoginPassword.text!)
}
當(dāng)用戶點擊 Login 時,這些代碼將驗證用戶信息。
我們接下來需要在用戶登錄成功后導(dǎo)航到下一個頁面。
Observing Authentication State
Firebase 有可以監(jiān)控用戶驗證狀態(tài)的觀察者。這里是添加 segue 最好的地方。在 LoginViewController: 添加如下代碼:
override func viewDidLoad() {
super.viewDidLoad()
// 1
FIRAuth.auth()!.addStateDidChangeListener() { auth, user in
// 2
if user != nil {
// 3
self.performSegue(withIdentifier: self.loginToList, sender: nil)
}
}
}
注釋如下:
使用 addStateDidChangeListener(_:) 創(chuàng)建驗證觀察者。該 block 被傳入兩個參數(shù):auth 和 user。
測試 user 的值,如果驗證通過,返回用戶信息,如果驗證失敗,返回 nil 。
驗證成功,進(jìn)行頁面跳轉(zhuǎn)。傳輸 sender 為 nil 。這看起來有些奇怪,但是稍后我們將在 GroceryListTableViewController.swift 進(jìn)行設(shè)置。
Setting the User in the Grocery List
在 GroceryListTableViewController.swift 文件 viewDidLoad(): 方法底部添加如下代碼:
FIRAuth.auth()!.addStateDidChangeListener { auth, user in
guard let user = user else { return }
self.user = User(authData: user)
}
這里我們添加了一個 Firebase auth object 的驗證觀察者,當(dāng)用戶成功登錄時,依次分配用戶屬性。
Build and run. 如果用戶已經(jīng)登錄,app 將跳過 LoginViewController 直接導(dǎo)航到 GroceryListTableViewController. 當(dāng)用戶添加 items ,他們的 email 將顯示到 cell 的詳情里面。
Success! app 現(xiàn)在已經(jīng)有了基本的用戶驗證功能。
Monitoring Users’ Online Status
現(xiàn)在既然我們的 app 已經(jīng)擁有了用戶驗證功能,那是時候添加監(jiān)控哪個用戶在線功能了。打開 GroceryListTableViewController.swift ,添加如下 property:
let usersRef = FIRDatabase.database().reference(withPath: "online")
這是一個指向存儲在線用戶列表的在線位置的Firebase引用。
下一步,在 viewDidLoad() 方法下添加如下代碼到 addStateDidChangeListener(_:) 閉包的下面。
// 1
let currentUserRef = self.usersRef.child(self.user.uid)
// 2
currentUserRef.setValue(self.user.email)
// 3
currentUserRef.onDisconnectRemoveValue()
注釋如下:
- 使用用戶的 uid 創(chuàng)建一個 child 引用,當(dāng) Firebase 創(chuàng)建一個賬號時,這個引用會被生成。
- 使用這個引用保存當(dāng)前用戶的 email.
- 當(dāng) Firebase 連接關(guān)閉的時候,例如用戶退出 app , 調(diào)用 currentUserRef 的 onDisconnectRemoveValue(),刪除位置引用的值。這可以完美監(jiān)控離線用戶。
Build and run. 當(dāng) view 加載時,當(dāng)前用戶的電子郵件,會被添加在當(dāng)前在線位置的一個子節(jié)點。
Great! 現(xiàn)在當(dāng)用戶數(shù)量增加時,是時候改變 bar button item 的個數(shù)了。
Updating the Online User Count
仍然在 GroceryListTableViewController.swift 的 viewDidLoad() 方法下添加如下代碼:
usersRef.observe(.value, with: { snapshot in
if snapshot.exists() {
self.userCountBarButtonItem?.title = snapshot.childrenCount.description
} else {
self.userCountBarButtonItem?.title = "0"
}
})
這創(chuàng)建一個觀察者監(jiān)控在線用戶,當(dāng)用戶在線或者離線,userCountBarButtonItem 的 title 隨之更新。
Displaying a List of Online Users
打開 OnlineUsersTableViewController.swift,在 class 的 property section 添加一個本地引用到 Firebase 的在線用戶記錄。
let usersRef = FIRDatabase.database().reference(withPath: "online")
然后,在viewDidLoad(), 替換代碼
currentUsers.append("hungry@person.food")
為如下:
// 1
usersRef.observe(.childAdded, with: { snap in
// 2
guard let email = snap.value as? String else { return }
self.currentUsers.append(email)
// 3
let row = self.currentUsers.count - 1
// 4
let indexPath = IndexPath(row: row, section: 0)
// 5
self.tableView.insertRows(at: [indexPath], with: .top)
})
代碼注釋如下:
創(chuàng)建一個 children added 監(jiān)聽器,添加到被 usersRef 管理的位置。這與值偵聽器不同,因為只有添加的 child 被傳遞到閉包。
從 snapshot 獲取值,并賦值給本地變量 array。
因為 table view 的坐標(biāo)從 0 開始計算,當(dāng)前的 row 總是等于 array 的個數(shù) -1。
使用當(dāng)前 row index 創(chuàng)建一個 NSIndexPath.
使用動畫從頂部添加一行到 table view.
這將只渲染添加的條目,而不是重新加載整個列表,而且還可以指定一個漂亮的動畫。:]
由于用戶可以脫機(jī),table 需要對被刪除的用戶做出反應(yīng)。在我們剛剛添加的代碼下面添加以下內(nèi)容:
usersRef.observe(.childRemoved, with: { snap in
guard let emailToFind = snap.value as? String else { return }
for (index, email) in self.currentUsers.enumerated() {
if email == emailToFind {
let indexPath = IndexPath(row: index, section: 0)
self.currentUsers.remove(at: index)
self.tableView.deleteRows(at: [indexPath], with: .fade)
}
}
})
這只是添加了一個觀察者,它偵聽被刪除的 usersRef 引用的子元素。它在本地數(shù)組中搜索電子郵件的值,以找到相應(yīng)的子條目,一旦找到,它就從表中刪除相關(guān)的行。
Build and run.
在 Firebase 用戶儀表板上點擊 Online ,當(dāng)前用戶的電子郵件將出現(xiàn)在表格中。使用一些技巧,可以在網(wǎng)上添加一個用戶,一旦你做了,它就會顯示在列表中。在儀表板上單擊刪除按鈕,用戶就會從 table 中消失….
Booyah! 當(dāng)用戶被添加和刪除的時候,table 隨之更新了。
Enabling Offline
雜貨店因不穩(wěn)定的數(shù)據(jù)連接而臭名昭著。你會認(rèn)為他們現(xiàn)在都有了Wi-Fi,但是沒有!
不過沒關(guān)系,我們只需設(shè)置數(shù)據(jù)庫離線工作。打開 * AppDelegate*,在(_:didFinishLaunchingWithOptions:) 底部方法返回 true 之前,添加如下代碼:
FIRDatabase.database().persistenceEnabled = true
是的,就是這樣! 就像我們的應(yīng)用能夠離線運(yùn)行一樣。當(dāng) app 重啟,一旦建立網(wǎng)絡(luò)連接,離線更新也將作用于我們的 Firebase 數(shù)據(jù)庫。Oooh-ahhhh !
Where To Go From Here?
我們可以在這里下載 Grocr-final完整項目。
注意:下載完后,我們?nèi)孕枰砑幼约旱?GoogleService-Info.plist 和 設(shè)置允許Keychain sharing 。
在這個Firebase教程中,我們通過構(gòu)建一個協(xié)作的購物清單 app 了解了Firebase的基礎(chǔ)知識,我們已經(jīng)實現(xiàn)了將數(shù)據(jù)保存到一個 Firebase 數(shù)據(jù)庫、實時同步數(shù)據(jù)、認(rèn)證用戶、監(jiān)視在線用戶狀態(tài)以及實現(xiàn)了離線支持。所有這些都是在沒有寫一行服務(wù)器代碼的情況下完成的! :]
如果你對 Firebase 感興趣,請查看文檔 documentation,以及 Firebase 提供的示例。
如果您對這個Firebase教程、Firebase或示例應(yīng)用有任何意見或問題,請加入下面的論壇討論!
上海 虹橋V1
2017.09.06 19:02