15
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

RxSwift(とFirebase)でチャットツールを書く

Posted at

RxSwiftの勉強のために、Firebaseを使ったシンプルなチャットツールのコードを書いてみました。

  • Xcode: 10.2.1 ( iOS12 )
  • RxSwift: 5.0.0

ライブラリ管理にはCocoaPodsを使っています。

画面構成と機能

サインアップ、サインイン、チャットの3画面という必要最低限の画面で構成されます。
チャネルやルームの選択も、参加者の招待もできない超省略仕様です。

Image1.png

チャット画面では、画面の下にテキスト入力フィールドを表示します。
送信ボタンを押すと書いたものがリストに追加されるという、よくあるやつです。
リストには、ユーザ名と投稿したメッセージを表示します。

ソースコードと実行方法

コードは、 https://github.com/hirobe/ChatSample にあります。
実行するには、Firebaseにプロジェクトを作成する必要があります。

Firebaseプロジェクトの作成

Firebaseのコンソールへ移動し、プロジェクトを作成してください。
プロジェクトを作成したら、プロジェクトにiOSアプリを追加して、GoogleService-Info.plistダウンロードしてください。
XcodeでRxSwiftChatSampleを開いて、GoogleService-Info.plistをツリーにドロップしてプロジェクトに含めてください。

Firebaseプロジェクトの設定

以下を使用します。

  • Firebase Authentication
  • Firebase Firestore

認証のためにFirebase Authenticationを使用します。Firebaseのコンソールへ移動し、Firebase Authenticationを追加してください。ログインプロバイダとしてメール/パスワードを有効にしてください。
メールリンクは使用しませんのでOFFにしてください。

Firestore設定

DBとしてFirestoreを利用します。Firebaseのコンソールへ移動し、Databaseを追加してCloud FireStoreを選んでください(Realtime Databaseではありません)。
"+コレクションを追加"をクリックして"messages"というコレクションを作成してください。この下に投稿したメッセージが作られます。

続けて、ルールタブを選んで、右ペインに以下を貼り付けてください。認証済みユーザはで全データの読み書きができる設定です(あくまで開発用。本番用はちゃんと検討すべき)

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if request.auth != null;
    }
  }
}

以上で、サーバの準備は完了です。Firebaseは簡単ですねぇ

コードの説明

サインアップと、チャットの2画面を書きます。
サインイン画面はFirebaseがFirebaseUIという形で提供しているものを使用します。

  • MessageViewController : チャット画面
  • SignUpViewController : サインアップ

サインアップ

サインアップ画面は、Email, username, password, password confirmの4項目の入力フィールドとサインアップボタンを持ちます。ユーザが、4項目入力してサインアップボタンを押したらFirebaseにユーザを作成します。また、サインイン画面へ遷移するLoginボタンを持ちます。
SignUp.png

以下のファイルからなります。

  • SignUp
    • Model
      • SignUpValidator.swift
      • SignUpDefaultAPI.swift
    • ViewModel
      • SignUpViewModel.swift
    • View
      • SignUp.storyboard
      • SignUpViewController.Swift

