[iOSアプリ開発] チャットアプリ:Firebase利用

GoogleのFirebaseを利用して、チャットアプリを作成してみませんか?

チャットアプリのユーザ認証やログイン認証パスワード、メッセージデータの保存にGoogleのFirebaseを利用します。

目次

概要

この講座で学習する内容
  • チャットアプリを作成

アプリを作成する過程で、以下の内容を学習できます。

  • 画面遷移(Segue)、ナビゲーションバーの設定方法
  • ループ(For、While)
  • Swift Package Managerの設定方法
  • Firebase(Authentication)を使用したユーザ認証、ログイン認証
  • Firebase(Cloud Firestore)を使用したデータ保存など
  • テーブルビューの使用
  • カスタムビューを使用してテーブルビューにメッセージを表示する方法

MVCデザインパターンを適用しています。
Model(M)、View(V)、Controller(C
記事中、M、V、Cの略を使用しています。

参考にした講座】iOS & Swift – The Complete iOS App Development Bootcamp (Udemy)
 (Section15) Firebase Cloud FireStore, TableViews and Cocoapod Dependencies

この講座で作成するアプリの概要
スタート画面
ユーザ登録画面
ログイン画面
チャット画面
  1. アプリを起動するとスタート画面が立ち上がります。
  2. Registerをクリックするとユーザ登録画面へ遷移します。
    Loginをクリックするとログイン画面へ遷移します。
  3. ユーザ認証、ログイン認証、データ保存はGoogleのFirebaseを利用しています。
  4. ユーザ登録画面で、メールとパスワードを入力(一定のルールあり)すると、チャット画面に遷移します。
  5. ログイン画面で、メールとパスワードを入力し、登録者情報と合致(Firebase(Authentication))すると、チャット画面に遷移します。
  6. チャット画面は、Firebase(Cloud FireStore)からデータを入手し表示させています。
  7. チャット画面で新たにメッセージを送信すると、Firebase(Cloud FireStore)にデータが保存され、リアルタイムにそのデータを読み取り、チャット画面に表示します。
  8. チャット画面は、現在のユーザと別のユーザのメッセージスタイルを区別するように設定しています。
アプリの開発環境(2023年5月5日現在)
  • Xcode: Version 14.3
  • macOS: Venture: Version 13.3.1
  • iOS: 16.4.1
GitHubからプロジェクトを入手したい方はこちら

アプリの初期設定

開始プロジェクトをクローンして起動した状態を確認してみましょう。

開始プロジェクトの確認

開始プロジェクトの確認

階層図より、

Modelフォルダ:ファイルなし
Viewフォルダ:Main.storyboard
Controllerフォルダ:次の4ファイルあり、既に設定されているUI(Main.storyboard)とのリンクを図で示します。

  1. WelcomeViewController(C-1):スタート画面
  2. RegisterViewController(C-2):ユーザ登録画面
  3. LoginViewController(C-3):ログイン画面
  4. ChatViewController (C-4):チャット画面
WelcomeViewController(C-1):スタート画面)
RegisterViewController(C-2):ユーザ登録画面
LoginViewController(C-3):ログイン画面
ChatViewController (C-4):チャット画面

Segue(画面遷移)の設定

STEP
ユーザー登録画面→チャット画面

同様に、ログイン画面→チャット画面へのSegueを設定します。

STEP
スタート画面のログインボタン→ログイン画面

同様に、スタート画面のレジスターボタン→ユーザー登録画面へのSegueも設定します。

STEP
Identifierの登録

Identifierを以下のように登録すると、黄色いエラー(Segues initiated directly from view controllers must have an identifier)が消えました。
RegisterToChat」レジスター画面→チャット画面
LoginToChat」ログイン画面→チャット画面

タイトル画面をアニメーション風に見せる

アプリ起動時に「⚡️FlashChat」の文字がアニメーション風(1文字づつ)に表示されるようにコードを記載します。

