Firebase 新手指南:使用 Swift 構建一款簡單的社交應用

作者:MATTHEW MAHER,原文鏈接,原文日期:2015-11-12
譯者:CoderAFI;校對:Cee;定稿:Channe

隨著移動互聯(lián)網(wǎng)快速發(fā)展,想要自己做一款用戶喜歡的應用程序越來越困難。而且蘋果提供的開發(fā)工具和資源也越發(fā)不能滿足開發(fā)者在數(shù)據(jù)服務方面的需求。于是 BaaS 服務(Backend-as-a-Service)給開發(fā)者帶來了新的選擇。

谷歌的 Firebase 是目前最流行的 BaaS 服務。Firebase 在性能、易用性、可維護性方面做得都非常好。Firebase 的特色服務是實時同步的 JSON 結構數(shù)據(jù)庫。這種數(shù)據(jù)庫可以迅速感知數(shù)據(jù)變化并立馬同步到其他的客戶端和設備。換句話說,F(xiàn)irebase 同步功能快如閃電。

Firebase 同時提供了基于加密 SSL 鏈接的用戶授權認證服務。在認證方面,你可以選擇郵箱和密碼、Facebook、Twitter、Github、Google 或者自定義這些組合來進行認證。

不僅僅是在 iOS 上,F(xiàn)irebase 還在 Android 和 JavaScript 平臺上提供了 SDK。全平臺共用一套實時數(shù)據(jù)庫,F(xiàn)irebase 統(tǒng)一來管理數(shù)據(jù),自動進行數(shù)據(jù)同步。

我們很難想象,擁有眾多功能的 Firebase 會是一個經(jīng)濟實惠的選擇。所以下面要揭露一下價格上不好的地方……

其實并沒有。在寫這篇教程的時候,F(xiàn)irebase 免費支持同時 100 個并發(fā)連接。這個并發(fā)數(shù)已經(jīng)算比較大了。同時 100 個并發(fā)連接可以支持一個非常好的應用程序了。當然,如果每月付費 49 美元,就不會有上面的限制了。

FirebaseJokes 簡介

今天,我就帶領大家用 Firebase 來做一款發(fā)布笑話的應用程序。這款應用允許用戶用郵箱和密碼進行注冊和登錄。登錄成功之后就可以發(fā)布笑話,然后顯示笑話的 tableview 會立刻更新。同樣,如果其他人也發(fā)布了笑話,界面也會立馬更新。為了增加應用程序的互動性,添加了點贊功能。如果笑話很有意思就會得到大家的贊譽。

FirebaseJokes

下面是我們即將在 FirebaseJokes 中實現(xiàn)的功能列表:

  • 創(chuàng)建帳號
  • 郵箱密碼登錄
  • 登出
  • 對于登錄用戶跳過登錄環(huán)節(jié)
  • 發(fā)布新笑話
  • 使用 UITableView 展示笑話、作者和點贊數(shù)
  • 對好笑話進行點贊

現(xiàn)在,讓我們下載初始工程

首先打開 Main.Storyboard,App 的總體框架如下:

Main.Storyboard

在我們開發(fā)這款應用過程中,我們會逐步了解 Firebase 的相關的用法和概念。由于 Firebase 使用起來非常簡單,所以我們只需要花時間來開發(fā) FirebaseJokes 業(yè)務部分就可以。

Firebase 概覽

我們打開 Firebase 主頁并注冊一個賬號,或者如果你已經(jīng)有賬號了可以直接登錄。當然也支持谷歌賬號注冊登錄。注冊成功后,我們直接跳過官方為 JavaScript 準備的五分鐘簡明教程。我們在這里只需要查看 iOS 的版本即可。

Firebase Homepage

想了解 Firebase 究竟有哪些功能,我們只需要點擊 My First App 中的 Manage App 按鈕。這個新頁面就是 Firebase 控制臺(Firebase Forge)。它是一個非常酷的可視化調試工具,并且值得我們觀看新手引導教程。教程會指引你如何創(chuàng)建鍵、值數(shù)據(jù),甚至可以用加號按鈕來創(chuàng)建子節(jié)點數(shù)據(jù)。這是不是很像 JSON 呢?通過點按左上角的 Dashboard 圖標便可以退出新手引導教程。

創(chuàng)建應用程序

Create New App

