Firebase Tutorial: Getting Started 中文版

備注: 本教程已由 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:

  1. LoginViewController.swift.
    現(xiàn)在登錄功能還是使用的硬編碼 user credentials帆焕,稍后我們將優(yōu)化它惭婿。

  2. GroceryListTableViewController.swift.
    這個(gè) controller 是 UITableViewController 子類,它通過 UIAlertController 添加 items 到本地?cái)?shù)據(jù)庫(kù)的 list 表格叶雹。

  3. OnlineUsersTableViewController.swift.
    該 controller 使用 Firebase’s presence feature 展示所有當(dāng)前在線 users财饥。

此外,還有兩個(gè)模型類 GroceryItem.swiftUser.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è)重要步驟:

  1. 創(chuàng)建免費(fèi) Firebase 賬號(hào)
  2. 獲取你第一個(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"

下面我們解釋下:

  1. 我們創(chuàng)建一個(gè)到 Firebase 數(shù)據(jù)庫(kù) root 引用井辜。
  2. 使用一個(gè) URL 绎谦,我們可以創(chuàng)建一個(gè)引用到 Firebase 數(shù)據(jù)庫(kù)的子路徑。
  3. 通過給 rootRef 傳遞子路徑粥脚,我們可以使用 child(_:) 創(chuàng)建子引用窃肠,這個(gè)引用和上面的引用是一樣意思。
  4. 使用 itemsRef 刷允,我們可以創(chuàng)建到 milk 的子引用冤留。
  5. 每個(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())
}

注釋如下:

  1. 從 alert controller 獲取 text field 和它的內(nèi)容。

  2. 使用當(dāng)前用戶數(shù)據(jù)創(chuàng)建一個(gè)新的 GroceryItem 序臂。

  3. 使用 child(_:) 創(chuàng)建一個(gè)子引用蚌卤,這個(gè)引用的 key 是 item 的小寫名稱,因此如果我們添加一個(gè)復(fù)制的 item (即使使用大寫字母奥秆,或者使用混合字母)逊彭,數(shù)據(jù)庫(kù)只保存最后一個(gè)。

  4. 使用 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()
})

以上代碼的諸行解釋:

  1. 添加一個(gè)監(jiān)聽器監(jiān)聽 grocery-items 改變了什么覆享。

  2. 存儲(chǔ)最近一次版本數(shù)據(jù)到閉包中本地的一個(gè)變量中。

  3. 監(jiān)聽者閉包返回最近數(shù)據(jù)的一個(gè) snapshot营袜,這個(gè) snapshot 包含所有的 grocery items撒顿,而不是僅僅包含改變的 items。使 snapshot.children 连茧,我們可以循環(huán)獲取 grocery items 核蘸。

  4. 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ù)組中徙鱼。

  5. 將最新版本的數(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ì)注解:

  1. 使用 cellForRow(at:) 確定用戶點(diǎn)擊的 cell羹蚣。
  2. 根據(jù) index path 的 row 獲取對(duì)應(yīng)的 GroceryItem原探。
  3. 改變 grocery item 的 completed 的狀態(tài)。
  4. 調(diào)用 toggleCellCheckbox(_:isCompleted:) 更新 cell 的屬性度宦。
  5. 在 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!)
  }
}

以上代碼解釋:

  1. 從彈框中獲取郵箱和密碼金闽。
  2. 調(diào)用 Firebase 方法 createUser(withEmail:password:)纯露,傳遞郵箱和密碼給它。
  3. 如果執(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)
    }
  }
}

注釋如下:

  1. 使用 addStateDidChangeListener(_:) 創(chuàng)建驗(yàn)證觀察者剧浸。該 block 被傳入兩個(gè)參數(shù):auth 和 user爽丹。

  2. 測(cè)試 user 的值,如果驗(yàn)證通過辛蚊,返回用戶信息,如果驗(yàn)證失敗真仲,返回 nil 袋马。

  3. 驗(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()

注釋如下:

  1. 使用用戶的 uid 創(chuàng)建一個(gè) child 引用,當(dāng) Firebase 創(chuàng)建一個(gè)賬號(hào)時(shí)摄咆,這個(gè)引用會(huì)被生成。
  2. 使用這個(gè)引用保存當(dāng)前用戶的 email.
  3. 當(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)
})

代碼注釋如下:

  1. 創(chuàng)建一個(gè) children added 監(jiān)聽器中跌,添加到被 usersRef 管理的位置咨堤。這與值偵聽器不同,因?yàn)橹挥刑砑拥?child 被傳遞到閉包漩符。

  2. 從 snapshot 獲取值一喘,并賦值給本地變量 array。

  3. 因?yàn)?table view 的坐標(biāo)從 0 開始計(jì)算嗜暴,當(dāng)前的 row 總是等于 array 的個(gè)數(shù) -1凸克。

  4. 使用當(dāng)前 row index 創(chuàng)建一個(gè) NSIndexPath.

  5. 使用動(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末骄瓣,一起剝皮案震驚了整個(gè)濱河市停巷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌榕栏,老刑警劉巖畔勤,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異扒磁,居然都是意外死亡庆揪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門妨托,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)缸榛,“玉大人吝羞,你說我怎么就攤上這事∧诳牛” “怎么了钧排?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)均澳。 經(jīng)常有香客問我恨溜,道長(zhǎng),這世上最難降的妖魔是什么找前? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任糟袁,我火速辦了婚禮,結(jié)果婚禮上纸厉,老公的妹妹穿的比我還像新娘系吭。我一直安慰自己,他們只是感情好颗品,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布肯尺。 她就那樣靜靜地躺著,像睡著了一般躯枢。 火紅的嫁衣襯著肌膚如雪则吟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天锄蹂,我揣著相機(jī)與錄音氓仲,去河邊找鬼。 笑死得糜,一個(gè)胖子當(dāng)著我的面吹牛敬扛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播朝抖,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼啥箭,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了治宣?” 一聲冷哼從身側(cè)響起急侥,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎侮邀,沒想到半個(gè)月后坏怪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡绊茧,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年铝宵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片华畏。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捉超,死狀恐怖胧卤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情拼岳,我是刑警寧澤枝誊,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站惜纸,受9級(jí)特大地震影響叶撒,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜耐版,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一祠够、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧粪牲,春花似錦古瓤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至亭引,卻和暖如春绎速,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背焙蚓。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工纹冤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人购公。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓萌京,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親宏浩。 傳聞我的和親對(duì)象是個(gè)殘疾皇子枫夺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容