1.概览
欢迎学习 Friendly 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. 构建起始应用
如需构建起始应用,请执行以下操作:
- 在终端窗口中,从下载的示例代码导航到
ios-starter/swift-starter
目录 - 运行
pod install --repo-update
- 打开 FriendlyChatSwift.xcworkspace 文件以在 Xcode 中打开该项目。
- 点击 Run 按钮。
您应该会在几秒钟后看到 Entertainment Chat 主屏幕。系统应显示界面。但是,此时您无法登录、发送或接收邮件。在您完成下一步之前,应用将取消并抛出异常。
4. 创建 Firebase 控制台项目
创建项目
从 Firebase 控制台中,选择添加项目。
调用项目 FriendlyChat
,然后点击 Create Project。
关联您的 iOS 应用
- 在新项目的“Project Overview”屏幕中,点击将 Firebase 添加到您的 iOS 应用。
- 输入软件包 ID,例如“
com.google.firebase.codelab.FriendlyChatSwift
”。 - 输入“
123456
”作为 App Store ID。 - 点击注册应用。
将 GoogleService-Info.plist 文件添加到您的应用
在第二个屏幕上,点击下载 GoogleService-Info.plist,下载包含您的应用所需的所有 Firebase 元数据的配置文件。将该文件复制到您的应用,并将其添加到 FriendlyChatSwift 目标中。
现在,您可以点击“x”以关闭弹出式窗口(跳过第 3 步和第 4 步),因为此时您将执行这些步骤。
导入 Firebase 模块
首先,确保已导入 Firebase
模块。
AppDelegate.swift、FCViewController.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 控制台的“Database”(数据库)部分,选择“Realtime Database”,然后点击“Rules”标签页。然后更新规则,使其如下所示:
{
"rules": {
"messages": {
".read": "auth != null",
".write": "auth != null"
}
}
}
如需详细了解其工作原理(包括有关“auth”变量的文档),请参阅 Firebase 安全文档。
配置 Authentication API
您必须先启用 Firebase Authentication API,然后您的应用才能代表用户访问这些 API
- 前往 Firebase 控制台,选择您的项目
- 选择身份验证。
- 选择 Sign In Method(登录方法)标签页
- 将 Google 开关切换为“已启用”(蓝色)
- 在出现的对话框中按保存
如果您稍后在此 Codelab 中收到错误消息“CONFIGURATION_NOT_FOUND”,请返回此步骤并仔细检查您的操作。
确认 Firebase Auth 依赖项
确认 Podfile
文件中存在 Firebase Authentication 依赖项。
Podfile
pod 'Firebase/Auth'
设置您的 Info.plist 以便使用 Google 登录功能。
您需要为自己的 XCode 项目添加一个自定义网址架构。
- 打开您的项目配置:在左侧的树状视图中双击项目名称。从“目标”部分选择您的应用,然后选择“信息”标签,并展开“网址类型”部分。
- 点击 + 按钮,并为您的倒序客户端 ID 添加一个网址方案。要找到这个值,请打开 GoogleService-Info.plist 配置文件,然后查找 REVERSED_CLIENT_ID 键。复制该键的值,并将其粘贴到配置页面上的“网址架构”框中。将其他字段留空。
- 完成上述操作后,您的配置应显示如下(但其中的值应替换为您的应用的值):
设置用于 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)")
}
}
测试以已登录用户身份读取消息
- 点击 Run 按钮。
- 系统应该会立即将您转到登录屏幕。点按“Google 登录”按钮。
- 如果一切正常,您应该会转到消息屏幕。
6. 激活 Realtime Database
导入消息
在 Firebase 控制台的项目中,选择左侧导航栏上的数据库项。在数据库的溢出菜单中,选择导入 JSON。找到 FRIENDchat 目录中的 initial_messages.json
文件,选择该文件,然后点击 Import 按钮。这将替换您的数据库中当前存在的所有数据。您也可以直接修改数据库,使用绿色 + 和红色 x 添加和移除商品。
导入数据库后,您的数据库应如下所示:
确认 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
}
测试消息同步
- 点击 Run 按钮。
- 点击登录即可开始使用按钮,以转至消息窗口。
- 点击“消息”旁边的绿色“+”符号,直接在 Firebase 控制台中添加新消息条目并添加如下所示的对象:
- 确认它们显示在 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)
}
测试发送消息
- 点击 Run 按钮。
- 点击登录以转到消息窗口。
- 输入消息,然后点击“发送”。新消息应显示在应用界面和 Firebase 控制台中。
8. 存储和接收图片
确认 Firebase 存储依赖项
在 Podfile
的依赖项块中,确认是否已包含 Firebase/Storage
。
Podfile
pod 'Firebase/Storage'
在信息中心内激活 Firebase 存储
前往 Firebase 控制台并使用“gs://PROJECTID.appspot.com”确认已激活 Storage网域
如果您看到的是激活窗口,请点击“开始使用”即可按照默认规则启用它
配置 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])
}
}
}
测试发送和接收图片消息
- 点击 Run 按钮。
- 点击登录以转到消息窗口。
- 点击“添加照片”选择一张照片。包含照片的新消息应该会显示在应用界面和 Firebase 控制台中。
10. 恭喜!
您已使用 Firebase 轻松构建了一个实时聊天应用。
所学内容
- Realtime Database
- 联合登录
- 存储