備注: 本教程已由 Attila Hegedüs 更新適配 iOS 10 和 Swift 3澈蚌,原教程由David East 創(chuàng)作墅冷。
原文:https://www.raywenderlich.com/139322/firebase-tutorial-getting-started-2
翻譯:JoeyChang 轉(zhuǎn)載請(qǐng)標(biāo)明出處
Firebase 是一個(gè)移動(dòng)后臺(tái)服務(wù),它可以幫助我們創(chuàng)建具有優(yōu)秀特性的移動(dòng) apps掩幢。Firebase 提供以下三個(gè)主要服務(wù): a realtime database, user authentication and hosting逊拍。通過集成 Firebase iOS SDK, 你幾乎不用寫一行代碼就能創(chuàng)建出非常棒的應(yīng)用。
Firebase 具有數(shù)據(jù)庫(kù)實(shí)時(shí)性這樣的獨(dú)特性能际邻。
你曾經(jīng)使用過 pull-to-refresh 去拉新數(shù)據(jù)么芯丧?有了 Firebase,現(xiàn)在你可以忽略那種刷新數(shù)據(jù)方法了世曾。
當(dāng) Firebase 數(shù)據(jù)庫(kù)更新時(shí)缨恒,所有的連接者可以實(shí)時(shí)獲取更新,這就意味著你的 app 可以不用用戶交互就能獲取數(shù)據(jù)庫(kù)當(dāng)前最新值轮听。
本篇 Firebase 教程中骗露,我們將通過創(chuàng)建一個(gè)名叫 Grocr 的具有協(xié)作性的grocery list app , 來(lái)學(xué)習(xí)Firebase 的一些基本原理。當(dāng)我們添加一個(gè)項(xiàng)目到列表時(shí)血巍,它將實(shí)時(shí)出現(xiàn)在用戶的其它設(shè)備中萧锉,但是我們并不滿足于此,我們還將調(diào)整 Grocr 讓它可以離線工作述寡,以致即使僅有一個(gè) grocery 數(shù)據(jù)連接柿隙,列表也能保持同步叶洞。
通過本文,你將學(xué)習(xí)到以下技能:
- 保存數(shù)據(jù)到Firebase數(shù)據(jù)庫(kù)
- 從 Firebase 實(shí)時(shí)同步數(shù)據(jù)
- 驗(yàn)證 users
- 在線監(jiān)控 users
- 實(shí)現(xiàn)離線支持
開始禀崖,下載初始項(xiàng)目 Grocr-starter. 它使用 CocoaPods 管理 Firebase 衩辟。
在 Xcode 中打開 Grocr.xcworkspace,該項(xiàng)目包含三個(gè)view controllers:
LoginViewController.swift.
現(xiàn)在登錄功能還是使用的硬編碼 user credentials帆焕,稍后我們將優(yōu)化它惭婿。GroceryListTableViewController.swift.
這個(gè) controller 是 UITableViewController 子類,它通過 UIAlertController 添加 items 到本地?cái)?shù)據(jù)庫(kù)的 list 表格叶雹。OnlineUsersTableViewController.swift.
該 controller 使用 Firebase’s presence feature 展示所有當(dāng)前在線 users财饥。
此外,還有兩個(gè)模型類 GroceryItem.swift 和 User.swift 折晦。它們做為 app 的數(shù)據(jù)模型钥星。
Build and run, 你將看到如下這樣效果:
注: 當(dāng) build 工程時(shí),我們將看到一些 ‘nullability’ 編譯警告满着。它們來(lái)自Firebase谦炒,暫時(shí)我們先忽略它們,稍后解決风喇。
我們可以點(diǎn)擊 Login 進(jìn)行登錄宁改,這將使用一個(gè)寫死的 user 數(shù)據(jù),現(xiàn)在該 app 還只能使用本地?cái)?shù)據(jù)魂莫。接下來(lái)我們將調(diào)用 Firebase 數(shù)據(jù)使 app 生動(dòng)起來(lái)还蹲。
創(chuàng)建 Firebase 賬號(hào)
有兩個(gè)重要步驟:
- 創(chuàng)建免費(fèi) Firebase 賬號(hào)
- 獲取你第一個(gè) app 的 URL
我們可以訪問 Getting Started page 進(jìn)行注冊(cè)。當(dāng)我們使用我們谷歌賬號(hào)共享登錄進(jìn)入 firebase, 我們將看到一個(gè)干凈的 Firebase 控制臺(tái)耙考。不要擔(dān)心費(fèi)用問題谜喊,現(xiàn)在 Firebase 免費(fèi)版本已經(jīng)足夠強(qiáng)大,夠用了倦始。
創(chuàng)建我們的第一個(gè)工程斗遏,點(diǎn)擊 CREATE NEW PROJECT 。在彈出的對(duì)話框中輸入項(xiàng)目名稱以及你的首選 國(guó)家/地區(qū):
點(diǎn)擊 CREATE PROJECT, 我們就可以通過控制面板來(lái)管理我們的項(xiàng)目了鞋邑。
這將作為所有 Firebase 服務(wù)的容器诵次,我們用它存儲(chǔ)數(shù)據(jù)和授權(quán)用戶。
選擇 Add Firebase to your iOS app 開始我們的項(xiàng)目枚碗。本項(xiàng)目的 bundle ID 是 rw.firebase.gettingstarted藻懒,所以添加此 id 到 iOS bundle ID 文本框。
點(diǎn)擊 ADD APP 视译,將下載一個(gè) GoogleService-Info.plist 文件。將該文件拖拽到 Xcode 中的 Grocr 項(xiàng)目归敬。
點(diǎn)擊 CONTINUE. 接下來(lái)一頁(yè)描述怎樣安裝 Firebase SDK酷含。
本項(xiàng)目已經(jīng)替我們集成好了鄙早,所以點(diǎn)擊 CONTINUE 繼續(xù)。最后一頁(yè)說明當(dāng) app 啟動(dòng)時(shí)怎樣連接到 Firebase椅亚。
點(diǎn)擊 FINISH 限番,查看新項(xiàng)目細(xì)節(jié)。
在 Xcode 打開 GroceryListTableViewController.swift 呀舔,添加如下代碼弥虐,創(chuàng)建 Firebase 連接。
let ref = FIRDatabase.database().reference(withPath: "grocery-items")
這個(gè) Firebase 連接使用已提供的 path媚赖。在 documentation 中霜瘪,這些 Firebase 屬性被稱為 references ,它們指定 Firebase 的位置惧磺。
簡(jiǎn)言之颖对,這些屬性可以實(shí)現(xiàn)保存和同步數(shù)據(jù)到給定的位置。
我們發(fā)現(xiàn)磨隘,base URL 不是必須的缤底,相反,它使用 grocery-items 的 child path番捂。Firebase 數(shù)據(jù)庫(kù)是 JSON NoSQL 數(shù)據(jù)庫(kù)个唧,所以數(shù)據(jù)都是保存為 JSON 格式。
JSON 是分等級(jí)的 key-value 數(shù)據(jù)結(jié)構(gòu) -- keys 指的是可以根據(jù)它獲取其它對(duì)象格式的 values 值设预。JSON data 是一個(gè)簡(jiǎn)單的 key value 對(duì)兒樹形結(jié)構(gòu)徙歼。
在 Firebase 中,key 是一個(gè) URL絮缅,value是形如 number, string, boolean , object 的隨意的數(shù)據(jù)鲁沥。
Structuring Data
無(wú)論客戶端是什么數(shù)據(jù)格式,保存到 Firebase 的是 JSON 格式耕魄。下面是一個(gè) 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 中画恰,你可以看到每對(duì)兒數(shù)據(jù)都是以鍵值對(duì)兒形式出現(xiàn)的。我們可以繼續(xù)遍歷樹并在更深的位置檢索數(shù)據(jù)吸奴。
在上面的例子中允扇,我們可以通過路徑檢索所有的 grocery item。
grocery-items
如果你想獲取第一個(gè) grocery item 则奥,你可以通過以下路徑獲取:
grocery-items/milk
因?yàn)樗械?Firebase keys 對(duì)應(yīng)paths考润,所以 key 的名字選擇很重要。
Understanding Firebase References
一個(gè)基本的原則是读处,F(xiàn)irebase 引用指向 Firebase 中數(shù)據(jù)存儲(chǔ)的位置糊治。如果我們創(chuàng)建多引用,那么這些引用共享同一個(gè)連接罚舱。
看如下代碼:
// 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)建一個(gè)到 Firebase 數(shù)據(jù)庫(kù) root 引用井辜。
- 使用一個(gè) URL 绎谦,我們可以創(chuàng)建一個(gè)引用到 Firebase 數(shù)據(jù)庫(kù)的子路徑。
- 通過給 rootRef 傳遞子路徑粥脚,我們可以使用 child(_:) 創(chuàng)建子引用窃肠,這個(gè)引用和上面的引用是一樣意思。
- 使用 itemsRef 刷允,我們可以創(chuàng)建到 milk 的子引用冤留。
- 每個(gè)引用都有 key 屬性。這個(gè)屬性和 Firebase 數(shù)據(jù)庫(kù)關(guān)鍵字的名字一樣树灶。
我們不需要在同一個(gè)項(xiàng)目中都添加這樣的代碼纤怒,這里只是出于展示目的進(jìn)行列舉。
Adding New Items to the List
在 GroceryListTableViewController.swift 的底部破托,找到 addButtonDidTouch(_:) 方法肪跋。
在這里我們要實(shí)現(xiàn)通過 UIAlertController 的方式添加一個(gè)新的 item 。
在 saveAction 方法內(nèi)土砂,現(xiàn)在僅僅保存數(shù)據(jù)到一個(gè)本地 array州既,因此 saveAction 不能同步不同客戶端的數(shù)據(jù),而且在下次啟動(dòng) app 時(shí)萝映,保存的數(shù)據(jù)將丟失吴叶。
沒有人會(huì)使用不能記錄或者同步他們 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)建一個(gè)新的 GroceryItem 序臂。
使用 child(_:) 創(chuàng)建一個(gè)子引用蚌卤,這個(gè)引用的 key 是 item 的小寫名稱,因此如果我們添加一個(gè)復(fù)制的 item (即使使用大寫字母奥秆,或者使用混合字母)逊彭,數(shù)據(jù)庫(kù)只保存最后一個(gè)。
使用 setValue(_:) 保存數(shù)據(jù)到數(shù)據(jù)庫(kù)构订。這個(gè)方法期望一個(gè)字典格式侮叮。GroceryItem 有個(gè) toAnyObject() 方法,可以轉(zhuǎn)換對(duì)象為字典格式悼瘾。
在你可以連接數(shù)據(jù)庫(kù)之前囊榜,我們還需要配置它。找到 AppDelegate.swift 亥宿,并在 application(_:didFinishLaunchingWithOptions:) 返回 true 之前添加如下代碼:
FIRApp.configure()
默認(rèn)情況卸勺,F(xiàn)irebase 數(shù)據(jù)庫(kù)需要用戶授權(quán)讀寫權(quán)限。在瀏覽器進(jìn)入 Firebase 控制面板烫扼,選中左邊的 Database 選項(xiàng)曙求,設(shè)置 RULES 如下:
{
"rules": {
".read": true,
".write": true
}
}
修改后,選擇 PUBLISH 按鈕進(jìn)行保存設(shè)置。
Build and run. 在 Firebase 控制面板圆到,選擇 DATA 標(biāo)簽怎抛,并將瀏覽器窗口緊挨模擬器。當(dāng)我們?cè)谀M器中添加 item 芽淡,我們將看到它會(huì)出現(xiàn)在控制面板。
現(xiàn)在豆赏,我們就有了一個(gè)可以實(shí)時(shí)添加數(shù)據(jù)到 Firebase 的活生生的 grocery list app挣菲!但是雖然 key 特性已經(jīng)可以運(yùn)行完好了,但是沒有數(shù)據(jù)添加到table view掷邦。
那么我們?cè)鯓硬拍軐?shù)據(jù)從數(shù)據(jù)庫(kù)同步到 table view 呢白胀?
Retrieving Data
我們可以通過 observeEventType(_:withBlock:) 方法異步檢索 Firebase 中的數(shù)據(jù)。
在 GroceryListTableViewController.swift 的 viewDidLoad() 下添加如下方法:
ref.observe(.value, with: { snapshot in
print(snapshot.value)
})
該方法有兩個(gè)參數(shù):FIRDataEventType 的一個(gè)實(shí)例以及一個(gè)閉包抚岗。
event type 確定我們要監(jiān)聽的事件或杠,.value 監(jiān)聽諸如 add, removed, changed 這樣的 Firebase 數(shù)據(jù)庫(kù)重點(diǎn)數(shù)據(jù)改變。
當(dāng)改變發(fā)生宣蔚,數(shù)據(jù)庫(kù)使用最新數(shù)據(jù)更新 app 顯示向抢。
app 在閉包方法中通過接受到的 FIRDataSnapshot 一個(gè)實(shí)例獲知數(shù)據(jù)改變。snapshot胚委,代表某個(gè)特定時(shí)間點(diǎn)的數(shù)據(jù)快照挟鸠。我們可以通過 value 那個(gè)屬性獲取到 snapshot 的數(shù)據(jù)。
Build and run亩冬,我們將看到艘希,在控制臺(tái)會(huì)有 items 列表數(shù)據(jù)被打印出來(lái)。
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()
})
以上代碼的諸行解釋:
添加一個(gè)監(jiān)聽器監(jiān)聽 grocery-items 改變了什么覆享。
存儲(chǔ)最近一次版本數(shù)據(jù)到閉包中本地的一個(gè)變量中。
監(jiān)聽者閉包返回最近數(shù)據(jù)的一個(gè) snapshot营袜,這個(gè) snapshot 包含所有的 grocery items撒顿,而不是僅僅包含改變的 items。使 snapshot.children 连茧,我們可以循環(huán)獲取 grocery items 核蘸。
GroceryItem 結(jié)構(gòu)有一個(gè)常用的實(shí)例化器,它使用 FIRDataSnapshot
來(lái)填充它的屬性啸驯。snapshot 的值可以為任意類型客扎,可以是 dictionary, array, number, or string。當(dāng)創(chuàng)建好一個(gè) GroceryItem 實(shí)例罚斗,它被添加到一個(gè)包含最近一次版本數(shù)據(jù)的數(shù)組中徙鱼。將最新版本的數(shù)據(jù)賦值給 items,然后更新 table view,使它展示最新數(shù)據(jù)袱吆。
Build and run. 添加一個(gè) pizza item 怎么樣厌衙? 它將顯示到 table view。
不用刷新绞绒,就可以及時(shí)獲取到更新后的數(shù)據(jù)婶希。
Removing Items From the Table View
table view 將同步我們所有的改變數(shù)據(jù), 但是當(dāng)我們想刪除 pizza 時(shí)蓬衡,現(xiàn)在還不能更新喻杈。
為了通知數(shù)據(jù)庫(kù)刪除數(shù)據(jù),我們需要設(shè)置一個(gè) Firebase reference狰晚,當(dāng)用戶輕掃時(shí)候刪除 item筒饰。
定位到 tableView(_:commit:forRowAt:)。現(xiàn)在壁晒,該方法使用 index 移除 array 中的 grocery item瓷们。這可以實(shí)現(xiàn)功能,但我們還有更好的解決方法秒咐。替換為如下實(shí)現(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 被用來(lái)獲取相關(guān)的 grocery item固蚤。每個(gè) GroceryItem 擁有一個(gè)名為 ref 的 Firebase reference property,調(diào)用 它的 removeValue() 將移除我們?cè)?viewDidLoad() 定義的 listener歹茶。該listener有一個(gè)閉包夕玩,它使用最新的數(shù)據(jù)重新加載表視圖。
Build and run. 輕掃 item 惊豺,點(diǎn)擊刪除燎孟,我們發(fā)現(xiàn) app 和 Firebase 的數(shù)據(jù)都消失了。
Nice work! 我們 items 可以實(shí)時(shí)刪除了尸昧。
Checking Off Items
現(xiàn)在我們知道了怎么添加揩页、刪除以及同步 items ,這很酷烹俗。但是當(dāng)我們實(shí)際購(gòu)物時(shí)候會(huì)怎樣呢爆侣?我們會(huì)刪除我們剛購(gòu)買的物品么,或者當(dāng)我們添加購(gòu)物車時(shí)給物品打個(gè)標(biāo)記是否更好幢妄?
在以前的紙質(zhì)時(shí)代兔仰,人們過去常常把東西從購(gòu)物清單上劃掉,因?yàn)槲覀円矊⒃谖覀兊?app 用現(xiàn)代的方式模仿這個(gè)行為蕉鸳。
打開 GroceryListTableViewController.swift 乎赴,找到 toggleCellCheckbox(_:isCompleted:) 方法忍法,該方法可以根據(jù) item 是否完成來(lái)切換UITableViewCell 的必要視圖屬性。
當(dāng) table view 第一次加載后榕吼,剛方法在tableView (_:cellForRowAtIndexPath:) 中會(huì)被調(diào)用饿序,以及當(dāng)用戶點(diǎn)擊 cell 時(shí)也會(huì)被調(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:) 確定用戶點(diǎn)擊的 cell羹蚣。
- 根據(jù) index path 的 row 獲取對(duì)應(yīng)的 GroceryItem原探。
- 改變 grocery item 的 completed 的狀態(tài)。
- 調(diào)用 toggleCellCheckbox(_:isCompleted:) 更新 cell 的屬性度宦。
- 在 updateChildValues(:) 方法中踢匣,通過傳遞字典參數(shù),更新Firebase戈抄。該方法與 setValue(:) 不同,因?yàn)樗粦?yīng)用更新后专,而setValue(_:) 具有破壞性划鸽,并在該引用中替換整個(gè)值。
Build and run. 點(diǎn)擊一個(gè) item戚哎,我們就可以看到該行被勾號(hào)標(biāo)記并排序裸诽。
恭喜,我們已經(jīng)完成了一個(gè)相當(dāng)漂亮的 grocery list app 型凳。
Sorting the Grocery List
如果把 ice cream 放在未排序的標(biāo)記里面丈冬,有時(shí)我們可能會(huì)忘記它。現(xiàn)在讓我們進(jìn)行些優(yōu)化甘畅。
如果可以把已選中的 items 自動(dòng)移動(dòng)到列表底部埂蕊,我們的 app 將更加令人喜歡。這樣疏唾,未被標(biāo)記的 items 可以更容易被我們發(fā)現(xiàn)蓄氧。
使用 Firebase queries, 我們可以根據(jù)任意屬性對(duì)列表進(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:) 對(duì)數(shù)據(jù)進(jìn)行排序喉童。
由于列表需要完成順序,所以 completed 鍵將傳遞給查詢顿天。然后堂氯,queryOrdered(byChild:)返回一個(gè)引用,通知服務(wù)器以有序的方式返回?cái)?shù)據(jù)牌废。
Build and run. 點(diǎn)擊一行咽白,使其置換為已完成狀態(tài),我們將看到畔规,它神奇地自動(dòng)移動(dòng)到了最后一行局扶。
哇! 我們現(xiàn)在真的讓購(gòu)物變得更容易了。跨多個(gè)用戶同步數(shù)據(jù)三妈,似乎應(yīng)該足夠簡(jiǎn)單畜埋,例如,與一個(gè)重要的其他用戶或 housemate畴蒲。這聽起來(lái)像…身份驗(yàn)證!
Authenticating Users
Firebase 有一個(gè) authentication service悠鞍,它允許 apps 驗(yàn)證不同的提供者蟆湖,我們可以使用 Google, Twitter, Facebook, Github, email & password, 匿名, 甚至 custom backends 這些方式泪掀。這里我們使用郵箱和密碼方式進(jìn)行身份認(rèn)證遮婶,因?yàn)檫@種方式是最簡(jiǎn)單的斗忌。
進(jìn)入 Firebase dashboard 帘睦,點(diǎn)擊 Auth试浙,激活郵箱密碼認(rèn)證族吻。
選中 SIGN-IN METHOD 標(biāo)簽欄暴心,再在 Sign-in providers 那一節(jié)選中Email/Password 行辽旋,切換 Enable 并點(diǎn)擊 SAVE:
Firebase 存儲(chǔ)賬戶信息到 keychain浩嫌,因此最后一步,在項(xiàng)目中补胚,切換到 target’s Capabilities 打開 Keychain Sharing 開關(guān)码耐。
現(xiàn)在,我們已經(jīng)可以使用郵箱和密碼進(jìn)行身份認(rèn)證了溶其。
Registering Users
在 LoginViewController.swift骚腥,找到 signUpDidTouch(_:) 方法,這里會(huì)彈出 UIAlertController 讓用戶注冊(cè)賬號(hào)瓶逃,定位到 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í)行沒有錯(cuò)誤代芜,用戶賬號(hào)即被創(chuàng)建埠褪。但是,我們還要再進(jìn)行一下登錄操作 signIn(withEmail:password:) 挤庇,同樣需要傳遞郵箱和密碼钞速。
Build and run. 點(diǎn)擊 Sign up ,鍵入郵箱和密碼嫡秕,點(diǎn)擊保存】视铮現(xiàn)在 view controller 還不能在登錄成功后導(dǎo)航到其它地方。我們刷新 Firebase Login & Auth 昆咽,我們將看到新建的用戶驾凶。
喔牙甫!我們的 app 現(xiàn)在可以讓用戶注冊(cè)并進(jìn)行登錄了,不過我們先不要慶祝调违,我們還需要再做些優(yōu)化窟哺,好使用戶更好的使用它。
Logging Users In
Sign up 按鈕可以注冊(cè)和登錄技肩,然而 Login 現(xiàn)在還什么都做不了且轨,因?yàn)槲覀冞€沒有給它綁定驗(yàn)證。
到 LoginViewController.swift, 找到 loginDidTouch(_:) 方法虚婿,修改如下:
@IBAction func loginDidTouch(_ sender: AnyObject) {
FIRAuth.auth()!.signIn(withEmail: textFieldLoginEmail.text!,
password: textFieldLoginPassword.text!)
}
當(dāng)用戶點(diǎn)擊 Login 時(shí)旋奢,這些代碼將驗(yàn)證用戶信息。
我們接下來(lái)需要在用戶登錄成功后導(dǎo)航到下一個(gè)頁(yè)面然痊。
Observing Authentication State
Firebase 有可以監(jiān)控用戶驗(yà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)建驗(yàn)證觀察者剧浸。該 block 被傳入兩個(gè)參數(shù):auth 和 user爽丹。
測(cè)試 user 的值,如果驗(yàn)證通過辛蚊,返回用戶信息,如果驗(yàn)證失敗真仲,返回 nil 袋马。
驗(yàn)證成功,進(jìn)行頁(yè)面跳轉(zhuǎn)秸应。傳輸 sender 為 nil 虑凛。這看起來(lái)有些奇怪,但是稍后我們將在 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)
}
這里我們添加了一個(gè) Firebase auth object 的驗(yàn)證觀察者桑谍,當(dāng)用戶成功登錄時(shí),依次分配用戶屬性祸挪。
Build and run. 如果用戶已經(jīng)登錄锣披,app 將跳過 LoginViewController 直接導(dǎo)航到 GroceryListTableViewController. 當(dāng)用戶添加 items ,他們的 email 將顯示到 cell 的詳情里面贿条。
Success! app 現(xiàn)在已經(jīng)有了基本的用戶驗(yàn)證功能雹仿。
Monitoring Users’ Online Status
現(xiàn)在既然我們的 app 已經(jīng)擁有了用戶驗(yàn)證功能,那是時(shí)候添加監(jiān)控哪個(gè)用戶在線功能了整以。打開 GroceryListTableViewController.swift 胧辽,添加如下 property:
let usersRef = FIRDatabase.database().reference(withPath: "online")
這是一個(gè)指向存儲(chǔ)在線用戶列表的在線位置的Firebase引用。
下一步公黑,在 viewDidLoad() 方法下添加如下代碼到 addStateDidChangeListener(_:) 閉包的下面邑商。
// 1
let currentUserRef = self.usersRef.child(self.user.uid)
// 2
currentUserRef.setValue(self.user.email)
// 3
currentUserRef.onDisconnectRemoveValue()
注釋如下:
- 使用用戶的 uid 創(chuàng)建一個(gè) child 引用,當(dāng) Firebase 創(chuàng)建一個(gè)賬號(hào)時(shí)摄咆,這個(gè)引用會(huì)被生成。
- 使用這個(gè)引用保存當(dāng)前用戶的 email.
- 當(dāng) Firebase 連接關(guān)閉的時(shí)候人断,例如用戶退出 app , 調(diào)用 currentUserRef 的 onDisconnectRemoveValue()吭从,刪除位置引用的值。這可以完美監(jiān)控離線用戶含鳞。
Build and run. 當(dāng) view 加載時(shí)影锈,當(dāng)前用戶的電子郵件,會(huì)被添加在當(dāng)前在線位置的一個(gè)子節(jié)點(diǎn)蝉绷。
Great! 現(xiàn)在當(dāng)用戶數(shù)量增加時(shí)鸭廷,是時(shí)候改變 bar button item 的個(gè)數(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)建一個(gè)觀察者監(jiān)控在線用戶熔吗,當(dāng)用戶在線或者離線辆床,userCountBarButtonItem 的 title 隨之更新。
Displaying a List of Online Users
打開 OnlineUsersTableViewController.swift桅狠,在 class 的 property section 添加一個(gè)本地引用到 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)建一個(gè) children added 監(jiān)聽器中跌,添加到被 usersRef 管理的位置咨堤。這與值偵聽器不同,因?yàn)橹挥刑砑拥?child 被傳遞到閉包漩符。
從 snapshot 獲取值一喘,并賦值給本地變量 array。
因?yàn)?table view 的坐標(biāo)從 0 開始計(jì)算嗜暴,當(dāng)前的 row 總是等于 array 的個(gè)數(shù) -1凸克。
使用當(dāng)前 row index 創(chuàng)建一個(gè) NSIndexPath.
使用動(dòng)畫從頂部添加一行到 table view.
這將只渲染添加的條目,而不是重新加載整個(gè)列表闷沥,而且還可以指定一個(gè)漂亮的動(dòng)畫萎战。:]
由于用戶可以脫機(jī),table 需要對(duì)被刪除的用戶做出反應(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)
}
}
})
這只是添加了一個(gè)觀察者蚂维,它偵聽被刪除的 usersRef 引用的子元素。它在本地?cái)?shù)組中搜索電子郵件的值颖侄,以找到相應(yīng)的子條目鸟雏,一旦找到,它就從表中刪除相關(guān)的行览祖。
Build and run.
在 Firebase 用戶儀表板上點(diǎn)擊 Online 孝鹊,當(dāng)前用戶的電子郵件將出現(xiàn)在表格中。使用一些技巧展蒂,可以在網(wǎng)上添加一個(gè)用戶又活,一旦你做了苔咪,它就會(huì)顯示在列表中。在儀表板上單擊刪除按鈕柳骄,用戶就會(huì)從 table 中消失….
Booyah! 當(dāng)用戶被添加和刪除的時(shí)候团赏,table 隨之更新了。
Enabling Offline
雜貨店因不穩(wěn)定的數(shù)據(jù)連接而臭名昭著耐薯。你會(huì)認(rèn)為他們現(xiàn)在都有了Wi-Fi舔清,但是沒有!
不過沒關(guān)系,我們只需設(shè)置數(shù)據(jù)庫(kù)離線工作曲初。打開 * AppDelegate*,在(_:didFinishLaunchingWithOptions:) 底部方法返回 true 之前体谒,添加如下代碼:
FIRDatabase.database().persistenceEnabled = true
是的,就是這樣! 就像我們的應(yīng)用能夠離線運(yùn)行一樣。當(dāng) app 重啟臼婆,一旦建立網(wǎng)絡(luò)連接抒痒,離線更新也將作用于我們的 Firebase 數(shù)據(jù)庫(kù)。Oooh-ahhhh !
Where To Go From Here?
我們可以在這里下載 Grocr-final完整項(xiàng)目颁褂。
注意:下載完后故响,我們?nèi)孕枰砑幼约旱?GoogleService-Info.plist 和 設(shè)置允許Keychain sharing 。
在這個(gè)Firebase教程中颁独,我們通過構(gòu)建一個(gè)協(xié)作的購(gòu)物清單 app 了解了Firebase的基礎(chǔ)知識(shí)彩届,我們已經(jīng)實(shí)現(xiàn)了將數(shù)據(jù)保存到一個(gè) Firebase 數(shù)據(jù)庫(kù)、實(shí)時(shí)同步數(shù)據(jù)誓酒、認(rèn)證用戶惨缆、監(jiān)視在線用戶狀態(tài)以及實(shí)現(xiàn)了離線支持。所有這些都是在沒有寫一行服務(wù)器代碼的情況下完成的! :]
如果你對(duì) Firebase 感興趣丰捷,請(qǐng)查看文檔 documentation,以及 Firebase 提供的示例寂汇。
如果您對(duì)這個(gè)Firebase教程病往、Firebase或示例應(yīng)用有任何意見或問題,請(qǐng)加入下面的論壇討論!
上海 虹橋V1
2017.09.06 19:02