現(xiàn)在,讓開始創(chuàng)建 FirebaseJokes 吧。在 My First App 的左邊,有個顏色較淡的框就是用來創(chuàng)建新應用的。在 APP NAME 的文本框中,輸入「Jokes」;APP URL 的文本框中,輸入「jokes-你的名字」。這樣可以保證每個應用都有唯一的 url。最后點擊 CREATE NEW APP 按鈕,成功之后點擊 Manage App 按鈕。

接下來,我們就進入了這個應用的控制中心界面。在這里,我們可以看到當前應用程序數(shù)據(jù)的實時更新變化。我們也可以在控制中心直接修改數(shù)據(jù)。為了更好的理解應用程序的工作流程,我們造一些假數(shù)據(jù)來模擬下,步驟如下:

  1. 在應用名稱旁邊點擊小加號按鈕;
  2. 在文本框中輸入「jokes」;
  3. 在新建的「jokes」上再次點擊加號來新創(chuàng)建一行;
  4. 在新的文本框中輸入一些隨機數(shù);
  5. 在創(chuàng)建的隨時數(shù)上點擊加號再新創(chuàng)建一行;
  6. 輸入「jokeText」作為鍵;
  7. 輸入「What did one computer say to the other? 11001001010101」作為值。
Forge Data

上圖就是在控制中心創(chuàng)建一條笑話后的樣子。我們不僅僅需要創(chuàng)建「笑話」,也需要創(chuàng)建「用戶」,但其實兩者都非常類似。時不時的回來看下控制臺上的應用程序的數(shù)據(jù)變化是一個非常好的習慣。

有一點我要指出的是 Firebase 上的數(shù)據(jù)都是以 JSON 對象的形式存儲的。而 Parse 是采用表格的形式來存儲的。當我們向 Firebase 的數(shù)據(jù)庫中添加數(shù)據(jù)其實就是向現(xiàn)有 JSON 結構中添加一組鍵值對結構。剛剛我們創(chuàng)建的數(shù)據(jù) JSON 結構如下:

json
{
  "jokes" : {
    "e32e223r44" : {
      "jokeText" : "What did one computer say to the other? 11001001010101"
    }
  }
}

現(xiàn)在你已經(jīng)基本了解了 Firebase 數(shù)據(jù)的存儲方式,讓我們繼續(xù)吧。

記住在我們開始驗證用戶信息之前,先把之前創(chuàng)建的假數(shù)據(jù)刪掉,因為接下來我們會在 app 中編寫這些數(shù)據(jù)。

在 FirebaseJokes 中,我們將會用郵箱和密碼注冊登錄來進行授權認證。點擊控制中心左邊的「Login & Auth」面板來開啟這個功能。在「Email & Password」選項卡下,選擇「Enable Email & Password Authentication」復選框。選擇完成后在復選框下面會出現(xiàn)密碼重置的相關設置信息。同時,你也可以點擊其他選項卡了解下其他登錄授權方式。

User Auth

安裝 Firebase SDK 并配置 Base URL

我們返回到控制中心,就可以看到 Firebase 應用的 base url。這個 url 正是我們 app 中需要的那一個 url,所以我們打開 Xcode,將它復制并粘貼到 Constants.swift 中的 BASE_URL 處。

import Foundation

let BASE_URL = "https://jokes-matt-maher.firebaseio.com"

現(xiàn)在,我們終于可以在工程中集成 Firebase SDK 了。在此之前我們需要安裝 CocoaPods。如果你沒用過 CocoaPods,你可以去 CocoaPods 的官網(wǎng)上找到一些安裝的指南。

CocoaPods 安裝好后,打開終端,在你的工程目錄下運行如下命令來初始化 Cocoapods 配置文件:

sh
cd <your-xcode-project-directory>
pod init

運行完后,會出現(xiàn)一個 Podfile 的文件。運行如下命令,在 Xcode 中打開 Podfile 文件:

sh
open -a Xcode Podfile

接下來,像這樣編輯 Podfile 文件:

sh
platform :ios, '8.0'
use_frameworks!
 
pod 'Firebase', '>= 2.5.0'

最后,在工程目錄下運行如下命令,下載安裝 Firebase SDK:

sh
pod install

跳轉到工程目錄,雙擊打開 FirebaseJokes.xcworkspace 就可以開始敲代碼了。

使用Firebase SDK