(実際のソースコードは https://github.com/hirobe/ChatSample/tree/master/RxSwiftChatSample を参照)

サインアップ画面はRxSwiftが提供しているサンプルコードRxExample( https://github.com/ReactiveX/RxSwift/tree/master/RxExample )のGitHubSignupのusingDriverを参考にしており、流れは同じです。

Model

Model層はFirebaseのAPIを呼ぶSignUpDefaultAPI, 入力値の検査を行うSignUpValidatorです。
SinUpDefaultAPI.swiftでは、Firebase Authenticationにユーザを作り、入力されたUsernameをdisplayNameとしてセットして、結果をObservableとして返します。ちなみに、ここではシンプルにFirebae Authenticationだけにユーザを作成していますが、本来ならFirestoreにもパスワード等を除いたユーザ情報をドキュメントとして保存するのが良いでしょうね。Firebase Authenticationの他のユーザのデータはアプリで参照できないので。

ViewModel

ViewのTextFieldをDriverとして受け取り、入力値のValidationを生成します。
実際のValidationはモデル層のSignUpValidatorで行います。文字数とかPasswordとかPassword confirmが同一かどうかを監視し、警告メッセージを出力します。
すべての入力がOKであればsignUpEnabledをtrueにします。
加えて、signUpTapsによりSignUpボタン押下時のAPI呼び出しを行います。

エラーの処理のために、signUpErrorというDriverを公開します。これはAPIの呼び出し結果をmaterialize()を使って、Errorを扱うためのストリームを分けています。errorの専用のストリームを用意することで、Viewではalertの表示がシンプルに行えます。(参照:RxSwift研究読本2)

また、API呼び出し中にグルグル回るインジケータを表示できるようにsigningUpというDirverを作成しています。このような処理中の制御には、RxExampleで使われているActivityIndicatorクラスが良さそうです。そのまま使っています。ActivityIndicatorは、監視対象がSubscribeされてからDisposeされるまでtrueとなるObservableです。これを監視してインジケータをまわします。インジケータにはかっこよかったのでJGProgressHUDを使用しています。

View

SignUpViewControllerは4つの入力フィールドを持ちます。email,username,password,password confirmです。ViweModelにこれらのDriverを渡すとともに、ViewModelからの出力を監視し警告メッセージ、ボタンの有効、エラーメッセージの表示を行います。
また、ログインのボタンを押された場合は、サインイン画面を生成し遷移します。サインイン画面はFirebaseUIが提供するものを使っていますので、サインイン画面に関するソースコードはありません。

ところで、実はFirebaseUIはサインインだけじゃなくサインアップも可能なのですが、ここではFUIEmailAuthのallowNewEmailAccountsプロパティをfalseにすることで、サインアップをできなくしています。

チャット

チャットの画面の機能は、大きく以下の2つになります。

  1. 画面下部の入力フィールドに入力されたメッセージをサーバーにPOSTする機能
  2. チャットのメッセージをサーバから取得してTableViewに表示する機能

Chat.png

入力とPOSTの機能

入力フィールド(InptToolbarView)の制御およびPOSTは以下のファイルが担当します。

  • Message
    • Model
      • PostDefaultAPI.swift
    • ViewModel
      • InputToolbarViewModel.swift
    • View
      • InputToolbar.xib
      • InputToolbarView.swift

InputToolbarView自体は、チャット画面であるMessageViewControllerのInputAccessaryViewプロパティに指定することで、画面の下部に表示します。最初は画面下に表示しつつキーボードが表示されたらそれに合わせてキーボードの上部に移動するのは、妙に難しいコツがいるのですが、概ね以下のやり方で実現できます。詳しくはMessageViewControllerとInpuToolbarViewのソースを見てください。

  • MessageViewControllerのcanBecomeFirstResponderプロパティでtrueを返す
  • MessageViewControllerのinputAccessoryViewプロパティでInputToolbarViewを返す
  • InputToolbarViewではlayoutMarginsGuideに対してAutolayout constraintを設定する

また、些細なUI改善として、キーボードのEnterを押してもキーボードが隠れないようしています。UITextFieldDelegateのtextFieldShouldReturnでfalseを返すとキーボードが隠れません。

POSTの処理はPostDefaultAPIで行います。中でFirestoreにレコードを作っています。エラー処理はサインアップと同様にError用のDriverを生成して、Viewでそれを受けてアラートを表示します。

メッセージをサーバから取得してTableViewに表示する機能

メッセージの表示に関わる処理は以下のファイルになります。
サーバからのデータの取得には、Firestoreのスナップショットリスナーを使用します。これはリスナーを作成しておけば、サーバと勝手にデータを同期してくれる便利なやつです。addSnapshotListenerのlistenerブロック内でonNextを発行すれば、サーバのデータを勝手に監視しつつデータを同期してくれるObserverの出来上がりです(MessageListener.swift)。

ViewModelでやっていることは、MessageLisnterのObserverを公開しているだけです。addSnapshotListenerの変更通知は一度に複数回起きることがあるので、debounce()でまとめています。

ViewではviewModel.items.driveをtableView.rx.itemにdriveして(bindして)います。ソースを見ると、サーバからデータを取得してtableViewに表示するまで行数にして100stepたらずで実現できていることがわかると思います。RxSwiftとFirebaseの力は恐るべしですねー

MessageViewControllerでは、そのほかにInputToolbarViewの表示、RxKeyboard( https://github.com/RxSwiftCommunity/RxKeyboard )を使用したキーボード表示時のスクロール制御、上スクロールによる入力のキャンセル、メッセージ追加時のスクロールを行っています。

  • Message
    • Model
      • Message.swift
      • MessageLisitener.swift
    • ViewModel
      • MessageViewModel.swift
    • View
      • Message.storyboard
      • MessageCell.swift
      • MessageViewController.swift

参考文献

参考にした資料です。

RxSwift で多重実行防止と実行中の表現を簡潔に書く https://blog.cybozu.io/entry/2018/12/27/080000

終わりに

RxSwift初心者なので変な書き方があれば指摘していただければ。

WWDCでSwiftUIも発表されたので、この後同じものをSwiftUIでも書いてみようと思います。

15
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?