Firebase iOS 程式碼研究室 Swift

1. 總覽

2efe6805ef369641.png

歡迎來到「親切的聊天」程式碼研究室。在本程式碼研究室中,您將瞭解如何使用 Firebase 平台建立 iOS 應用程式。您將使用 Firebase 實作即時通訊用戶端,並監控用戶端成效。

課程內容

  • 允許使用者登入。
  • 使用 Firebase 即時資料庫同步處理資料。
  • 在 Firebase 儲存空間中儲存二進位檔案。

軟硬體需求

  • Xcode
  • CocoaPods
  • 搭載 iOS 8.0 以上版本或模擬器的測試裝置

您會如何使用這個教學課程?

僅供閱讀 閱讀並完成練習

針對開發 iOS 應用程式的經驗,你會給予什麼評價?

新手 中級 還算容易

2. 取得程式碼範例

從指令列複製 GitHub 存放區。

$ git clone https://github.com/firebase/codelab-friendlychat-ios

3. 建構範例應用程式

2f4c98d858c453fe.png

如要建構範例應用程式:

  1. 在終端機視窗中,從下載的程式碼範例前往 android_studio_folder.pngios-starter/swift-starter 目錄
  2. 執行 pod install --repo-update
  3. 開啟 friendlyChatSwift.xcworkspace 檔案即可在 Xcode 中開啟專案。
  4. 按一下 98205811bbed9d74.png「Run」(執行) 按鈕。

幾秒後,你應該就會看到友善的 Chat 主畫面。系統應該會顯示 UI。但是,目前無法登入、收發郵件。應用程式將因例外狀況而取消,直到您完成下一個步驟為止。

4. 建立 Firebase 控制台專案

建立專案

Firebase 控制台,選取「新增專案」

呼叫專案 FriendlyChat,然後按一下「Create Project」

2015-11-06 14:13:39.png 的螢幕截圖

連結 iOS 應用程式

  1. 在新專案的「專案總覽」畫面中,按一下 [將 Firebase 新增至您的 iOS 應用程式]
  2. 輸入軟體包 ID,例如「com.google.firebase.codelab.FriendlyChatSwift」。
  3. 將 App Store ID 輸入「123456」。
  4. 按一下「Register App」

將 GoogleService-Info.plist 檔案新增至應用程式

在第二個畫面中,點選「Download GoogleService-Info.plist」下載設定檔,其中包含應用程式所有必要的 Firebase 中繼資料。將該檔案複製到您的應用程式,並新增至 friendlyChatSwift 目標。

您現在可以按一下「x」,即可跳過步驟 3 和步驟 4。您將在此執行這些步驟。

19d59efb213ddbdc.png

匯入 Firebase 模組

首先,請確認已匯入 Firebase 模組。

AppDelegate.swiftFCViewController.swift

import Firebase

在 AppDelegate 中設定 Firebase

使用「設定」方法,位於 application:didFinishLaunchingWithOptions 函式內,透過 .plist 檔案設定基礎 Firebase 服務。

AppDelegate.swift

  func application(_ application: UIApplication, didFinishLaunchingWithOptions
      launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  FirebaseApp.configure()
  GIDSignIn.sharedInstance().delegate = self
  return true
}

5. 識別使用者

使用規則限制已驗證使用者

我們現在要新增一項規則,規定使用者在讀取或寫入任何郵件前必須通過驗證。如要執行此操作,我們在訊息資料物件中新增下列規則。在 Firebase 控制台的「資料庫」專區中選取「即時資料庫」,然後點選「規則」分頁標籤。接著更新規則,如下所示:

{
  "rules": {
    "messages": {
      ".read": "auth != null",
      ".write": "auth != null"
    }
  }
}

如要進一步瞭解運作方式 (包括「auth」變數的說明文件),請參閱 Firebase 安全性說明文件

設定 Authentication API

您必須先為應用程式啟用 Firebase Authentication API,您的應用程式才能代表使用者存取 API。

  1. 前往 Firebase 控制台,然後選取所需專案
  2. 選取「Authentication」
  3. 選取「Sign In Method」分頁標籤
  4. 將「Google」切換鈕撥到啟用狀態 (藍色)
  5. 在產生的對話方塊中按下「Save」(儲存)