首先,我們在 DataService.swift 中做一些配置,為之后的所要做的事情鋪路。最重要的就是定義一些引用。

import Foundation
import Firebase
 
class DataService {
    static let dataService = DataService()
    
    private var _BASE_REF = Firebase(url: "\(BASE_URL)")
    private var _USER_REF = Firebase(url: "\(BASE_URL)/users")
    private var _JOKE_REF = Firebase(url: "\(BASE_URL)/jokes")
    
    var BASE_REF: Firebase {
        return _BASE_REF
    }
    
    var USER_REF: Firebase {
        return _USER_REF
    }
    
    var CURRENT_USER_REF: Firebase {
        let userID = NSUserDefaults.standardUserDefaults().valueForKey("uid") as! String
        
        let currentUser = Firebase(url: "\(BASE_REF)").childByAppendingPath("users").childByAppendingPath(userID)
        
        return currentUser!
    }
    
    var JOKE_REF: Firebase {
        return _JOKE_REF
    }
}

導入 Firebase 框架后就可以使用 Firebase SDK 中的接口了。上面的 DataService 其實是一個和 Firebase 交互的服務類。為了能夠讀寫數(shù)據(jù),我們引用了 base url 來創(chuàng)建了一個 Firebase 的數(shù)據(jù)庫。接下來,我們會將用戶和笑話當做子節(jié)點來存儲。獲取這些子節(jié)點的內容,我們只要在 base url 后拼接上節(jié)點名稱(例如 name)就可以訪問了。為了讓子節(jié)點操作更方便,我們也創(chuàng)建了對子節(jié)點的引用。

注意:創(chuàng)建一個數(shù)據(jù)庫引用并不意味著就創(chuàng)建了一個 Firebase 服務的數(shù)據(jù)庫連接。如果沒有發(fā)起讀和寫的操作是無法獲取數(shù)據(jù)的。

創(chuàng)建一個新賬戶

我們從創(chuàng)建 CreateAccountViewController.swift 文件來開始。對于要用 Firebase 的類,必須要在文件頂部導入 Firebase 框架。

import UIKit
import Firebase
 
class CreateAccountViewController: UIViewController {

createAccount() 方法中,我們將用戶輸入的文本用來創(chuàng)建新的用戶。該方法其實是調用了 Firebase 的 createUser() 方法,代碼如下:

@IBAction func createAccount(sender: AnyObject) {
    let username = usernameField.text
    let email = emailField.text
    let password = passwordField.text
    
    if username != "" && email != "" && password != "" {
        
        // Set Email and Password for the New User.
        
        DataService.dataService.BASE_REF.createUser(email, password: password, withValueCompletionBlock: { error, result in
            
            if error != nil {
                
                // There was a problem.
                self.signupErrorAlert("Oops!", message: "Having some trouble creating your account. Try again.")
                
            } else {
                
                // Create and Login the New User with authUser
                DataService.dataService.BASE_REF.authUser(email, password: password, withCompletionBlock: {
                    err, authData in
                    
                    let user = ["provider": authData.provider!, "email": email!, "username": username!]
                    
                    // Seal the deal in DataService.swift.
                    DataService.dataService.createNewAccount(authData.uid, user: user)
                })
                
                // Store the uid for future access - handy!
                NSUserDefaults.standardUserDefaults().setValue(result ["uid"], forKey: "uid")
                
                // Enter the app.
                self.performSegueWithIdentifier("NewUserLoggedIn", sender: nil)
            }
        })
        
    } else {
        signupErrorAlert("Oops!", message: "Don't forget to enter your email, password, and a username.")
    }
 
}

如果用戶輸入的信息無誤,就可以注冊并登錄到 app 中。如果信息缺失,就會展示一些相應的提示信息。下面的代碼來展示錯誤信息:

func signupErrorAlert(title: String, message: String) {
        
    // Called upon signup error to let the user know signup didn't work.
    
    let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
    let action = UIAlertAction(title: "Ok", style: .Default, handler: nil)
    alert.addAction(action)
    presentViewController(alert, animated: true, completion: nil)
}

實際上,創(chuàng)建用戶是在 DataService.swift 的 createNewAccount() 方法中:

func createNewAccount(uid: String, user: Dictionary<String, String>) {
    
    // A User is born.
    
    USER_REF.childByAppendingPath(uid).setValue(user)
}

要向 Firebase 數(shù)據(jù)庫中保存數(shù)據(jù),你只需要調用 setValue 方法。在上面的代碼中,將會根據(jù) uid 保存一個新的 user 對象到 users 中(例如 /users/1283834)。

除了將用戶存儲到 Firebase 數(shù)據(jù)庫,我們還需要在本地用 NSUserDefaults 來存儲用戶的 uid。這樣可以實時跟蹤當前登錄用戶。

用戶登錄

在我們深入探討之前,我們在 LoginViewController.swift 文件中引入 Firebase 框架。在這個類中我們將會判斷用戶是否登錄過,如果沒用登錄則跳轉到登錄界面。

viewDidAppear() 方法中,我們會檢查存儲的 uid 是否為空,以此來驗證用戶是否登錄。如果檢查通過,則可以直接跳過登錄;反之進行登錄授權。

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    
    // If we have the uid stored, the user is already logger in - no need to sign in again!
    