方法1:forループを使用

forループ
class WelcomeViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        
        titleLabel.text = ""
        var charIndex = 0.0
        let titleText = "⚡️FlashChat"
        for letter in titleText {
            Timer.scheduledTimer(withTimeInterval: 0.1 * charIndex, 
            repeats: false) { (timer) in
                self.titleLabel.text?.append(letter)
            }
            charIndex += 1
        }
    }
}

方法2:CLTypingLabelを使用

プロジェクト用Pod(CLTypingLabel)のインストール

テキスト・アニメーションを行うためのコードが豊富なCLTypingLabelをインストールします。

https://cocoapods.org/pods/CLTypingLabel

ここでは、インストールの手順から詳しく解説します。

STEP
プロジェクトがあるディレクトリへ移動

ターミナルで操作します。

STEP
Podファイルの初期化
STEP
Podfileの修正
修正後
STEP
Podファイルのインストール
STEP
xcworkspaceを確認する
xcworkspace
プロジェクトのビルド

xcworkspaceを開き、ビルド「command+b」すると、以下の4つのエラーが出ましたので、順に解消していきます。

STEP
エラー1「characters is unavailable」の解消

CLTypingLabel」のサイトで解決法を検索しました。以下の通り、コードを2箇所修正することで解決しました。

https://cocoapods.org/pods/CLTypingLabel
https://github.com/cl7/CLTypingLabel/pulls?q=is%3Apr+is%3Aclosed
STEP
エラー2「NSAttributedStringKey has been renamed …」

このエラーは、コードがSwiftの低いバージョンを使用しているために生じているようです。

Podfile.lock
https://github.com/cl7/CLTypingLabel/releases
https://guides.cocoapods.org/syntax/podfile.html
https://github.com/cl7/CLTypingLabel/blob/master/CLTypingLabel.podspec
Podファイルを修正

これで、無事エラーを解消する事ができました。

CLTypingLabelの使用
https://cocoapods.org/pods/CLTypingLabel
ラベルのClassを「CLTypingLabel」へ変更

CLTypingLabelをインポートすれば、1行のコード(青枠)でタイトル画面をアニメーション風に見せる事ができました。

(参考)CLTypingLabelを削除する方法
  1. ①「Change the class of a label from UILabel to CLTypingLabel;」を基に戻す
  2. ②「Programmatically set a new String to its text property at runtime, animation would be triggered automatically;」を基に戻す
  3. PodFileを基に戻す
  4. ターミナルで「pod install」を実行する。→CLTypingLabelが削除されます

Firebase(Authentication)の利用

Firebaseの設定が未了の方は次の記事のこちらのページから確認ください。

Firebaseの設定が完了されている方は、次に進んでください。

ユーザー認証の設定

これから作成する基本的な流れ
  1. ユーザー登録画面(C-2)から、「e-mail」と「パスワード」を入力
  2. Registerボタンを押すと、Firebaseに「e-mail」と「パスワード」情報が送信される
  3. Firebaseは、「e-mail」情報を記録し、「パスワード」を暗号化します。
  4. ユーザー登録完了後、チャット画面(C-4)に移動します。
STEP
Firebase Authの利用開始登録
メール/パスワードの使用→有効にする
https://console.firebase.google.com/project/
STEP
ユーザ登録に必要なコードを入手
https://firebase.google.com/docs/auth/ios/start?hl=ja&authuser=0
STEP
Xcodeユーザー認証画面(C-2)にペースト
STEP
プログラムコードの追加

「e-mail」と「パスワード」を入力すると、チャット画面(C-4)に移動するようにプログラムコードを追加します。

チャット画面(C-4)に移動するためには、Identifierに登録した「RegisterToChat」がコード記載時に必要になります。

import UIKit
import FirebaseAuth

class RegisterViewController: UIViewController {