如果稍後在本程式碼研究室中發生錯誤,並顯示「CONFIGURATION_NOT_FOUND」訊息,請返回此步驟並仔細檢查工作。

確認 Firebase 驗證依附元件

確認 Podfile 檔案中已有 Firebase 驗證依附元件。

Podfile

pod 'Firebase/Auth'

設定 Google 登入所需的 Info.plist。

您必須在 XCode 專案中新增自訂網址通訊協定。

  1. 開啟專案設定:在左側樹狀檢視中,按兩下專案名稱。從「目標」部分選取您的應用程式,然後選取「資訊」分頁標籤,展開「網址類型」部分。
  2. 按一下 + 按鈕,然後為反向用戶端 ID 加入網址配置。如要尋找這個值,請開啟 GoogleService-Info.plist 設定檔,然後找出 REVERSED_CLIENT_ID 金鑰。複製該鍵的值,並貼到設定網頁上的 [網址配置] 方塊中。將其他欄位留白。
  3. 設定完成後,設定看起來應類似下列內容 (但包含您的應用程式專屬值):

1b54d5bd2f4f1448.png

設定 Google 登入功能的 ClientID

Firebase 設定完畢後,我們就可以在「didFinishLaunchingWithOptions」中使用 clientID 設定 Google 登入:方法。

AppDelegate.swift

  func application(_ application: UIApplication, didFinishLaunchingWithOptions
      launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  FirebaseApp.configure()
  GIDSignIn.sharedInstance().clientID = FirebaseApp.app()?.options.clientID
  GIDSignIn.sharedInstance().delegate = self
  return true
}

新增登入處理常式

成功傳回 Google 登入之後,請使用該帳戶進行 Firebase 驗證。

AppDelegate.swift

  func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error?) {
    if let error = error {
      print("Error \(error)")
      return
    }

    guard let authentication = user.authentication else { return }
    let credential = GoogleAuthProvider.credential(withIDToken: authentication.idToken,
                                                      accessToken: authentication.accessToken)
    Auth.auth().signIn(with: credential) { (user, error) in
      if let error = error {
        print("Error \(error)")
        return
      }
    }
  }

自動登入使用者。接著將事件監聽器新增至 Firebase Auth,讓使用者在成功登入後登入應用程式。然後移除 deinit 上的事件監聽器。

SignInViewController.swift

  override func viewDidLoad() {
    super.viewDidLoad()
    GIDSignIn.sharedInstance().uiDelegate = self
    GIDSignIn.sharedInstance().signInSilently()
    handle = Auth.auth().addStateDidChangeListener() { (auth, user) in
      if user != nil {
        MeasurementHelper.sendLoginEvent()
        self.performSegue(withIdentifier: Constants.Segues.SignInToFp, sender: nil)
      }
    }
  }

  deinit {
    if let handle = handle {
      Auth.auth().removeStateDidChangeListener(handle)
    }
  }

登出

新增登出方法

FCViewController.swift

  @IBAction func signOut(_ sender: UIButton) {
    let firebaseAuth = Auth.auth()
    do {
      try firebaseAuth.signOut()
      dismiss(animated: true, completion: nil)
    } catch let signOutError as NSError {
      print ("Error signing out: \(signOutError.localizedDescription)")
    }
  }

以已登入的使用者身分測試讀取訊息

  1. 按一下 98205811bbed9d74.png「Run」(執行) 按鈕。
  2. 登入畫面應該會隨即顯示。輕觸 Google 登入按鈕。
  3. 如果一切順利,系統就應該將畫面傳送到訊息畫面。

6. 啟用即時資料庫

2efe6805ef369641.png

匯入郵件

前往 Firebase 控制台的專案,選取左側導覽列的「資料庫」項目。在資料庫的溢位選單中,選取「Import JSON」(匯入 JSON)。找到 Nearbychat 目錄中的 initial_messages.json 檔案,選取該檔案,然後按一下「Import」按鈕。這會取代資料庫中現有的任何資料,您也可以直接編輯資料庫,使用綠色的 + 和紅色 x 新增與移除商品。

20ccf4856b715b4c.png

匯入資料庫後看起來會像這樣:

f3e0367f1c9cd187.png

確認 Firebase 資料庫依附元件

Podfile 檔案的依附元件區塊中,確認已納入 Firebase/Database