    if NSUserDefaults.standardUserDefaults().valueForKey("uid") != nil && DataService.dataService.CURRENT_USER_REF.authData != nil {
        self.performSegueWithIdentifier("CurrentlyLoggedIn", sender: nil)
    }
}

當用戶點擊 Login 按鈕時,tryLogin() 方法就會執(zhí)行。在該方法中加入如下代碼并添加一個 loginErrorAlert 的輔助方法:

@IBAction func tryLogin(sender: AnyObject) {
    
    let email = emailField.text
    let password = passwordField.text
    
    if email != "" && password != "" {
        
        // Login with the Firebase's authUser method
        
        DataService.dataService.BASE_REF.authUser(email, password: password, withCompletionBlock: { error, authData in
            
            if error != nil {
                print(error)
                self.loginErrorAlert("Oops!", message: "Check your username and password.")
            } else {
                
                // Be sure the correct uid is stored.
                
                NSUserDefaults.standardUserDefaults().setValue(authData.uid, forKey: "uid")
                
                // Enter the app!
                
                self.performSegueWithIdentifier("CurrentlyLoggedIn", sender: nil)
            }
        })
        
    } else {
        
        // There was a problem
        
        loginErrorAlert("Oops!", message: "Don't forget to enter your email and password.")
    }
}

func loginErrorAlert(title: String, message: String) {
    
    // Called upon login error to let the user know login didn't work.
    
    let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
    let action = UIAlertAction(title: "Ok", style: .Default, handler: nil)
    alert.addAction(action)
    presentViewController(alert, animated: true, completion: nil)
}

Firebase 默認支持用戶郵箱和密碼進行用戶授權認證。tryLogin() 方法調用了 Firebase 的 authUser() 方法來檢查當前郵箱和密碼是否已經(jīng)注冊過。如果注冊過,就能獲取用戶的 uid ,登錄成功進入 app。如果沒有,我們則給出一些其他嘗試的提示。

既然現(xiàn)在 app 已經(jīng)完成了用戶注冊和登錄功能,接下來我們就來看看如何對笑話數(shù)據(jù)進行處理。

笑話的數(shù)據(jù)模型

什么是笑話?這個哲學問題我們可以以后再回答,或者看下 FirebaseJokes 上的一個笑話就會明白了。對我們而言,抽象出一個 Joke 數(shù)據(jù)模型更加重要。

我們在 Joke.swift 中導入 Firebase 框架。在我們的數(shù)據(jù)庫中,一個 joke 對象映射一個唯一編號。這個自動生成的編號包含了 joke 中的如下屬性:

  • jokeText
  • jokeVotes
  • username(笑話的作者)

我們將笑話的 id、key 和內容,以字典的形式作為參數(shù),通過 init() 方法來新建一個笑話對象。

class Joke {
    private var _jokeRef: Firebase!
    
    private var _jokeKey: String!
    private var _jokeText: String!
    private var _jokeVotes: Int!
    private var _username: String!
    
    var jokeKey: String {
        return _jokeKey
    }
    
    var jokeText: String {
        return _jokeText
    }
    
    var jokeVotes: Int {
        return _jokeVotes
    }
    
    var username: String {
        return _username
    }
    
    // Initialize the new Joke
    