    @IBOutlet weak var emailTextfield: UITextField!
    @IBOutlet weak var passwordTextfield: UITextField!
//「register」ボタンをクリック  
    @IBAction func registerPressed(_ sender: UIButton) {
        
        if let email = emailTextfield.text, 
           let password = passwordTextfield.text {

//email,passwordの登録
              Auth.auth().createUser(
              withEmail: email, 
              password: password) { authResult, error in

                if let e = error {
                    print(e.localizedDescription)
                } else {
//チャット画面へ遷移
                    self.performSegue(withIdentifier: "RegisterToChat", 
                    sender: self)
                }
            }
        }
    }
}
ユーザー登録画面(C-2)
チャット画面(C-4)
  • e-mail:「1@2.com」
  • パスワード:「123456」
    →XcodeのSecre Text Entryにチェックを入れて、非表示にしています
  • Registerボタンをクリックすると、チャット画面(C-4)に移動できました。
STEP
Firebaseの確認

ユーザー登録に成功したので、Firebaseの登録状況を確認してみます。

「Authentication」→「Users」に移動すると、先ほど設定したe-mail「1@2.com」が登録されていることが確認できます。パスワードはGoogleの管理下で安全に暗号化されているため、非表示にされています。

ログインの設定

これから作成する基本的な流れ
  1. ユーザーはログイン画面(C-3)から、先ほど登録した「e-mail」と「パスワード」を入力
  2. Loginボタンを押すと、Firebaseに「e-mail」と「パスワード」情報が送信される
  3. Firebaseは、Authenticationのデーターベースに保存されている情報と照合します。
  4. 照合確認が成功後、チャット画面(C-4)に移動します。
STEP
ログインに必要な情報を入手
STEP
Xcodeログイン画面(C-3)にペースト
STEP
プログラムコードの追加

入力された「e-mail」と「パスワード」が、Authenticationのデーターベースに保存されている情報と照合し、合致されていることが確認できたら、チャット画面(C-4)に移動するようにプログラムコードを追加します。

チャット画面(C-4)に移動するためには、Identifierに登録した「LoginToChat」がコード記載時に必要になります。

import UIKit
import FirebaseAuth

class LoginViewController: UIViewController {
    @IBOutlet weak var emailTextfield: UITextField!
    @IBOutlet weak var passwordTextfield: UITextField!

//「login」ボタンをクリック    
    @IBAction func loginPressed(_ sender: UIButton) {

        if let email = emailTextfield.text, 
           let password = passwordTextfield.text {
//ログイン認証
            Auth.auth().signIn(
            withEmail: email, 
            password: password) { authResult, error in
                if let e = error {
                    print(e.localizedDescription)
                } else {
//成功時、チャット画面へ遷移
                    self.performSegue(withIdentifier: "LoginToChat", 
                    sender: self)
                }
            }
        }   
    }
}
ログイン画面(C-3)
チャット画面(C-4)
  • e-mail:「1@2.com」
  • パスワード:「123456」
  • Loginボタンをクリックすると、チャット画面(C-4)に移動できました。

ログアウトの設定

ユーザーがログアウトして、チャット画面からスタート画面に戻る方法を設定します。

STEP
ログアウトボタンの設置

NavigationBarにログアウトボタンを設置します。

STEP
ログアウトボタンとコードのリンク
  1. ボタンの名称を「LogOut」にする
  2. コード(ChatViewController)とリンクをする
STEP
プログラムコードの追加

ログアウトボタンを押すと、スタート画面(C-1)に移動するようにプログラムコードを追加します。

import UIKit
import FirebaseAuth

class ChatViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    @IBOutlet weak var messageTextfield: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()

    }
    
    @IBAction func sendPressed(_ sender: UIButton) {
    }

//「logOut」ボタンをクリック    
    @IBAction func logOutPressed(_ sender: UIBarButtonItem) {
        do {
//成功時、スタート画面に戻る
            try Auth.auth().signOut()
            navigationController?.popToRootViewController(animated: true)
        } catch let signOutError as NSError {
            print("Error signing out: %@", signOutError)
        }
    }
}

