Firebase iOS Codelab Swift

1. 概览

2efe6805ef369641

欢迎学习 Free Chat Codelab。在此 Codelab 中,您将学习如何使用 Firebase 平台创建 iOS 应用。您将实现一个聊天客户端,并使用 Firebase 监控其性能。

学习内容

  • 允许用户登录。
  • 使用 Firebase Realtime Database 同步数据。
  • 在 Firebase 存储中存储二进制文件。

所需条件

  • Xcode
  • CocoaPods
  • 搭载 iOS 8.0+ 或模拟器的测试设备

您将如何使用本教程?

仅阅读教程内容 阅读并完成练习

您如何评价自己在构建 iOS 应用方面的经验水平?

新手 中等 熟练

2. 获取示例代码

从命令行克隆 GitHub 代码库。

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

3. 构建起始应用

2f4c98d858c453fe

如需构建起始应用,请执行以下操作:

  1. 在终端窗口中,从下载的示例代码导航到 android_studio_folder.pngios-starter/swift-starter 目录
  2. 运行:pod install --repo-update
  3. 打开 FreeChatSwift.xcworkspace 文件以在 Xcode 中打开项目。
  4. 点击 98205811bbed9d74Run 按钮。

您应该会在几秒钟后看到 Free Chat 主屏幕。界面应会出现。但是,此时您无法登录,发送或接收邮件。在您完成下一步之前,应用将中止,但出现异常。

4.创建 Firebase 控制台项目

创建项目

Firebase 控制台中,选择添加项目

调用项目 FriendlyChat,然后点击 Create Project

2015-11-06 14:13:39 的屏幕截图.png

关联您的 iOS 应用

  1. 在新项目的“Project Overview”屏幕中,点击将 Firebase 添加到您的 iOS 应用
  2. 输入软件包 ID,例如“com.google.firebase.codelab.FriendlyChatSwift”。
  3. 请输入“123456”作为 App Store ID。
  4. 点击注册应用

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

在第二个屏幕上,点击下载 GoogleService-Info.plist,下载包含应用所需的所有 Firebase 元数据的配置文件。将该文件复制到您的应用中,然后将其添加到 RelationChatSwift 目标中。

现在,您可以跳过第 3 步和第 4 步,点击弹出式窗口右上角的“x”将其关闭,因为您将在这里执行这些步骤。

19d59efb213ddbdc.png

导入 Firebase 模块

首先,确保已导入 Firebase 模块。

AppDelegate.swiftFCViewController.swift

import Firebase

在 AppDelegate 中配置 Firebase

使用 FirebaseApp 中的 application:didFinishLaunchingWithOptions 函数中的“configure”方法通过 .plist 文件配置底层 Firebase 服务。

AppDelegate.swift

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

5. 识别用户

使用规则限制为仅通过身份验证的用户访问

我们现在将添加一条规则,要求在读取或写入邮件之前进行身份验证。为此,我们需要向消息数据对象添加以下规则。在 Firebase 控制台的“Database”部分中,选择“Realtime Database”,然后点击“规则”标签页。然后更新规则,使其如下所示:

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

如需详细了解相关工作原理(包括有关“auth”变量的文档),请参阅 Firebase 安全文档

配置 Authentication API

您必须先启用应用,然后才能代表用户访问 Firebase Authentication API

  1. 导航到 Firebase 控制台并选择您的项目
  2. 选择身份验证
  3. 选择登录方法标签页
  4. Google 开关切换为启用(蓝色)
  5. 在出现的对话框中按 Save

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

确认 Firebase Auth 依赖项

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

Podfile

pod 'Firebase/Auth'

设置 Info.plist 以实现 Google 登录

您需要为 XCode 项目添加一个自定义网址架构。

  1. 打开您的项目配置:在左侧的树状视图中双击项目名称。在“目标”部分中选择您的应用,然后选择“信息”标签页,并展开“网址类型”部分。
  2. 点击 + 按钮,并为您的倒序客户端 ID 添加一个网址方案。要找到这个值,请打开 GoogleService-Info.plist 配置文件,然后查找 REVERSED_CLIENT_ID 键。复制该键的值,并将其粘贴到配置页面上的“网址方案”框中。将其他字段留空。
  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)
    }
  }

退出帐号

添加 Sign out 方法

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. 点击 98205811bbed9d74Run 按钮。
  2. 系统应该会立即将您转到登录屏幕。点按 Google 登录按钮。
  3. 如果一切正常,您应该会进入消息界面。

6. 激活 Realtime Database

2efe6805ef369641

导入邮件

Firebase 控制台的项目中,选择左侧导航栏上的 Database 项。在数据库的溢出菜单中,选择导入 JSON。浏览到 friendlychat 目录中的 initial_messages.json 文件,选择该文件,然后点击 Import 按钮。这将替换您的数据库中当前的所有数据。您也可以使用绿色 + 和红色 x 来添加和移除项,从而直接修改数据库。

20ccf4856b715b4c

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

F3e0367f1c9cd187

确认 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. 点击 98205811bbed9d74Run 按钮。
  2. 点击登录即可开始使用按钮,转到消息窗口。
  3. 您可以直接在 Firebase 控制台中添加新消息,方法是点击“messages”条目旁边的绿色 + 符号,然后添加一个如下所示的对象:f9876ffc8b316b14
  4. 确认它们是否显示在 Friends-Chat 界面中。

7. 发送消息

实现发送消息

将值推送到数据库。当您使用推送方法向 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. 点击 98205811bbed9d74Run 按钮。
  2. 点击登录即可转到消息窗口。
  3. 输入消息,然后点击“发送”。新消息应显示在应用界面和 Firebase 控制台中。

8. 存储和接收图片

确认 Firebase 存储依赖项

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

Podfile

pod 'Firebase/Storage'

在信息中心内激活 Firebase Storage

转到 Firebase 控制台,确认已使用“gs://PROJECTID.appspot.com”网域激活 Storage

b0438b37a588bcee

如果您看到的是激活窗口,请点击“开始使用”以使用默认规则激活该窗口。

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: 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. 点击 98205811bbed9d74Run 按钮。
  2. 点击登录即可转到消息窗口。
  3. 点击“添加照片”图标选择一张照片。应用界面和 Firebase 控制台中应该会显示包含照片的新消息。

10. 恭喜!

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

所学内容

  • Realtime Database
  • 联合登录
  • 存储

了解详情