    init(key: String, dictionary: Dictionary<String, AnyObject>) {
        self._jokeKey = key
        
        // Within the Joke, or Key, the following properties are children
        
        if let votes = dictionary["votes"] as? Int {
            self._jokeVotes = votes
        }
        
        if let joke = dictionary["jokeText"] as? String {
            self._jokeText = joke
        }
        
        if let user = dictionary["author"] as? String {
            self._username = user
        } else {
            self._username = ""
        }
        
        // The above properties are assigned to their key.
        
        self._jokeRef = DataService.dataService.JOKE_REF.childByAppendingPath(self._jokeKey)
    }
}

添加新的笑話

AddJokeViewController.swift 文件中,是時候引入 Firebase 框架了。在這個類中,我們將完成讓用戶添加一個笑話,發(fā)送到 Firebase 服務上,然后立刻推送到各個設備上進行同步展示。

首先在 viewDidLoad() 方法中,我們首先獲取當前用戶的用戶名,來當做新創(chuàng)建笑話的作者,代碼如下:

override func viewDidLoad() {
    super.viewDidLoad()
    
    // Get username of the current user, and set it to currentUsername, so we can add it to the Joke.
    
    DataService.dataService.CURRENT_USER_REF.observeEventType(FEventType.Value, withBlock: { snapshot in
        
        let currentUser = snapshot.value.objectForKey("username") as! String
        
        print("Username: \(currentUser)")
        self.currentUsername = currentUser
        }, withCancelBlock: { error in
            print(error.description)
    })
}

saveJoke() 方法被調用時,將會創(chuàng)建一個新的笑話的字典對象。這個字典對象會包含由 jokeField 中的文字組成的笑話的內容、投票數(shù)(默認為 0)以及作者名(當前用戶的用戶名)。這些值將通過 DataSerivce 中的 createNewJoke() 方法發(fā)送到服務器來進行存儲。

AddJokeViewController 類中聲明如下變量:

var currentUsername = ""

更新 saveJoke() 方法:

@IBAction func saveJoke(sender: AnyObject) {
    
    let jokeText = jokeField.text
    
    if jokeText != "" {
        
        // Build the new Joke. 
        // AnyObject is needed because of the votes of type Int.
        
        let newJoke: Dictionary<String, AnyObject> = [
            "jokeText": jokeText!,
            "votes": 0,
            "author": currentUsername
        ]
        
        // Send it over to DataService to seal the deal.
        
        DataService.dataService.createNewJoke(newJoke)
        
        if let navController = self.navigationController {
            navController.popViewControllerAnimated(true)
        }
    }
}

我們用一個字典對象來臨時存儲笑話數(shù)據(jù)。并將該字典傳遞給 DataService 中的 createNewJoke() 方法來發(fā)送到服務器端進行存儲。在 DataSerivce.swift 文件中,添加 createNewJoke() 方法:

func createNewJoke(joke: Dictionary<String, AnyObject>) {
    
    // Save the Joke
    // JOKE_REF is the parent of the new Joke: "jokes".
    // childByAutoId() saves the joke and gives it its own ID.
    
    let firebaseNewJoke = JOKE_REF.childByAutoId()
    
    // setValue() saves to Firebase.
    
    firebaseNewJoke.setValue(joke)
}

我們是通過 Firebase 的 setValue() 方法來存儲的笑話對象。但是也不要忽略我們調用了 Joke 數(shù)據(jù)庫引用對象的 childByAutoId 方法。通過調用此方法,F(xiàn)irebase 會為每一個笑話生成唯一的編號,來確保笑話存儲的唯一性。

用戶登出

這個功能通常會出現(xiàn)在設置和個人簡介部分。但是我們就姑且把登出功能放在 AddJokeViewController.swift 類中吧。或者說,這就是一個最大的笑話。

logout() 方法實際上是調用了 Firebase 的 unauth() 方法來讓用戶退出登錄。在用戶登出的同時,我們需要刪除在 LoginViewController 中存儲在本地的 uid

像這樣更新 logout 部分的方法:

@IBAction func logout(sender: AnyObject) {
    
    // unauth() is the logout method for the current user.
    
    DataService.dataService.CURRENT_USER_REF.unauth()
    
    // Remove the user's uid from storage.
    
    NSUserDefaults.standardUserDefaults().setValue(nil, forKey: "uid")
    
    // Head back to Login!
    
    let loginViewController = self.storyboard!.instantiateViewControllerWithIdentifier("Login")
    UIApplication.sharedApplication().keyWindow?.rootViewController = loginViewController
}