テストする度に、emailとパスワードを入力するのは面倒!

アプリのテスト中の手間を省くために、あらかじめ登録しておくことができます。

スタート画面(C-1)
ログイン画面(C-3)
チャット画面(C-4)
  • Loginボタンを押すと、ログイン画面(C-3)に遷移します。
  • email、パスワードが予め登録されています。LogInボタンを押すと、チャット画面(C-4)に遷移します。
  • LogOutボタンを押すと、スタート画面(C-1)に遷移します。
STEP
Backボタンを消す、タイトル表示の設定
class ChatViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "⚡️FlashChat"
        navigationItem.hidesBackButton = true
    }
}
  • チャット画面(C-4)に、Backボタンは不要であるため、見えなくなるように設定します。
  • チャット画面(C-4)のNavigationBarに「⚡️FlashChat」を表示させます。

文字列を格納するファイルの作成

文字列を入力する場合、Xcodeの入力補完機能が働かないため、タイプミスに気付きにくい

このまま、アプリを実行すると、もちろん、クラッシュしてしまいます。

このようなミスを軽減するため、文字列を格納するファイルを作成し、このファイルにコードを使ってアクセスするようにします。

文字列を格納するファイル

letの前に「static」を付している。構造体の初期化不要でデータの呼び出しが可能。

スタート画面(C-1)
チャット画面(C-4)
ユーザー登録画面(C-2)
ログイン画面(C-3)

テーブルビューにメッセージを表示する

テーブルビューにメッセージを表示する

STEP
メッセージを格納するファイル(構造体)を作成
import Foundation

struct Message {
    let sender: String //emailアドレス
    let body: String   //Message本文
}
STEP
メッセージを表示するコードの作成
チャット画面(C-4)
class ChatViewController: UIViewController {

//練習用にメッセージを3つ作成
    var messages: [Message] = [
        Message(sender: "1@2.com", 
            body: "やあ!"),
        Message(sender: "a@b.com", 
            body: "こんにちは!"),
        Message(sender: "1@2.com", 
            body: "調子はどう?")
    ]
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
    }
}

//テーブルビューにデータを入れる役割
extension ChatViewController: UITableViewDataSource { 
    func tableView(_ tableView: UITableView, 
        numberOfRowsInSection section: Int) -> Int {
//メッセージの行数
        return messages.count  
    }
    
    func tableView(_ tableView: UITableView, 
        cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//メッセージを表示
        let cell = tableView.dequeueReusableCell(
        withIdentifier: K.cellIdentifier, for: indexPath)  

        cell.textLabel?.text = messages[indexPath.row].body
        return cell
    }
}
  • messages: [Message]に表示させたいメールを作成します
  • UITableViewDataSource」プロトコルを適用します
  • func …numberOfRowsInSection
    →メッセージの行数を返します
  • func …cellForRowAt
    →メッセージの行数分呼び出される、表示したい内容(メッセージ本文)を返します
  • K.cellIdentifier(=”ReusableCell“)
STEP
メッセージをクリックしても何も起こらないようにする

メッセージングアプリにおいては、メッセージをクリックしても何も起こらないようにしたい。

メッセージをクリックしても何も起こらないようにする設定
(参考)メッセージをクリックしたときに発動するデリゲート
class ChatViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

extension ChatViewController: UITableViewDelegate {
    //特定の行が選択された時発動
    func tableView(_ tableView: UITableView, 
        didSelectRowAt indexPath: IndexPath) {
        print(indexPath.row)
    }
}
  • UITableViewDelegate」プロトコルを適用します
  • func …didSelectRowAt
    →選択されたメッセージの何かを返します(例は行数)

カスタムセルを使用してテーブルビューにメッセージを表示する

STEP
カスタムセルのファイル作成