Podfile

pod 'Firebase/Database'

同步處理現有郵件

新增程式碼,將新增的訊息同步處理至應用程式 UI。

您在這個部分新增的程式碼將會:

  • 初始化 Firebase 資料庫並新增事件監聽器,來處理資料庫的變更。
  • 更新 DataSnapshot,以便顯示新訊息。

修改 FCViewController 的「deinit」、「configureDatabase」和「tableView:cellForRow indexPath:」方法;換成下方定義的程式碼:

FCViewController.swift

  deinit {
    if let refHandle = _refHandle {
      self.ref.child("messages").removeObserver(withHandle: _refHandle)
    }
  }


  func configureDatabase() {
    ref = Database.database().reference()
    // Listen for new messages in the Firebase database
    _refHandle = self.ref.child("messages").observe(.childAdded, with: { [weak self] (snapshot) -> Void in
      guard let strongSelf = self else { return }
      strongSelf.messages.append(snapshot)
      strongSelf.clientTable.insertRows(at: [IndexPath(row: strongSelf.messages.count-1, section: 0)], with: .automatic)
    })
  }


  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // Dequeue cell
    let cell = self.clientTable.dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath)
    // Unpack message from Firebase DataSnapshot
    let messageSnapshot = self.messages[indexPath.row]
    guard let message = messageSnapshot.value as? [String: String] else { return cell }
    let name = message[Constants.MessageFields.name] ?? ""
    let text = message[Constants.MessageFields.text] ?? ""
    cell.textLabel?.text = name + ": " + text
    cell.imageView?.image = UIImage(named: "ic_account_circle")
    if let photoURL = message[Constants.MessageFields.photoURL], let URL = URL(string: photoURL),
        let data = try? Data(contentsOf: URL) {
      cell.imageView?.image = UIImage(data: data)
    }
    return cell
  }

測試訊息同步處理

  1. 按一下 98205811bbed9d74.png「Run」(執行) 按鈕。
  2. 請按一下 [登入開始使用] 按鈕,前往訊息視窗。
  3. 按一下「訊息」旁邊的綠色 + 符號,即可直接在 Firebase 控制台中新增訊息然後新增如下的物件:f9876ffc8b316b14.png
  4. 確認這些內容會顯示在友善即時通訊的 UI 中。

7. 傳送訊息

實作「傳送訊息」功能

將值推送至資料庫。透過推送方法將資料新增至 Firebase 即時資料庫時,系統會自動新增 ID。這些自動產生的 ID 會依序排列,可確保新訊息以正確順序加入。

修改 FCViewController 的「sendMessage:」方法;換成下方定義的程式碼:

FCViewController.swift

  func sendMessage(withData data: [String: String]) {
    var mdata = data
    mdata[Constants.MessageFields.name] = Auth.auth().currentUser?.displayName
    if let photoURL = Auth.auth().currentUser?.photoURL {
      mdata[Constants.MessageFields.photoURL] = photoURL.absoluteString
    }

    // Push data to Firebase Database
    self.ref.child("messages").childByAutoId().setValue(mdata)
  }

測試傳送訊息

  1. 按一下 98205811bbed9d74.png「Run」(執行) 按鈕。
  2. 按一下 [登入],前往郵件視窗。
  3. 輸入訊息,然後按一下「傳送」新訊息應該會顯示在應用程式 UI 和 Firebase 控制台中。

8. 儲存及接收圖片

確認 Firebase Storage 依附元件

Podfile 的依附元件區塊中,確認已納入 Firebase/Storage

Podfile

pod 'Firebase/Storage'

在資訊主頁中啟用 Firebase 儲存空間

前往 Firebase 控制台,確認 Cloud Storage 已啟用「gs://PROJECTID.appspot.com」網域

b0438b37a588bcee.png

如果顯示的是啟用視窗,請按一下「開始使用」即可透過預設規則啟用該位址

c290bbebff2cafa7.png

設定 FirebaseStorage

FCViewController.swift

  func configureStorage() {
    storageRef = Storage.storage().reference()
  }

接收現有郵件中的圖片

新增從 Firebase 儲存空間下載圖片的程式碼。

修改 FCViewController 的 "tableView: CellForRowAt indexPath:方法;換成下方定義的程式碼:

