0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Xcode/Swift/Firebase】チャットアプリにおける友達一覧画面の作り方

Last updated at Posted at 2024-07-30

この記事でやること

LINEのようなチャットアプリの友達一覧画面とその機能を実装する。画面は自分・友達一覧を表示し、友達追加ボタン・ログアウトボタンを設置する。機能は自分表示・友達表示・友達追加・ログアウトを実現する。

環境

  • Xcode 15.0.1
  • Swift 5

前提

users
  |-- userID1
        |-- username: "user1"
        |-- email: "user1@example.com"
        |-- profileImageURL: "http://example.com/user1.jpg"
        |-- friends: ["userID2", "userID3"]

Story Board

  1. ControllerViewを設置
  2. EmbeddedInからNavigationControllerを選択
  3. ControllerViewにTableViewとCellを表示
  4. NavigationBarの真ん中にLabel("友達一覧")を、右側と左側にそれぞれButtonを設置する
  5. TableViewを選択し、右側のインスペクターパネルの中にある「Connections Inspector」(接続インスペクタ)を開き、DataSourceおよびDelegateをControllerViewにドロップする
  6. FirendsListViewController作成後にTableViewをアウトレット接続し、各ボタンをアクション接続する

下記画像参照(画像では Tab Bar も実装しているが、この記事では必要ない)
image.png

FriendsListViewController

インポートとクラス定義

import UIKit
import FirebaseFirestore
import FirebaseAuth

class FriendsListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
  • UIKit:iOSアプリのUIを作成するためのフレームワーク。
  • FirebaseFirestore:Firebase Firestoreデータベースを操作するためのフレームワーク。
  • FirebaseAuth:Firebase認証を操作するためのフレームワーク。
  • FriendsListViewController:UIViewControllerのサブクラスで、UITableViewDelegateとUITableViewDataSourceプロトコルに準拠。

プロパティの定義

    @IBOutlet weak var tableView: UITableView!

    var friends: [String] = [] // 友達のユーザーIDを格納する配列
    var friendUsernames: [String] = [] // 友達のユーザー名を格納する配列
    var username: String = "" // 自分のユーザー名
  • tableView:ユーザーのプロフィールと友達リストを表示するためのテーブルビュー。
  • friends:友達のユーザーIDを格納する配列。
  • friendUsernames:友達のユーザー名を格納する配列。
  • username:自分のユーザー名を格納する文字列。

viewDidLoadメソッド

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // ナビゲーションバーの設定
        navigationItem.title = "友達一覧"
        let addFriendButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addFriend))
        navigationItem.rightBarButtonItem = addFriendButton
        
        tableView.delegate = self
        tableView.dataSource = self
        
        fetchUserProfile()
        fetchFriendsList()
        
        // ダミーユーザーの作成
//        DummyUserCreator.createDummyUsers()
    }
  • viewDidLoadメソッド:ビューがロードされたときに呼び出される。
  • ナビゲーションバーに「友達一覧」というタイトルを設定し、右上に友達追加ボタンを追加。
  • tableViewのデリゲートとデータソースを設定。
  • fetchUserProfileメソッドとfetchFriendsListメソッドを呼び出して、ユーザーのプロフィール情報と友達リストを取得。

友達追加処理

    @IBAction func addFriend(_ sender: UIBarButtonItem) {
        // 友達追加処理
        let alert = UIAlertController(title: "友達追加", message: "友達のユーザー名を入力してください", preferredStyle: .alert)
        alert.addTextField { textField in
            textField.placeholder = "ユーザー名"
        }
        alert.addAction(UIAlertAction(title: "キャンセル", style: .cancel, handler: nil))
        alert.addAction(UIAlertAction(title: "追加", style: .default, handler: { _ in
            if let addedUsername = alert.textFields?.first?.text, !addedUsername.isEmpty {
                self.addFriendToDatabase(addedUsername: addedUsername)
            }
        }))
        present(alert, animated: true, completion: nil)
    }
  • addFriendメソッド:友達追加ボタンがタップされたときに呼び出される。
  • アラートを表示し、ユーザー名を入力させる。
  • 入力されたユーザー名が空でない場合、addFriendToDatabaseメソッドを呼び出して友達をデータベースに追加。

ログアウト処理

    @IBAction func logoutButtonTapped(_ sender: UIBarButtonItem) {
        do {
            try Auth.auth().signOut()
            // ログイン画面に遷移
            let storyboard = UIStoryboard(name: "Main", bundle: nil)
            let loginViewController = storyboard.instantiateViewController(withIdentifier: "LoginViewController")
            if let window = UIApplication.shared.windows.first {
                window.rootViewController = loginViewController
                UIView.transition(with: window, duration: 0.5, options: .transitionCrossDissolve, animations: nil, completion: nil)
            }
        } catch let signOutError as NSError {
            print("Error signing out: %@", signOutError)
        }
    }
  • logoutButtonTappedメソッド:ログアウトボタンがタップされたときに呼び出される。
  • Firebaseからサインアウトし、ログイン画面に遷移。