如果沒有刪除用戶的 uid,可能會導致用戶再次登錄的時候出現(xiàn)問題,這里要注意一下。

展示所有的笑話

最后就是從 Firebase 上獲取笑話數(shù)據(jù)展示出來。我們將會在 JokesFeedTableViewController.swift 文件中用一個 UITableView 來展示所有的笑話。在這里我們還是要先導入 Firebasde 框架。

viewDidLoad() 方法中,配置 observeEventType() 方法。Firebase 是通過數(shù)據(jù)庫的引用對象來異步監(jiān)聽數(shù)據(jù)變化來獲取更新數(shù)據(jù)的。這個方法是在導航到 JokesFeedTableViewController.swift 界面時,在 viewDidLoad() 方法中被調用的。當數(shù)據(jù)庫有任何笑話數(shù)據(jù)變化時這個監(jiān)聽方法就會被調用,代碼如下:

var jokes = [Joke]()
   
override func viewDidLoad() {
   super.viewDidLoad()
   
   // observeEventType is called whenever anything changes in the Firebase - new Jokes or Votes.
   // It's also called here in viewDidLoad().
   // It's always listening.
   
   DataService.dataService.JOKE_REF.observeEventType(.Value, withBlock: { snapshot in
       
       // The snapshot is a current look at our jokes data.
       
       print(snapshot.value)
       
       self.jokes = []
       
       if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {
           
           for snap in snapshots {
               
               // Make our jokes array for the tableView.
               
               if let postDictionary = snap.value as? Dictionary<String, AnyObject> {
                   let key = snap.key
                   let joke = Joke(key: key, dictionary: postDictionary)
                   
                   // Items are returned chronologically, but it's more fun with the newest jokes first.
                   
                   self.jokes.insert(joke, atIndex: 0)
               }
           }
           
       }
       
       // Be sure that the tableView updates when there is new data.
       
       self.tableView.reloadData()
   })
}

在回調方法中返回了一些笑話對象。我們用一個數(shù)組來存儲這些對象,并展示在 tableView 上。我們想讓最新的笑話保持在頂部,但是 Firebase 返回的笑話數(shù)據(jù)是按照時間排序的,所以我們必須將數(shù)據(jù)反向插入。

每次當笑話更新的時候,我們別忘了調用 tableView 的 reloadData 方法,這樣才能讓新的數(shù)據(jù)展示出來。

我們剩下的工作就是在 tableView:cellForRowAtIndexPath: 方法中指定自定義 cell:JokeCellTableViewCell.swift。在 JokeCellTableViewCell.swift 文件中創(chuàng)建一個 configureCell() 方法,當執(zhí)行 tableView:cellForRowAtIndexPath: 時調用該方法,代碼如下:

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    
    return 1
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    
    return jokes.count
}


override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    
    let joke = jokes[indexPath.row]
    
    // We are using a custom cell. 
    
    if let cell = tableView.dequeueReusableCellWithIdentifier("JokeCellTableViewCell") as? JokeCellTableViewCell {
        
        // Send the single joke to configureCell() in JokeCellTableViewCell.
        
        cell.configureCell(joke)
        
        return cell
        
    } else {
        
        return JokeCellTableViewCell()
        
    }
}

在 JokeCellTableViewCell.swift 文件中的 configureCell() 方法里,我們需要設置 label 標簽以及添加監(jiān)聽投票的點按邏輯。

func configureCell(joke: Joke) {
    self.joke = joke
    
    // Set the labels and textView.
    
    self.jokeText.text = joke.jokeText
    self.totalVotesLabel.text = "Total Votes: \(joke.jokeVotes)"
    self.usernameLabel.text = joke.username
    
    // Set "votes" as a child of the current user in Firebase and save the joke's key in votes as a boolean.
    
    voteRef = DataService.dataService.CURRENT_USER_REF.childByAppendingPath("votes").childByAppendingPath(joke.jokeKey)
    
    // observeSingleEventOfType() listens for the thumb to be tapped, by any user, on any device.
    
    voteRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
        
        // Set the thumb image.
        
        if let thumbsUpDown = snapshot.value as? NSNull {
            
            // Current user hasn't voted for the joke... yet.
            
            print(thumbsUpDown)
            self.thumbVoteImage.image = UIImage(named: "thumb-down")
        } else {
            
            // Current user voted for the joke!
            
            self.thumbVoteImage.image = UIImage(named: "thumb-up")
        }
    })
}

