一、概述
歡迎來到 Friendly Chat 代碼實驗室。在此 Codelab 中,您將學習如何使用 Firebase 平台創建 iOS 應用程序。您將實現一個聊天客戶端並使用 Firebase 監控其性能。
你會學到什麼
- 允許用戶登錄。
- 使用 Firebase 實時數據庫同步數據。
- 將二進製文件存儲在 Firebase 存儲中。
你需要什麼
- Xcode
- 可可豆
- 帶有 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 中打開項目。
- 點擊
運行按鈕。
幾秒鐘後,您應該會看到 Friendly Chat 主屏幕出現。用戶界面應該出現。但是,此時您無法登錄、發送或接收消息。該應用程序將異常中止,直到您完成下一步。
4. 創建 Firebase 控制台項目
創建項目
從Firebase 控制台選擇Add Project 。
調用項目FriendlyChat
,然後單擊Create Project 。
連接您的 iOS 應用
- 在新項目的項目概覽屏幕中,點擊將 Firebase 添加到您的 iOS 應用程序。
- 輸入包 ID,如“
com.google.firebase.codelab.FriendlyChatSwift
”。 - 輸入 App Store id 作為“
123456
”。 - 單擊註冊應用程序。
將 GoogleService-Info.plist 文件添加到您的應用
在第二個屏幕上,點擊下載 GoogleService-Info.plist以下載一個配置文件,其中包含您的應用所需的所有 Firebase 元數據。將該文件複製到您的應用程序並將其添加到FriendlyChatSwift目標。
您現在可以單擊彈出窗口右上角的“x”將其關閉——跳過第 3 步和第 4 步——因為您將在此處執行這些步驟。
導入 Firebase 模塊
首先確保已導入Firebase
模塊。
AppDelegate.swift , FCViewController.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 控制台的數據庫部分中選擇實時數據庫,然後單擊規則選項卡。然後更新規則,使它們看起來像這樣:
{
"rules": {
"messages": {
".read": "auth != null",
".write": "auth != null"
}
}
}
有關其工作原理的更多信息(包括有關“auth”變量的文檔),請參閱 Firebase安全文檔。
配置身份驗證 API
在您的應用程序可以代表您的用戶訪問 Firebase 身份驗證 API 之前,您必須啟用它
- 導航到Firebase 控制台並選擇您的項目
- 選擇認證
- 選擇登錄方法選項卡
- 將Google開關切換為啟用(藍色)
- 在出現的對話框中按保存
如果您稍後在此 Codelab 中收到消息“CONFIGURATION_NOT_FOUND”的錯誤,請返回此步驟並仔細檢查您的工作。
確認 Firebase Auth 依賴
確認Podfile
文件中存在 Firebase Auth 依賴項。
播客文件
pod 'Firebase/Auth'
為 Google 登錄設置您的 Info.plist。
您需要將自定義 URL 方案添加到您的 XCode 項目中。
- 打開您的項目配置:雙擊左側樹視圖中的項目名稱。從 TARGETS 部分選擇您的應用程序,然後選擇 Info 選項卡,並展開 URL Types 部分。
- 單擊 + 按鈕,並為您的反向客戶端 ID 添加 URL 方案。要查找此值,請打開 GoogleService-Info.plist 配置文件,然後查找 REVERSED_CLIENT_ID 鍵。複製該鍵的值,並將其粘貼到配置頁面上的 URL Schemes 框中。將其他字段留空。
- 完成後,您的配置應類似於以下內容(但具有特定於應用程序的值):
為 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 上刪除監聽器。
登錄視圖控制器.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)")
}
}
以登錄用戶身份測試閱讀消息
- 點擊
運行按鈕。
- 您應該立即被發送到登錄屏幕。點擊谷歌登錄按鈕。
- 如果一切正常,您應該會被發送到消息屏幕。
6.激活實時數據庫
導入消息
在Firebase 控制台的項目中,選擇左側導航欄上的數據庫項。在數據庫的溢出菜單中選擇Import JSON 。瀏覽到 friendlychat 目錄中的initial_messages.json
文件,選擇它然後單擊“導入”按鈕。這將替換當前數據庫中的所有數據。您還可以直接編輯數據庫,使用綠色 + 和紅色 x 添加和刪除項目。
導入數據庫後應如下所示:
確認 Firebase 數據庫依賴性
在Podfile
文件的依賴項塊中,確認包含Firebase/Database
。
播客文件
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
}
測試消息同步
- 點擊
運行按鈕。
- 單擊“登錄以開始”按鈕轉到消息窗口。
- 通過單擊“消息”條目旁邊的綠色 + 符號並添加如下所示的對象,直接在 Firebase 控制台中添加新消息:
- 確認他們出現在 Friendly-Chat UI 中。
7. 發送消息
實現發送消息
將值推送到數據庫。當您使用 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)
}
測試發送消息
- 點擊
運行按鈕。
- 單擊“登錄”轉到消息窗口。
- 輸入消息並點擊發送。新消息應該在應用程序用戶界面和 Firebase 控制台中可見。
8.存儲和接收圖像
確認 Firebase 存儲依賴
在Podfile
的依賴項塊中,確認包含Firebase/Storage
。
播客文件
pod 'Firebase/Storage'
在儀表板中激活 Firebase 存儲
轉到 Firebase 控制台並確認已使用“gs://PROJECTID.appspot.com”域激活存儲
如果您看到的是激活窗口,請單擊“開始”以使用默認規則激活它。
配置 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.發送圖像信息
實施存儲和發送圖像
從用戶上傳圖像,然後將此圖像的存儲 URL 同步到數據庫,以便在消息中發送此圖像。
修改你的 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])
}
}
}
測試發送和接收圖像消息
- 點擊
運行按鈕。
- 單擊“登錄”轉到消息窗口。
- 單擊“添加照片”圖標選擇一張照片。帶有照片的新消息應該在應用程序用戶界面和 Firebase 控制台中可見。
10. 恭喜!
您已經使用 Firebase 輕鬆構建了一個實時聊天應用程序。
我們涵蓋的內容
- 實時數據庫
- 聯合登錄
- 貯存