Firebase iOS 程式碼研究室 Swift

1. 總覽

2efe6805ef369641.png

歡迎參加 Friendly Chat 程式碼研究室。在本程式碼研究室中,您將瞭解如何使用 Firebase 平台建立 iOS 應用程式。您將實作即時通訊用戶端,並使用 Firebase 監控其效能。

課程內容

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

軟硬體需求

  • 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按鈕。

幾秒後,您應該會看到 Friendly Chat 主畫面。畫面上應會顯示 UI。不過,此時您無法登入、收發郵件。在完成下一個步驟之前,應用程式會因例外狀況而中止。

4. 設定 Firebase 專案

建立新的 Firebase 專案

  1. 使用 Google 帳戶登入 Firebase 控制台
  2. 按一下按鈕建立新專案,然後輸入專案名稱 (例如 FriendlyChat)。
  3. 按一下「繼續」
  4. 如果系統提示,請詳閱並接受 Firebase 條款,然後按一下「繼續」
  5. (選用) 在 Firebase 控制台中啟用 AI 輔助功能 (稱為「Gemini in Firebase」)。
  6. 本程式碼研究室不需要 Google Analytics,因此請關閉 Google Analytics 選項。
  7. 按一下「建立專案」,等待專案佈建完成,然後按一下「繼續」

升級 Firebase 定價方案

如要使用 Cloud Storage for Firebase,Firebase 專案必須採用即付即用 (Blaze) 定價方案,也就是連結至 Cloud Billing 帳戶

  • Cloud Billing 帳戶需要付款方式,例如信用卡。
  • 如果您剛開始使用 Firebase 和 Google Cloud,請確認是否符合 $300 美元抵免額和免費試用 Cloud Billing 帳戶的資格。
  • 如果您是在活動中進行這項程式碼研究室,請詢問主辦單位是否有可用的 Cloud 抵免額。

如要將專案升級至 Blaze 方案,請按照下列步驟操作:

  1. 在 Firebase 控制台中,選取「升級方案」
  2. 選取 Blaze 方案。按照畫面上的指示,將 Cloud Billing 帳戶連結至專案。
    如果你在升級過程中需要建立 Cloud Billing 帳戶,可能需要返回 Firebase 控制台的升級流程,才能完成升級。

連結 iOS 應用程式

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

將 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 函式中,使用 FirebaseApp 內的「configure」方法,從 .plist 檔案設定基礎 Firebase 服務。

AppDelegate.swift

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

5. 識別使用者

使用規則限制只有已驗證的使用者可以存取

現在我們要新增規則,規定必須先驗證身分,才能讀取或寫入任何訊息。為此,我們會在訊息資料物件中加入下列規則。在 Firebase 主控台的「資料庫」專區中,選取「Realtime Database」,然後按一下「規則」分頁標籤。然後更新規則,使其如下所示:

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

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

設定驗證 API

應用程式必須先啟用這項功能,才能代表使用者存取 Firebase 驗證 API

  1. 前往 Firebase 控制台,然後選取專案
  2. 選取「驗證」
  3. 選取「登入方式」分頁標籤
  4. 將「Google」切換鈕設為啟用 (藍色)
  5. 在結果對話方塊中按一下「儲存」

如果在本程式碼研究室的後續步驟中發生錯誤,並收到「CONFIGURATION_NOT_FOUND」訊息,請返回這個步驟,仔細檢查您的工作。

確認 Firebase Auth 依附元件

確認 Podfile 檔案中是否有 Firebase Auth 依附元件。

Podfile

pod 'Firebase/Auth'

設定 Info.plist,以便使用 Google 登入。

您需要在 XCode 專案中新增自訂網址架構。

  1. 開啟專案設定:在左側樹狀檢視中按兩下專案名稱。在「目標」部分選取應用程式,然後選取「資訊」分頁,並展開「網址類型」部分。
  2. 按一下「+」按鈕,然後為反向用戶端 ID 新增網址架構。如要找出這個值,請開啟 GoogleService-Info.plist 設定檔,然後尋找 REVERSED_CLIENT_ID 金鑰。複製該金鑰的值,然後貼到設定頁面的「URL Schemes」(網址架構) 方塊中。將其他欄位留空。
  3. 完成後,您的設定應如下所示 (但會使用應用程式專屬值):

1b54d5bd2f4f1448.png

設定 Google 登入的 clientID

設定 Firebase 後,我們可以使用 clientID 在「didFinishLaunchingWithOptions:」方法中設定 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按鈕。
  2. 系統應會立即將你帶往登入畫面。輕觸「使用 Google 帳戶登入」按鈕。
  3. 如果一切正常,系統會將你導向訊息畫面。

6. 啟用即時資料庫

2efe6805ef369641.png

匯入訊息

Firebase 主控台的專案中,選取左側導覽列的「資料庫」項目。在資料庫的溢位選單中,選取「Import JSON」。瀏覽至 friendlychat 目錄中的 initial_messages.json 檔案,選取該檔案,然後按一下「匯入」按鈕。這會取代資料庫中的所有現有資料。您也可以直接編輯資料庫,使用綠色 + 和紅色 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按鈕。
  2. 按一下「登入以開始使用」按鈕,前往訊息視窗。
  3. 直接在 Firebase 控制台中新增訊息,方法是按一下「messages」項目旁的綠色 + 符號,然後新增類似下列的物件:f9876ffc8b316b14.png
  4. 確認這些訊息是否顯示在 Friendly-Chat UI 中。

7. 傳送訊息

導入 Send Message

將值推送到資料庫。使用 push 方法將資料新增至 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按鈕。
  2. 按一下「登入」前往訊息視窗。
  3. 輸入訊息內容,然後按「傳送」。應用程式 UI 和 Firebase 控制台應會顯示新訊息。

8. 儲存及接收圖片

確認 Firebase Storage 依附元件

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

Podfile

pod 'Firebase/Storage'

設定 Cloud Storage for Firebase

如要在 Firebase 專案中設定 Cloud Storage for Firebase,請按照下列步驟操作:

  1. 在 Firebase 主控台的左側面板中,展開「Build」,然後選取「Storage」
  2. 按一下「開始使用」
  3. 選取預設 Storage bucket 的位置。
    位於 US-WEST1US-CENTRAL1US-EAST1 的 bucket 可享有 Google Cloud Storage 的「永久免費」方案。其他所有位置的值區均適用 Google Cloud Storage 定價和用量
  4. 按一下「以測試模式啟動」。請詳閱安全性規則免責事項。
    在本程式碼研究室的後續步驟中,您將新增安全性規則來保護資料。請勿在未為 Storage 值區新增安全規則的情況下,公開發布或公開應用程式。
  5. 點選「建立」

設定 FirebaseStorage

FCViewController.swift

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

在現有訊息中接收圖片

新增從 Firebase Storage 下載圖片的程式碼。

修改 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: didFinishPickingMediaWithInfo:」方法,並替換為下列程式碼:

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按鈕。
  2. 按一下「登入」前往訊息視窗。
  3. 按一下「新增相片」圖示,然後選擇相片。應用程式 UI 和 Firebase 控制台應會顯示附有相片的新訊息。

10. 恭喜!

您已使用 Firebase 輕鬆建構即時通訊應用程式。

涵蓋內容

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

瞭解詳情