UITapGestureRecognizer 手勢監(jiān)聽是在 awakeFromNib() 方法中添加的。在類的頂部,同時要加上 Firebase 和 Joke 對象的引用。

var joke: Joke!
var voteRef: Firebase!
    
override func awakeFromNib() {
    super.awakeFromNib()
    
    // UITapGestureRecognizer is set programatically.
    
    let tap = UITapGestureRecognizer(target: self, action: "voteTapped:")
    tap.numberOfTapsRequired = 1
    thumbVoteImage.addGestureRecognizer(tap)
    thumbVoteImage.userInteractionEnabled = true
}

voteTapped() 方法中,有另外一個監(jiān)聽者在等待著用戶點擊后觸發(fā)回調。在這個方法中,將會把投票數(shù)通過 configureCell() 方法中的 voteRef 對象存儲到對應笑話下鍵為 votes 的字段值中。

func voteTapped(sender: UITapGestureRecognizer) {
    
    // observeSingleEventOfType listens for a tap by the current user.
    
    voteRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
        
        if let thumbsUpDown = snapshot.value as? NSNull {
            print(thumbsUpDown)
            self.thumbVoteImage.image = UIImage(named: "thumb-down")
            
            // addSubtractVote(), in Joke.swift, handles the vote.
            
            self.joke.addSubtractVote(true)
            
            // setValue saves the vote as true for the current user.
            // voteRef is a reference to the user's "votes" path.
            
            self.voteRef.setValue(true)
        } else {
            self.thumbVoteImage.image = UIImage(named: "thumb-up")
            self.joke.addSubtractVote(false)
            self.voteRef.removeValue()
        }
        
    })
}

voteTapped() 方法也會通過一個布爾值調用 addSubtractVote() 函數(shù)來更新 Joke.swift 中的投票數(shù)。如果參數(shù)是 True,則代表用戶給這個笑話投票了;反之,F(xiàn)alse 則代表沒有投過票。

// Add or Subtract a Vote from the Joke.

func addSubtractVote(addVote: Bool) {
    
    if addVote {
        _jokeVotes = _jokeVotes + 1
    } else {
        _jokeVotes = _jokeVotes - 1
    }
    
    // Save the new vote total.
    
    _jokeRef.childByAppendingPath("votes").setValue(_jokeVotes)
    
}

在 Joke.swift 的文件中,addSubtractVote() 方法使用了這個布爾值完成對票數(shù)的加減。Firebase 所提供的 setValue() 方法用來更新當前笑話的投票數(shù)到遠端數(shù)據(jù)庫。

測試

現(xiàn)在,可以對這個應用進行測試了。創(chuàng)建一個新用戶并創(chuàng)建一些笑話。也可以對這些笑話進行投票點贊。然后回到 Firebase 的控制中心,就可以看到剛剛創(chuàng)建的用戶和笑話,如下圖:

Joke Firebase Data

總結

完成了!我們基于 Firebase 開發(fā)了一個用戶體驗不錯的小應用。很有成就感吧!

這里是完成后的代碼

這個應用只是一個小的開始,基于 Firebase 服務來開發(fā)應用程序將會有無限可能。

嘗試下為 FirebaseJokes 添加用戶授權的功能;也可以添加聊天的功能;你會發(fā)現(xiàn) Firebase 提供了無窮無盡的可能性。

關于圖片存儲的建議:Firebase 存儲服務也有其局限性,像圖片文件就不太適合用 Firebase 來存儲,應該存儲在單獨的圖片服務器上。像上面的純文本存儲型應用使用 Firebase 是沒有問題的,但是如果應用要存儲大型文件,最好做一套單獨的文件服務。

好了,現(xiàn)在就是加入 Firebase 服務陣營的最好時機,行動起來吧。

本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權,最新文章請訪問 http://swift.gg

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 12,245評論 4 61
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,349評論 25 708
  • 中國人的做事習慣比較愛找關系,尤其是拿不準的找個人斟酌一下,買個車,買個房都愛走走關系看看能不能拿個內部價。 我自...
    基礎繪畫社閱讀 1,215評論 1 0
  • www.taggerin.com,主要處理日常文檔的在線編輯,以及與Markdown,PDF,html等格式的雙向...
    igool閱讀 291評論 0 1