「File」→「New」→「Cocoa Touch Class」を開きます

  • Class: MessageCell
  • Subclass of: UITableViewCellを選択
  • Also create XIB file: チェック
  • Language: Swift

を入力し、「Next」を押し、保存先を決定すると、2つのファイルが作成されます。

  • MessageCell.swift
  • MessageCell.xib
MessageCell.swift
MessageCell.xib
STEP
カスタムセルのデザインを作成
①UIViewの追加→Backgroundカラーの設定
②Label, ImageViewの追加
③ViewとImageViewをStackViewにドロップ
④ImageViewの幅、高さに制約を追加
④StackViewの上下左右に制約を追加
⑤Labelの上下左右に制約を追加
⑥StackView項目間の間隔を設定
⑦ImageViewの画像を選択
STEP
カスタムセルのリンクの設定
STEP
カスタムセルファイルをテーブルビューに登録 

カスタムセルファイル(MessageCell)をチャット画面(ChatViewController)(C-4)のViewDidLoadに登録します。

class ChatViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

//カスタムセルのクラス名        
        tableView.register(UINib(nibName: K.cellNibName, bundle: nil), 
//Identifier
        forCellReuseIdentifier: K.cellIdentifier)
    }
  • nibName: K.cellNibName(=MessageCell
  • forCellReuseIdentifier: K.cellIdentifier(=ReusableCell
STEP
DataSourceメソッドでカスタムセルを設定
修正前
//メッセージを表示
        let cell = tableView.dequeueReusableCell(
        withIdentifier: K.cellIdentifier, for: indexPath)  

        cell.textLabel?.text = messages[indexPath.row].body
        return cell
修正後
extension ChatViewController: UITableViewDataSource { 
    func tableView(_ tableView: UITableView, 
        cellForRowAt indexPath: IndexPath)
         -> UITableViewCell {

//MessageCellにダウンキャスティング
        let cell = tableView.dequeueReusableCell(
        withIdentifier: K.cellIdentifier, for: indexPath) 
        as! MessageCell 

        cell.label.text = messages[indexPath.row].body
        return cell
    }
}
  • as! MessageCell: カスタムセルを使用するためダウンキャスティング
  • K.cellIdentifier(=ReusableCell
  • cell.label.text: カスタムセルにメッセージを挿入
  • カスタムセルを使用するため、テーブルビュー内のReusableCell削除
STEP
カスタムセルでメッセージ表示

これまで設定した内容を確認するためアプリ実行し、カスタムセルを使用してメッセージが表示されることを確認します。

問題なく表示できていますね。

STEP
カスタムセルの調整
  • Labelの文字色:BrandLightPurpleに変更
  • メッセージの枠をバブルのように丸みを持たせる
  • ラベルのLinesプロパティ=0にする(メッセージが何行でも表示可)
  • StackViewのAlignmentをTopに変更
MessageCell: メッセージの枠をバブルのように丸みを持たせる
class MessageCell: UITableViewCell {
    override func awakeFromNib() {
        super.awakeFromNib()

//メッセージの枠をバブルのように丸みを持たせる        
        messageBubble.layer.cornerRadius 
        = messageBubble.frame.size.height / 5
    }
}
StackViewのAlignmentをTopに変更

Firebase(Cloud FireStore)の利用

Cloud FireStoreへのデータ保存

データベースの新規作成

ユーザが送信したメッセージの保管に、Cloud FireStore(Firebaseの主要な機能)を使用します。

プログラムコードの作成

AppDelegateへのコード追記
https://firebase.google.com/docs/firestore/quickstart?hl=ja&authuser=0#swift

Googleのドキュメントを参考に赤枠の行を追記します。

import FirebaseFirestore

class AppDelegate: UIResponder, UIApplicationDelegate {
//アプリ起動時
    func application(_ application: UIApplication, 
        didFinishLaunchingWithOptions launchOptions: 
        [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

//FireStoreのインスタンス初期化
        FirebaseApp.configure()
        let db = Firestore.firestore()
        return true
    }
チャット画面(C-4)への追記
https://firebase.google.com/docs/firestore/quickstart?hl=ja&authuser=0#swift

Googleのドキュメントを参考に赤線の行を追記します。

  • K.FStore.collectionName(=”messages“)
  • K.FStore.senderField(=”sender“)
  • K.FStore.bodyField(=”body“)
import FirebaseFirestore

class ChatViewController: UIViewController {
    let db = Firestore.firestore()
    
    @IBAction func sendPressed(_ sender: UIButton) {

//メッセージ、メールアドレスがあれば、
        if let messageBody = messageTextfield.text, 
           let messageSender = Auth.auth().currentUser?.email {

//FireStoreへデータ保存
            db.collection(K.FStore.collectionName).addDocument(data: 
            [K.FStore.senderField: messageSender,
             K.FStore.bodyField: messageBody]) { (error) in
                if let e = error {
                    print("There was an issue saving data to firestore, \(e)")
                } else {
                    print("Successfully saved data.")
                }
                
            }
        }
    }

アプリ実行→FireStoreへのデータ保存確認

アプリを実行し、メッセージに「テストメッセージ」を入力し、送信ボタンをクリックしました。

その結果、FireStoreに赤枠の通りデータが保存されていることが確認できました。

  • コレクション名:messages
  • sender:1@2.com
  • bodyテストメッセージ

Cloud FireStoreからデータ読み取り

読み取ったデータをコンソール出力する

Cloud FireStoreからデータを読み取るためのプログラムコードを作成します。

Googleのドキュメントを参考に赤枠のコードを追記します。

https://firebase.google.com/docs/firestore/query-data/get-data?hl=ja&authuser=0
https://firebase.google.com/docs/reference/swift/firebasefirestore/api/reference/Classes/QuerySnapshot?authuser=0
  • K.FStore.collectionName(=”messages“)
class ChatViewController: UIViewController {    
    override func viewDidLoad() {
        super.viewDidLoad()
       
        loadMessages()
    }
    
    func loadMessages() {
        messages = []

//FireStoreからデータ取得        
        db.collection(K.FStore.collectionName).getDocuments() 
            { (querySnapshot, err) in

            if let err = err {
                print("Error getting documents: \(err)")
            } else {
                for doc in querySnapshot!.documents {
                    print(doc.data())
                }
            }
        }
    }

プログラムを実行すると、”messages“コレクションに保存されたデータがコンソールに出力されました。

読み取ったデータをチャット画面(C-4)に表示する

読み取った後、チャット画面(C-4)に表示するためのプログラムコードを追記します。

db.collection(K.FStore.collectionName).getDocuments() 
    { (querySnapshot, err) in

    if let err = err {
        print("Error getting documents: \(err)")
    } else {

FireStoreからのデータを読み取り
        for doc in querySnapshot!.documents {
            let data = doc.data()

            if let messageSender = data[K.FStore.senderField] as? String,
               let messageBody = data[K.FStore.bodyField] as? String {

                let newMessage = Message(sender: messageSender, body: messageBody)
                self.messages.append(newMessage)
 //DispatchQueue                   
                DispatchQueue.main.async {
                    self.tableView.reloadData()
                }
            }
        }
    }
}
  • self.tableView.reloadData()
    クロージャーの中でUIを操作するときは、DispatchQueueの中に入れる
    →データの取得処理がバックグラウンドで処理される
  • data[K.FStore.senderField] as? String、data[K.FStore.bodyField] as? String
    →Any型からString型へダウンキャスティング

プログラムを実行すると、”messages“コレクションに保存されたデータがチャット画面(C-4)に表示されました。

メッセージ送信後、直ぐにチャット画面(C-4)を更新する

現在、アプリにログイン後、FireStoreに保存されているメッセージをチャット画面(C-4)に表示することはできるようになりました。

しかし、新しいメッセージを送信した時データはFireStoreに保存されますが、そのメッセージを直ぐにチャット画面(C-4)に表示することができません。

そこで、以下の記事を参考に、新しいメッセージがFirestoreデータベースに追加されるたびに、チャット画面に表示されるようにしていきます。

これを行うには、getDocuments()addSnapshotListener()に変更します。

https://firebase.google.com/docs/firestore/query-data/listen?hl=ja&authuser=0

新しいメッセージを追加すると、テーブルビューにメッセージが表示されました。

しかし、最初から表示されていたメッセージが2重に表示されているため、

messages = [ ]の場所を、変更します。
(変更前)db.collection…addSnapshotListener()の
(変更後)db.collection…addSnapshotListener()の

新しくメッセージを追加すると、二重表示なく表示されるようになりました

送信した時刻順にメッセージが表示されるようにする

今度は、データベースから取得したメッセージを送信時刻順に並び替えるようにプログラムを修正します。

現在、メッセージをデータベースに送信するとき、messageSendermessageBodyの2つの情報を送っていますが、これにDate()オブジェクトを使用して、送信した時刻を加えてみましょう。

その前に、今保存されているデータベースをクリアします。

STEP
データベースをクリア
①コレクションを削除を選択します。
②コレクション名「messages」を入力し、
削除ボタンを押します。
③データがクリアされました。
STEP
送信時刻をデータベースに追加する
①送信データに現在時刻「Date().timeIntervalSince1970」を追記します。
  • 新たに、3つのメッセージを送信しました
  • 最初に送信したメッセージが1番下に表示されています
  • 送信時刻順に上から表示されるように、プログラムを修正します。
.oder(by: “date”)をプログラムに追記します。
https://firebase.google.com/docs/firestore/query-data/order-limit-data?hl=ja&authuser=0
「date」データをキーにして並び替えするように、プログラムを修正します。
.order(by: K.FStore.dateField)を追記
K.FStore.dateField:”date”

Firebaseデータ読み書きのルール変更

Cloud FireStoreで最初にデータベースを作成した時、「30日間は誰でも読み書きできる」ルールに設定していました。

これを、「アプリにサインアップしたユーザは、データベースに読み書きできる」ルールに変更します。

https://firebase.google.com/docs/firestore/security/get-started?hl=ja&authuser=0
これで、データベースの無期限確保は完了です。

ユーザインターフェイスの改良

テキストフィールドが常に表示されるように設定する

現在、ユーザがテキストフィールドをタップすると、キーボードが立ち上がり、テキストフィールドと送信ボタンが完全に覆われてしまいます

シュミレータ上では、「コマンドキー+K」でキーボードの表示、非表示を切り替えることができますが、iPhone実機ではこれができません。

サードパーティのライブラリを利用してこの問題を解決します。

IQKeyboardManagerSwiftのインストール

IQKeyboardManagerSwiftは、コードを書く必要がなく、バックグラウンドで動作し、テキストフィールドを持ち上げて、あらゆる向き、どんなデバイスでもキーボードに邪魔されないようにするものです。

このパッケージをインストールする方法は複数ありますが、今回は、SwiftPackageManagerによるインストールを行います。

https://cocoapods.org/pods/IQKeyboardManagerSwift
URLをコピーする
https://github.com/hackiftekhar/IQKeyboardManager
「File」→「Add Packages」をクリックして上記画面を出す。
①GitHubをクリック
②先程コピーしたURLをペースト
③Dependency Rule、Add to Projectを選択
④Add Packageをクリック
赤線の2行をAppDelegateに追記
https://cocoapods.org/pods/IQKeyboardManagerSwift

アプリを実行後、

テキストフィールドをタップすると、キーボードが立ち上がりますが、テキストフィールドと送信ボタンも上に移動するため、隠れずに表示されています。

続けて、以下の設定コードを追記します。
・Toolbarを表示しない
・テキストフィールド以外をタップすると、キーボードが非表示になる

//IQKeyboardManagerを利用可能にする
IQKeyboardManager.shared.enable = true  

//Toolbarを表示しない
IQKeyboardManager.shared.enableAutoToolbar = false 

//テキストフィールド以外をタップすると、キーボードが非表示になる
IQKeyboardManager.shared.shouldResignOnTouchOutside = true

また、クリックしたときの背景がグレーにならないように、
Interactionのチェックを外します。

別のユーザのメッセージスタイルの作成

別のユーザのメッセージスタイルが区別されるように、現在のメッセージセルを使ってスタイルを設定します。

Me画像をコピーし、ペーストします
複製された画像を左端にドラッグします
ImageViewから「YouAvatar」画像を選択します
Viewとリンクを作成します

ユーザを区別して、メッセージスタイルを変更する

ユーザによって、表示されるメッセージスタイルを以下のように変えるコードを追加します。

別のユーザの場合

  • You」画像を表示にする
  • 「Me」画像を非表示する
  • 背景色をpurpleにする
  • テキスト色をlightPurpleにする

現在のユーザの場合

  • 「You」画像を非表示にする
  • Me」画像を表示する
  • 背景色をlightPurpleにする
  • テキスト色をpurpleにする
別のユーザの場合
現在のユーザの場合
func tableView(_ tableView: UITableView,
                   cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let message = messages[indexPath.row]
        let cell = tableView.dequeueReusableCell(
            withIdentifier: K.cellIdentifier,
            for: indexPath) as! MessageCell 
        
        cell.label.text = message.body
        
        //現在のユーザの場合
        if message.sender == Auth.auth().currentUser?.email {
//You画像隠す
            cell.leftImageView.isHidden = true
//Me画像表示
            cell.rightImageView.isHidden = false
//背景色
            cell.messageBubble.backgroundColor
            = UIColor(named: K.BrandColors.lightPurple)
//文字色
            cell.label.textColor = UIColor(named: K.BrandColors.purple)
        }
        //別のユーザの場合
        else {
//You画像表示
            cell.leftImageView.isHidden = false
//Me画像隠す
            cell.rightImageView.isHidden = true
//背景色
            cell.messageBubble.backgroundColor
            = UIColor(named: K.BrandColors.purple)
//文字色
            cell.label.textColor = UIColor(named: K.BrandColors.lightPurple)
        }
}

テーブルビューの最後の行を一番下に表示させる

現在、メッセージを複数追加していくと、最後に追加した表示を見るためには、手動でスクロールして表示させる必要があります。

新しいメッセージが追加されるたびに、テーブルビューの一番下まで自動でスクロールさせるためのコードを追記します。

メッセージを追加すると、テーブルビューが自動でスクロールして、最後に追加したメッセージが表示されるようになりました。

メッセージ送信後、メッセージをクリアする

現在、メッセージを送信後もテキストフィールドにメッセージが残ったままであるため、送信後クリアされるようにコードを追記します。

クロージャの中でユーザーインターフェイスを更新しようとしているため、DispatchQueueを利用しています。

ナビゲーションバーの色を「BrandBlue」に設定します
  • Translucentのチェックを外す
    ナビゲーションバーの青色と、View背景色の青色を同じように見せるため
  • Title ColorをWhiteColorにする

WelcomeViewのナビゲーションバーは、何も使用することがないため非表示にします。

非表示にするタイミングは、WelcomeViewが表示される直前「viewWillAppear」です。

ナビゲーションバー
表示
ナビゲーションバー非表示

しかし、非表示にした状態のままアプリを実行すると、ログイン画面や登録画面に入ったときに、ナビゲーションバーが表示されません。

そこで、WelcomeViewが消えて次の画面に進もうとする時点「viewWillDisappear」で、ナビゲーションバーを表示することにします。

ナビゲーションバー
非表示
ナビゲーションバー表示

この記事が気に入ったら
いいね または フォローしてね!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次