Firebase iOS Codelab Swift

1. 概览

2efe6805ef369641.png

欢迎学习 Friendly Chat Codelab。在此 Codelab 中,您将学习如何使用 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 主屏幕。界面应随即显示。不过,此时您无法登录、发送或接收邮件。在完成后续步骤之前,应用将因出现异常而中止。

4. 设置 Firebase 项目

创建新的 Firebase 项目

  1. 使用您的 Google 账号登录 Firebase 控制台
  2. 点击相应按钮以创建新项目,然后输入项目名称(例如 FriendlyChat)。
  3. 点击继续
  4. 如果看到相关提示,请查看并接受 Firebase 条款,然后点击继续
  5. (可选)在 Firebase 控制台中启用 AI 辅助功能(称为“Gemini in Firebase”)。
  6. 在此 Codelab 中,您不需要使用 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. 在新建项目的“项目概览”界面中,点击将 Firebase 添加到您的 iOS 应用
  2. 输入软件包 ID,例如“com.google.firebase.codelab.FriendlyChatSwift”。
  3. 输入 App Store ID,格式为“123456”。
  4. 点击注册应用

将 GoogleService-Info.plist 文件添加到您的应用

在第二个界面上,点击下载 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 Authentication API 之前,您必须先启用该 API

  1. 前往 Firebase 控制台,然后选择您的项目
  2. 选择身份验证
  3. 选择登录方法标签页
  4. Google 开关切换为启用状态(蓝色)
  5. 按相应对话框中的保存

如果您在本 Codelab 的后续步骤中遇到“CONFIGURATION_NOT_FOUND”错误消息,请返回此步骤并仔细检查您的工作。

确认 Firebase Auth 依赖项

确认 Podfile 文件中存在 Firebase Auth 依赖项。

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运行按钮。
  2. 系统应立即将您转到登录界面。点按“使用 Google 账号登录”按钮。
  3. 如果一切正常,您应该会进入消息界面。

6. 激活 Realtime Database

2efe6805ef369641.png

导入消息

Firebase 控制台中,选择左侧导航栏中的数据库项。在数据库的溢出菜单中,选择导入 JSON。在 friendlychat 目录中找到 initial_messages.json 文件,选择该文件,然后点击导入按钮。这会替换数据库中当前的所有数据。您还可以直接修改数据库,使用绿色加号和红色叉号添加和移除商品。

20ccf4856b715b4c.png

导入后,您的数据库应如下所示:

f3e0367f1c9cd187.png

确认 Firebase 数据库依赖项

Podfile 文件的依赖项代码块中,确认是否包含 Firebase/Database

Podfile

pod 'Firebase/Database'

同步现有消息

添加可将新添加的消息同步到应用界面的代码。

您在此部分中添加的代码将:

  • 初始化 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 控制台中添加新消息,并添加如下对象:f9876ffc8b316b14.png
  4. 确认它们显示在 Friendly-Chat 界面中。

7. 发送消息

实现“发送消息”功能

将值推送到数据库。使用 push 方法将数据添加到 Firebase Realtime Database 时,系统会自动添加 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. 输入消息,然后点击“发送”。新消息应显示在应用界面和 Firebase 控制台中。

8. 存储和接收图片

确认 Firebase Storage 依赖项

Podfile 的依赖项代码块中,确认是否包含 Firebase/Storage

Podfile

pod 'Firebase/Storage'

设置 Cloud Storage for Firebase

以下是在 Firebase 项目中设置 Cloud Storage for Firebase 的方法:

  1. 在 Firebase 控制台的左侧面板中,展开构建,然后选择存储
  2. 点击开始使用
  3. 为默认存储分区选择位置。
    US-WEST1US-CENTRAL1US-EAST1 中的存储分区可为 Google Cloud Storage 使用“始终免费”层级。所有其他位置的存储分区都遵循 Google Cloud Storage 价格和用量
  4. 点击以测试模式开始。阅读有关安全规则的免责声明。
    在本 Codelab 的后面部分,您将添加安全规则来保护您的数据。在未为您的存储桶添加安全规则的情况下,请不要公开分发或公开应用。
  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. 点击“添加照片”图标以选择照片。包含照片的新消息应显示在应用界面和 Firebase 控制台中。

10. 恭喜!

您已使用 Firebase 轻松构建了一个实时聊天应用。

所学内容

  • Realtime Database
  • 联合登录
  • 存储

了解详情