FCViewController.swift

  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    // Dequeue cell
    let cell = self.clientTable .dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath)
    // Unpack message from Firebase DataSnapshot
    let messageSnapshot: DataSnapshot! = self.messages[indexPath.row]
    guard let message = messageSnapshot.value as? [String:String] else { return cell }
    let name = message[Constants.MessageFields.name] ?? ""
    if let imageURL = message[Constants.MessageFields.imageURL] {
      if imageURL.hasPrefix("gs://") {
        Storage.storage().reference(forURL: imageURL).getData(maxSize: INT64_MAX) {(data, error) in
          if let error = error {
            print("Error downloading: \(error)")
            return
          }
          DispatchQueue.main.async {
            cell.imageView?.image = UIImage.init(data: data!)
            cell.setNeedsLayout()
          }
        }
      } else if let URL = URL(string: imageURL), let data = try? Data(contentsOf: URL) {
        cell.imageView?.image = UIImage.init(data: data)
      }
      cell.textLabel?.text = "sent by: \(name)"
    } else {
      let text = message[Constants.MessageFields.text] ?? ""
      cell.textLabel?.text = name + ": " + text
      cell.imageView?.image = UIImage(named: "ic_account_circle")
      if let photoURL = message[Constants.MessageFields.photoURL], let URL = URL(string: photoURL),
          let data = try? Data(contentsOf: URL) {
        cell.imageView?.image = UIImage(data: data)
      }
    }
    return cell
  }

9. 傳送圖片訊息

實作儲存及傳送圖片

從使用者上傳圖片,然後將這張圖片的儲存空間網址同步到資料庫,讓圖片傳到郵件中。

修改 FCViewController 的「imagePickerController: doFinishPickingMediaWithInfo::方法;換成下方定義的程式碼:

FCViewController.swift

  func imagePickerController(_ picker: UIImagePickerController,
    didFinishPickingMediaWithInfo info: [String : Any]) {
      picker.dismiss(animated: true, completion:nil)
    guard let uid = Auth.auth().currentUser?.uid else { return }

    // if it's a photo from the library, not an image from the camera
    if #available(iOS 8.0, *), let referenceURL = info[UIImagePickerControllerReferenceURL] as? URL {
      let assets = PHAsset.fetchAssets(withALAssetURLs: [referenceURL], options: nil)
      let asset = assets.firstObject
      asset?.requestContentEditingInput(with: nil, completionHandler: { [weak self] (contentEditingInput, info) in
        let imageFile = contentEditingInput?.fullSizeImageURL
        let filePath = "\(uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000))/\((referenceURL as AnyObject).lastPathComponent!)"
        guard let strongSelf = self else { return }
        strongSelf.storageRef.child(filePath)
          .putFile(from: imageFile!, metadata: nil) { (metadata, error) in
            if let error = error {
              let nsError = error as NSError
              print("Error uploading: \(nsError.localizedDescription)")
              return
            }
            strongSelf.sendMessage(withData: [Constants.MessageFields.imageURL: strongSelf.storageRef.child((metadata?.path)!).description])
          }
      })
    } else {
      guard let image = info[UIImagePickerControllerOriginalImage] as? UIImage else { return }
      let imageData = UIImageJPEGRepresentation(image, 0.8)
      let imagePath = "\(uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000)).jpg"
      let metadata = StorageMetadata()
      metadata.contentType = "image/jpeg"
      self.storageRef.child(imagePath)
        .putData(imageData!, metadata: metadata) { [weak self] (metadata, error) in
          if let error = error {
            print("Error uploading: \(error)")
            return
          }
          guard let strongSelf = self else { return }
          strongSelf.sendMessage(withData: [Constants.MessageFields.imageURL: strongSelf.storageRef.child((metadata?.path)!).description])
      }
    }
  }

測試傳送及接收圖片訊息

  1. 按一下 98205811bbed9d74.png「Run」(執行) 按鈕。
  2. 按一下 [登入],前往郵件視窗。
  3. 按一下「新增相片」圖示來挑選相片。應用程式 UI 和 Firebase 控制台中應該會顯示含有相片的新訊息。

10. 恭喜!

您已使用 Firebase 輕鬆建構即時聊天應用程式。

涵蓋內容

  • 即時資料庫
  • 聯合登入
  • 儲存空間

瞭解詳情