Firebase iOS 程式碼研究室:FriendlyChat

1. 總覽

歡迎參加 iOS 版 FriendlyChat 程式碼研究室!在本程式碼研究室中,您將瞭解如何使用 Firebase 和新式 Swift 功能 (例如 SwiftUI、Swift 並行和 Observation 架構),建構功能齊全的即時聊天 iOS 應用程式。

課程內容

  • 使用新式觀察架構 (@Observable) 管理狀態。
  • 使用 Firebase Authentication 驗證使用者。
  • 使用 Cloud Firestore 即時同步處理即時通訊訊息。
  • 使用 Cloud Storage for Firebase 儲存及提供圖片。
  • 使用新版 Swift 並行功能存取 Firebase API。

事前準備

  • 最新版 Xcode
  • iOS 模擬器或實體測試裝置
  • 可建立及管理 Firebase 專案的 Google 帳戶

2. 取得程式碼範例

從指令列複製 GitHub 存放區:

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

codelab-friendlychat-ios 存放區包含多個範例專案:

  • ios-starter/swift-starter:起始 Xcode 專案,內含 SwiftUI 版面配置架構和 TODO 註解,您將使用 Firebase 實作應用程式功能。
  • ios/swift:完成的 Xcode 專案,內含完成的範例應用程式。

3. 建構範例應用程式

建構並執行入門應用程式,熟悉使用者介面:

  1. 在 Finder 或終端機中,前往 ios-starter/swift-starter 目錄。
  2. 按兩下 FriendlyChatSwift.xcodeproj,在 Xcode 中開啟專案。
  3. 請注意,Xcode 會在背景自動解析 Swift Package Manager 依附元件 (從 GitHub 下載 firebase-ios-sdk v12 以上版本)。
  4. 選取 iOS 模擬器 (例如「iPhone 17 Pro」),然後按一下「執行」按鈕 (cmd+R)。

幾秒後,畫面上會顯示 FriendlyChat 登入畫面。此時,登入按鈕和訊息動態饋給是預留位置。您將在下列步驟中將 Firebase 連線至應用程式。

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 帳戶的資格。
  • 如果您是在活動中進行這項 Codelab,請向主辦單位詢問是否有可用的 Cloud 抵免額。

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

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

連結您的 iOS 應用程式

  1. 在「專案總覽」畫面中,按一下「iOS」圖示,啟動應用程式設定工作流程。
  2. 輸入軟體包 ID:
    com.google.firebase.codelab.FriendlyChatSwift
    
  3. 按一下 [Register app] (註冊應用程式)

GoogleService-Info.plist 新增至 Xcode 專案

  1. 按一下「Download GoogleService-Info.plist」儲存設定檔。
  2. 在 Xcode 中,將下載的 GoogleService-Info.plist 檔案拖曳至「Project Navigator」中的 FriendlyChatSwift 主要群組。
  3. 系統提示時,請確認已勾選「Copy items if needed」,並選取 FriendlyChatSwift 目標,然後按一下「Finish」

FriendlyChatSwiftApp.swift 中設定 Firebase

最後,在 SwiftUI 應用程式啟動時,使用專案設定初始化 Firebase。開啟 FriendlyChatSwiftApp.swift,並將 TODO 預留位置替換為 import FirebaseCoreFirebaseApp.configure()

import SwiftUI
import FirebaseCore

@main
struct FriendlyChatSwiftApp: App {
  init() {
    FirebaseApp.configure()
  }

  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

5. 驗證使用者

在 Firebase 控制台中設定驗證

您必須先在 Firebase 主控台中啟用所選的驗證供應商,才能驗證使用者。本教學課程使用電子郵件地址/密碼驗證:

  1. 在 Firebase 控制台中,依序前往「安全性」 >「驗證」,然後點選「開始使用」
  2. 選取「登入方式」分頁標籤。
  3. 按一下「電子郵件/密碼」,然後點選「啟用」切換鈕,並按一下「儲存」

使用驗證規則保護 Cloud Firestore

接著,要求使用者先通過驗證,才能讀取或寫入訊息:

  1. 在 Firebase 控制台中,依序前往「資料庫和儲存空間」>「Firestore 資料庫」,然後點選「建立資料庫」
  2. 選取位置,然後按一下「下一步」
  3. 選取「以測試模式啟動」,然後點選「建立」
  4. 選取「規則」分頁標籤,然後將安全性規則更新為:
    rules_version = '2';
    
    service cloud.firestore {
      match /databases/{database}/documents {
        match /messages/{messageId} {
          allow read, write: if request.auth != null;
        }
      }
    }
    
