1. 概要
フレンドリー チャットの Codelab へようこそ。この Codelab では、Firebase プラットフォームを使用して iOS アプリを作成する方法を学びます。Firebase を使用してチャット クライアントを実装し、そのパフォーマンスをモニタリングします。
ラボの内容
- ユーザーがログインできるようにします。
- Firebase Realtime Database を使用してデータを同期する。
- バイナリ ファイルを Firebase Storage に保存します。
必要なもの
- 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
- Xcode でプロジェクトを開くには、friendChatSwift.xcworkspace ファイルを開きます。
- [実行] ボタンをクリックします。
数秒後に、フレンドリー チャットのホーム画面が表示されます。UI が表示されます。ただし、現時点ではログインやメッセージの送受信はできません。次のステップを完了するまで、アプリは例外で中止されます。
4. Firebase コンソール プロジェクトを作成する
プロジェクトを作成する
Firebase コンソールで [プロジェクトを追加] を選択します。
プロジェクトの名前を FriendlyChat
にして、[プロジェクトを作成] をクリックします。
Firebase の料金プランをアップグレードする
Cloud Storage for Firebase を使用するには、Firebase プロジェクトが従量課金制(Blaze)料金プランを利用している必要があります。これは、Cloud 請求先アカウントにリンクされていることを意味します。
- Cloud 請求先アカウントには、クレジット カードなどのお支払い方法が必要です。
- Firebase と Google Cloud を初めて使用する場合は、$300 分のクレジットと無料トライアル用の Cloud 請求先アカウントの利用条件をご確認ください。
- イベントの一環としてこの Codelab を行う場合は、利用可能な Cloud クレジットがあるかどうか主催者にお問い合わせください。
プロジェクトを Blaze プランにアップグレードする手順は次のとおりです。
- Firebase コンソールで、プランをアップグレードを選択します。
- Blaze プランを選択します。画面上の手順に沿って、Cloud 請求先アカウントをプロジェクトにリンクします。
このアップグレードの一環として Cloud 請求先アカウントを作成する必要があった場合は、Firebase コンソールでアップグレード フローに移動してアップグレードを完了する必要があります。
iOS アプリを接続する
- 新しいプロジェクトの [Project Overview] 画面で、[Add Firebase to your iOS app] をクリックします。
- バンドル ID(「
com.google.firebase.codelab.FriendlyChatSwift
」など)を入力します。 - App Store ID を「
123456
」と入力します。 - [アプリを登録] をクリックします。
アプリに GoogleService-Info.plist ファイルを追加する
2 番目の画面で、[Download GoogleService-Info.plist] をクリックして、アプリに必要なすべての Firebase メタデータを含む構成ファイルをダウンロードします。このファイルをアプリケーションにコピーし、friendChatSwift ターゲットに追加します。
ポップアップの右上にある [x] をクリックして閉じます。ステップ 3 と 4 はここで行うため、スキップします。
Firebase モジュールをインポートする
まず、Firebase
モジュールがインポートされていることを確認します。
AppDelegate.swift、FCViewController.swift
import Firebase
AppDelegate で Firebase を構成する
.plist ファイルから基盤となる Firebase サービスを構成するには、application:didFinishLaunchingWithOptions 関数内の FirebaseApp の「configure」メソッドを使用します。
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 のセキュリティのドキュメントをご覧ください。
Authentication API を構成する
アプリケーションがユーザーに代わって Firebase Authentication API にアクセスするには、その API を有効にする必要があります。
- Firebase コンソールに移動し、プロジェクトを選択します。
- [認証] を選択します。
- [ログイン方法] タブを選択します。
- [Google] スイッチを有効(青)に切り替えます。
- 表示されたダイアログで [保存] を押します。
この Codelab の後半で「CONFIGURATION_NOT_FOUND」というメッセージでエラーが発生した場合は、このステップに戻って作業を再確認してください。
Firebase Authentication の依存関係を確認する
Firebase Auth の依存関係が Podfile
ファイルに存在することを確認します。
Podfile
pod 'Firebase/Auth'
Google ログイン用に Info.plist を設定します。
XCode プロジェクトにカスタム URL スキームを追加する必要があります。
- プロジェクト構成を開きます(左側のツリービューでプロジェクト名をダブルクリックします)。[TARGETS] セクションでアプリを選択し、[Info] タブを開いて [URL Types] セクションを展開します。
- [+] ボタンをクリックし、反転クライアント ID の URL スキームを追加します。この値を確認するには、GoogleService-Info.plist 構成ファイルを開き、REVERSED_CLIENT_ID キーを探します。見つかったキーの値をコピーし、構成ページの [URL スキーム] ボックスに貼り付けます。その他の入力欄は空白にしておきます。
- 完了すると、構成は次のようになります(ただし、値はアプリケーションによって異なります)。
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)
}
}
ログアウト
ログアウト メソッドを追加する
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)")
}
}
ログイン ユーザーとしてメッセージの読み取りをテストする
- [実行] ボタンをクリックします。
- すぐにログイン画面が表示されます。[Google ログイン] ボタンをタップします。
- すべてが正常に完了すると、メッセージ スクリーンが表示されます。
6. Realtime Database を有効にする
メッセージをインポートする
Firebase コンソールのプロジェクトで、左側のナビゲーション バーにある [データベース] アイテムを選択します。データベースのオーバーフロー メニューで、[JSON をインポート] を選択します。friendchat ディレクトリ内の initial_messages.json
ファイルを参照して選択し、[Import] ボタンをクリックします。これにより、現在データベースにあるすべてのデータが置き換えられます。また、データベースを直接編集することもできます。その場合、緑色の + と赤色の X を使用して、アイテムを追加または削除します。
インポート後のデータベースは次のようになります。
Firebase データベースの依存関係を確認する
Podfile
ファイルの依存関係ブロックに Firebase/Database
が含まれていることを確認します。
Podfile
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
}
Message Sync をテストする
- [実行] ボタンをクリックします。
- [ログインして開始] ボタンをクリックしてメッセージ ウィンドウに移動します。
- Firebase コンソールで新しいメッセージを直接追加するには、[messages] エントリの横にある緑色の + 記号をクリックし、次のようなオブジェクトを追加します。
- フレンドリー チャット UI に表示されていることを確認します。
7. メッセージを送信する
Send Message を実装する
値をデータベースに push します。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)
}
メッセージの送信のテスト
- [実行] ボタンをクリックします。
- [ログイン] をクリックしてメッセージ ウィンドウに移動します。
- メッセージを入力して送信します。新しいメッセージがアプリの UI と Firebase コンソールに表示されます。
8. 画像の保存と受信
Firebase Storage の依存関係を確認する
Podfile
の依存関係ブロックに Firebase/Storage
が含まれていることを確認します。
Podfile
pod 'Firebase/Storage'
Cloud Storage for Firebase を設定する
Firebase プロジェクトで Cloud Storage for Firebase を設定する方法は次のとおりです。
- Firebase コンソールの左側のパネルで [ビルド] を開き、[Storage] を選択します。
- [開始] をクリックします。
- デフォルトの Storage バケットのロケーションを選択します。
US-WEST1
、US-CENTRAL1
、US-EAST1
のバケットでは、Google Cloud Storage の「Always Free」階層を利用できます。他のすべてのロケーションのバケットには、Google Cloud Storage の料金と使用量が適用されます。 - [テストモードで開始] をクリックします。セキュリティ ルールに関する免責条項を確認します。
この Codelab の後半で、セキュリティ ルールを追加してデータを保護します。Storage バケットのセキュリティ ルールを追加せずに、アプリを配布または公開しないでください。 - [作成] をクリックします。
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. 画像メッセージを送信する
Store and Send Images を実装する
ユーザーから画像をアップロードし、この画像のストレージ 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])
}
}
}
画像メッセージの送受信をテストする
- [実行] ボタンをクリックします。
- [ログイン] をクリックして、メッセージ ウィンドウに移動します。
- [写真を追加] アイコンをクリックして写真を選択します。写真を含む新しいメッセージが、アプリの UI と Firebase コンソールに表示されます。
10. 完了
Firebase を使用してリアルタイム チャット アプリケーションを簡単に作成しました。
学習した内容
- Realtime Database
- 連携ログイン
- ストレージ