プロフィール取得処理

    func fetchUserProfile() {
        // ユーザーのプロフィールを取得
        guard let userId = Auth.auth().currentUser?.uid else { return }
        let db = Firestore.firestore()
        db.collection("users").document(userId).getDocument { document, error in
            if let document = document, document.exists {
                self.username = document.data()?["username"] as? String ?? ""
                self.tableView.reloadData()
            }
        }
    }
  • fetchUserProfileメソッド:現在ログインしているユーザーのプロフィール情報をFirestoreから取得し、usernameプロパティに保存。

友達リスト取得処理

    func fetchFriendsList() {
        // 友達リストを取得
        guard let userId = Auth.auth().currentUser?.uid else { return }
        let db = Firestore.firestore()
        db.collection("users").document(userId).getDocument { document, error in
            if let document = document, document.exists {
                self.friends = document.data()?["friends"] as? [String] ?? []
                self.fetchFriendUsernames() // 友達のユーザー名を取得
            }
        }
    }
  • fetchFriendsListメソッド:現在ログインしているユーザーの友達リストをFirestoreから取得し、friendsプロパティに保存。取得後、fetchFriendUsernamesメソッドを呼び出して友達のユーザー名を取得。

友達のユーザー名取得処理

    func fetchFriendUsernames() {
        // 友達のユーザー名を取得
        let db = Firestore.firestore()
        let dispatchGroup = DispatchGroup()
        friendUsernames = [] // リセット
        
        for friendId in friends {
            dispatchGroup.enter()
            db.collection("users").document(friendId).getDocument { document, error in
                if let document = document, document.exists {
                    let friendUsername = document.data()?["username"] as? String ?? ""
                    self.friendUsernames.append(friendUsername)
                }
                dispatchGroup.leave()
            }
        }
        
        dispatchGroup.notify(queue: .main) {
            self.tableView.reloadData()
        }
    }
  • fetchFriendUsernamesメソッド:friendsプロパティに格納されている友達のユーザーIDを使って、各ユーザーのユーザー名をFirestoreから取得し、friendUsernamesプロパティに保存。

友達追加処理

    func addFriendToDatabase(addedUsername: String) {
        // 友達をデータベースに追加
        guard let userId = Auth.auth().currentUser?.uid else { return }
        let db = Firestore.firestore()
        db.collection("users").whereField("username", isEqualTo: addedUsername).getDocuments { snapshot, error in
            if let documents = snapshot?.documents, !documents.isEmpty {
                if let friendId = documents.first?.documentID {
                    db.collection("users").document(userId).updateData([
                        "friends": FieldValue.arrayUnion([friendId])
                    ]) { error in
                        if let error = error {
                            print("友達追加に失敗しました: \(error)")
                        } else {
                            self.friends.append(friendId)
                            self.fetchFriendUsernames()
                        }
                    }
                }
            } else {
                print("ユーザーが見つかりませんでした")
            }
        }
    }
  • addFriendToDatabaseメソッド:入力されたユーザー名を使ってFirestoreでユーザーを検索し、見つかったユーザーを現在のユーザーの友達リストに追加。

UITableViewデリゲートおよびデータソースメソッド

    func numberOfSections(in tableView: UITableView) -> Int {
        return 2 // プロフィールセクションと友達セクション
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if section == 0 {
            return 1 // プロフィールセクションには自分のプロフィールのみ
        } else {
            return friendUsernames.count // 友達セクションには友達の数
        }
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "FriendCell", for: indexPath)
        if indexPath.section == 0 {
            cell.textLabel?.text = username
        } else {
            cell.textLabel?.text = friendUsernames[indexPath.row]
        }
        return cell
    }
    
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        if section == 0 {
            return "プロフィール"
        } else {
            return "友達一覧"
        }
    }
}
  • numberOfSections(in tableView:)メソッド:テーブルビューのセクション数を2に設定。
  • tableView(_:numberOfRowsInSection:)メソッド:各セクションの行数を設定。プロフィールセクションは1行、友達セクションは友達の数だけ行を表示。
  • tableView(_:cellForRowAt:)メソッド:各行のセルを設定。プロフィールセクションには自分のユーザー名を表示し、友達セクションには友達のユーザー名を表示。
  • tableView(_:titleForHeaderInSection:)メソッド:各セクションのヘッダーに表示するタイトルを設定。プロフィールセクションには「プロフィール」、友達セクションには「友達一覧」を表示。

全量

import UIKit
import FirebaseFirestore
import FirebaseAuth

class FriendsListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!

    var friends: [String] = [] // 友達のユーザーIDを格納する配列
    var friendUsernames: [String] = [] // 友達のユーザー名を格納する配列
    var username: String = "" // 自分のユーザー名

    override func viewDidLoad() {
        super.viewDidLoad()
        
        // ナビゲーションバーの設定
        navigationItem.title = "友達一覧"
        let addFriendButton = UIBarButtonItem(barButtonSystemItem: .add, target: self, action: #selector(addFriend))
        navigationItem.rightBarButtonItem = addFriendButton
        
        tableView.delegate = self
        tableView.dataSource = self
        
        fetchUserProfile()
        fetchFriendsList()
        
        // ダミーユーザーの作成
//        DummyUserCreator.createDummyUsers()
    }

    @IBAction func addFriend(_ sender: UIBarButtonItem) {
        // 友達追加処理
        let alert = UIAlertController(title: "友達追加", message: "友達のユーザー名を入力してください", preferredStyle: .alert)
        alert.addTextField { textField in
            textField.placeholder = "ユーザー名"
        }
        alert.addAction(UIAlertAction(title: "キャンセル", style: .cancel, handler: nil))
        alert.addAction(UIAlertAction(title: "追加", style: .default, handler: { _ in
            if let addedUsername = alert.textFields?.first?.text, !addedUsername.isEmpty {
                self.addFriendToDatabase(addedUsername: addedUsername)
            }
        }))
        present(alert, animated: true, completion: nil)
    }
    
    @IBAction func logoutButtonTapped(_ sender: UIBarButtonItem) {
        do {
            try Auth.auth().signOut()
            // ログイン画面に遷移
            let storyboard = UIStoryboard(name: "Main", bundle: nil)
            let loginViewController = storyboard.instantiateViewController(withIdentifier: "LoginViewController")
            if let window = UIApplication.shared.windows.first {
                window.rootViewController = loginViewController
                UIView.transition(with: window, duration: 0.5, options: .transitionCrossDissolve, animations: nil, completion: nil)
            }
        } catch let signOutError as NSError {
            print("Error signing out: %@", signOutError)
        }
    }

    func fetchUserProfile() {
        // ユーザーのプロフィールを取得
        guard let userId = Auth.auth().currentUser?.uid else { return }
        let db = Firestore.firestore()
        db.collection("users").document(userId).getDocument { document, error in
            if let document = document, document.exists {
                self.username = document.data()?["username"] as? String ?? ""
                self.tableView.reloadData()
            }
        }
    }

    func fetchFriendsList() {
        // 友達リストを取得
        guard let userId = Auth.auth().currentUser?.uid else { return }
        let db = Firestore.firestore()
        db.collection("users").document(userId).getDocument { document, error in
            if let document = document, document.exists {
                self.friends = document.data()?["friends"] as? [String] ?? []
                self.fetchFriendUsernames() // 友達のユーザー名を取得
            }
        }
    }
    
    func fetchFriendUsernames() {
        // 友達のユーザー名を取得
        let db = Firestore.firestore()
        let dispatchGroup = DispatchGroup()
        friendUsernames = [] // リセット
        
        for friendId in friends {
            dispatchGroup.enter()
            db.collection("users").document(friendId).getDocument { document, error in
                if let document = document, document.exists {
                    let friendUsername = document.data()?["username"] as? String ?? ""
                    self.friendUsernames.append(friendUsername)
                }
                dispatchGroup.leave()
            }
        }
        
        dispatchGroup.notify(queue: .main) {
            self.tableView.reloadData()
        }
    }

    func addFriendToDatabase(addedUsername: String) {
        // 友達をデータベースに追加
        guard let userId = Auth.auth().currentUser?.uid else { return }
        let db = Firestore.firestore()
        db.collection("users").whereField("username", isEqualTo: addedUsername).getDocuments { snapshot, error in
            if let documents = snapshot?.documents, !documents.isEmpty {
                if let friendId = documents.first?.documentID {
                    db.collection("users").document(userId).updateData([
                        "friends": FieldValue.arrayUnion([friendId])
                    ]) { error in
                        if let error = error {
                            print("友達追加に失敗しました: \(error)")
                        } else {
                            self.friends.append(friendId)
                            self.fetchFriendUsernames()
                        }
                    }
                }
            } else {
                print("ユーザーが見つかりませんでした")
            }
        }
    }
    
    // UITableViewデリゲートおよびデータソースメソッド
    func numberOfSections(in tableView: UITableView) -> Int {
        return 2 // プロフィールセクションと友達セクション
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if section == 0 {
            return 1 // プロフィールセクションには自分のプロフィールのみ
        } else {
            return friendUsernames.count // 友達セクションには友達の数
        }
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "FriendCell", for: indexPath)
        if indexPath.section == 0 {
            cell.textLabel?.text = username
        } else {
            cell.textLabel?.text = friendUsernames[indexPath.row]
        }
        return cell
    }
    
    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        if section == 0 {
            return "プロフィール"
        } else {
            return "友達一覧"
        }
    }
}
0
1
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
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?