    這些規則允許任何通過驗證的使用者讀取或寫入資料庫中的訊息,適合用於學習。如要進一步瞭解安全性規則,請參閱安全性規則說明文件
  5. 按一下「發布」。

UserViewModel.swift 中觀察驗證狀態

在 SwiftUI 中,觀察 Firebase Auth 狀態可確保使用者介面在登入畫面和即時通訊介面之間順暢切換。

開啟 ViewModels/UserViewModel.swift,並取代 init() 中的 TODO,使用 authStateChanges AsyncSequence 觀察驗證狀態變化:

  init() {
    authTask = Task {
      for await user in Auth.auth().authStateChanges {
        self.user = user
      }
    }
  }

使用 Swift 並行功能實作登入、註冊和登出功能

UserViewModel.swift 中,將其餘 TODO 註解替換為新版 async/await 驗證方法:

  func signIn(email: String, password: String) async {
    errorMessage = nil
    if email.isEmpty || password.isEmpty {
      showError("Please enter both email and password.")
      return
    }
    do {
      try await Auth.auth().signIn(withEmail: email, password: password)
    } catch {
      showError(error.localizedDescription)
    }
  }

  func signUp(email: String, password: String, displayName: String) async {
    errorMessage = nil
    if email.isEmpty || password.isEmpty {
      showError("Please enter both email and password.")
      return
    }
    do {
      let result =
        try await Auth.auth().createUser(withEmail: email, password: password)
      let changeRequest = result.user.createProfileChangeRequest()
      changeRequest.displayName = displayName.isEmpty ? email : displayName
      try await changeRequest.commitChanges()
      self.user = Auth.auth().currentUser
    } catch {
      showError(error.localizedDescription)
    }
  }

  func updateDisplayName(_ displayName: String) async {
    guard let currentUser = Auth.auth().currentUser else { return }
    do {
      let changeRequest = currentUser.createProfileChangeRequest()
      changeRequest.displayName = displayName
      try await changeRequest.commitChanges()
      self.user = Auth.auth().currentUser
    } catch {
      showError(error.localizedDescription)
    }
  }

  func signOut() {
    do {
      try Auth.auth().signOut()
    } catch {
      showError(error.localizedDescription)
    }
  }

測試使用者驗證

  1. 按一下 Xcode 中的「Run」按鈕。
  2. 在應用程式模擬器中,按一下「Sign Up」(註冊),輸入顯示名稱、電子郵件地址和密碼,然後輕觸「SIGN UP」(註冊)
  3. 系統應會自動驗證您的身分,並將您導向空白訊息畫面!

6. 從 Cloud Firestore 讀取

在 Firebase 控制台中新增範例訊息

將範例訊息填入資料庫:

  1. 在 Firebase 控制台中,前往「Firestore Database」,然後選取「Data」分頁標籤。
  2. 按一下「開始收集」
  3. 輸入「Collection ID」messages,然後按一下「Next」
  4. 將「文件 ID」設為自動產生 (或按一下「自動產生的 ID」)。
  5. 在文件中新增下列欄位:
    • text (類型:string,值:Hello)
    • displayName (類型:string,值:anonymous)
  6. 按一下 [儲存]
  7. 如要新增更多範例訊息,請按一下「新增文件」

同步處理「FriendlyMessageViewModel.swift」中的訊息

在 SwiftUI 中,@Observable 模型會驅動宣告式 UI 更新。開啟 ViewModels/FriendlyMessageViewModel.swift,並使用 snapshots AsyncSequence 實作 startListening()stopListening()

  func startListening() {
    stopListening()
    listenerTask = Task {
      let db = Firestore.firestore()
      do {
        for try await snapshot in db.collection("messages").snapshots {
          self.messages = snapshot.documents.compactMap { document in
            try? document.data(as: FriendlyMessage.self)
          }
        }
      } catch {
        print("Error listening for messages: \(error)")
      }
    }
  }

  func stopListening() {
    listenerTask?.cancel()
    listenerTask = nil
    messages.removeAll()
  }

SwiftUI 如何算繪及自動捲動訊息

Views/ContentView.swift 中,請注意 ScrollViewReaderLazyVStack 如何算繪即時通訊泡泡,以及每當 messages.count 變更時,如何自動捲動至最新訊息:

ScrollViewReader { scrollViewReader in
  ScrollView {
    LazyVStack(spacing: 12) {
      ForEach(messageViewModel.messages) { message in
        FriendlyMessageView(friendlyMessage: message)
          .id(message.id)
      }
    }
    .padding(.horizontal)
    .onChange(of: messageViewModel.messages.count) { _, count in
      guard count > 0,
            let lastId = messageViewModel.messages.last?.id else { return }
      withAnimation(.easeInOut) {
        scrollViewReader.scrollTo(lastId, anchor: .bottom)
      }
    }
  }
}

透過檢視畫面模型將資料庫狀態直接繫結至 UI 狀態,SwiftUI 可讓應用程式程式碼清楚地將轉場效果對應至動畫。在下一個步驟中,您將從應用程式將資料寫入資料庫,觀察這個動畫的實際運作情形。

7. 傳送訊息

使用 Swift 並行處理實作 sendMessage

使用 addDocument() 將文件新增至集合時,Cloud Firestore 會為每則即時通訊訊息產生專屬 ID。

ViewModels/FriendlyMessageViewModel.swift 中,將 sendMessage(text:imageUrl:) 中的 TODO 替換為 async throws 方法:

  func sendMessage(text: String?, imageUrl: String?) async throws {
    guard let currentUser = Auth.auth().currentUser else { return }
    let message = FriendlyMessage(
      text: text,
      displayName: currentUser.displayName ?? currentUser.email ?? "Anonymous",
      imageUrl: imageUrl,
      userId: currentUser.uid
    )
    let db = Firestore.firestore()
    _ = try db.collection("messages").addDocument(from: message)
  }

測試傳送訊息

  1. 按一下 Xcode 中的「Run」按鈕。
  2. 登入您的帳戶。
  3. 在底部的文字欄位中輸入訊息,然後輕觸「傳送」圖示。
  4. 訊息會立即顯示在模擬器中!

8. 儲存及接收圖片

設定 Cloud Storage for Firebase

  1. 在 Firebase 控制台中,依序前往「資料庫和儲存空間」>「儲存空間」,然後點選「開始使用」
  2. 選取「以測試模式啟動」,然後點選「下一步」
  3. 接受預設儲存位置,然後按一下「完成」

使用 SwiftUI PhotosPicker 挑選原生圖片

新版 SwiftUI (iOS 16 以上版本) 可透過 PhotosPickerimport PhotosUI 原生選取相片,不需要 UIKit 委派橋接器或 UIImagePickerController 包裝函式!

Views/FooterView.swift 中,觀察 PhotosPicker 如何繫結所選圖片:

PhotosPicker(selection: $selectedItem, matching: .images) {
  Image(systemName: "photo.on.rectangle.angled")
    .font(.system(size: 26))
    .foregroundStyle(.blue)
    .accessibilityLabel("Select photo")
}
.onChange(of: selectedItem) { _, newItem in
  Task {
    if let data = try? await newItem?.loadTransferable(type: Data.self) {
      await uploadAndSendImage(data: data)
    }
  }
}

實作圖片上傳功能

選取相片後,將圖片資料上傳至 Cloud Storage,並將公開下載網址儲存至 Cloud Firestore。

Views/FooterView.swift 中,將 uploadAndSendImage(data:) 中的 TODO 替換為:

  private func uploadAndSendImage(data: Data) async {
    guard let uid = Auth.auth().currentUser?.uid else { return }
    isUploading = true
    defer {
      isUploading = false
      selectedItem = nil
    }
    do {
      let filename = "\(uid)/\(UUID().uuidString).jpg"
      let storageRef =
        Storage.storage().reference().child("images").child(filename)
      let metadata = StorageMetadata()
      metadata.contentType = "image/jpeg"

      _ = try await storageRef.putDataAsync(data, metadata: metadata)
      let downloadURL = try await storageRef.downloadURL()
      try await viewModel.sendMessage(
        text: nil, imageUrl: downloadURL.absoluteString)
    } catch {
      print("Error uploading image: \(error.localizedDescription)")
    }
  }

透過 Cloud Storage 分享圖片

Views/FriendlyMessageImageView.swift 中,系統會使用 Swift 並行和 Cloud Storage SDK 非同步載入圖片網址:

  • 標準 HTTP/HTTPS 下載網址會透過 URLSession 以非同步方式擷取。
  • 系統會使用 Storage.storage().reference(forURL:) 擷取 Google Cloud Storage 網址 (例如 gs://...)。

測試圖片訊息

  1. 按一下 Xcode 中的「Run」按鈕。
  2. 登入您的帳戶。
  3. 輕觸「相片」圖示,從圖庫中選擇圖片,然後等待上傳並顯示在對話中!

9. 結論

恭喜!您已使用 Swift 和 Firebase 成功建構 iOS 即時通訊應用程式!

目前所學內容

  • Swift Package Manager (SPM):適用於 firebase-ios-sdk 的 Xcode 整合。
  • SwiftUI 宣告式版面配置、PhotosPickerScrollViewReader 自動捲動。
  • Swift 並行 (async/await),適用於 Authentication、Cloud Firestore 和 Cloud Storage。
  • 電子郵件/密碼和匿名驗證,並觀察反應式狀態。
  • Cloud Firestore 即時快照監聽和自動 ID 寫入。
  • Cloud Storage for Firebase 二進位檔上傳和公開下載網址。

瞭解詳情