この記事でやること
LINEのようなチャットアプリの友達一覧画面とその機能を実装する。画面は自分・友達一覧を表示し、友達追加ボタン・ログアウトボタンを設置する。機能は自分表示・友達表示・友達追加・ログアウトを実現する。
環境
- Xcode 15.0.1
- Swift 5
前提
- Firebaseを使用したユーザーの登録・ログイン機能が実装済であること
※下記参照 - ユーザーの管理はFirestoreで行っており、データ構成が以下のようになっていること
users
|-- userID1
|-- username: "user1"
|-- email: "user1@example.com"
|-- profileImageURL: "http://example.com/user1.jpg"
|-- friends: ["userID2", "userID3"]
Story Board
- ControllerViewを設置
- EmbeddedInからNavigationControllerを選択
- ControllerViewにTableViewとCellを表示
- NavigationBarの真ん中にLabel("友達一覧")を、右側と左側にそれぞれButtonを設置する
- TableViewを選択し、右側のインスペクターパネルの中にある「Connections Inspector」(接続インスペクタ)を開き、DataSourceおよびDelegateをControllerViewにドロップする
- FirendsListViewController作成後にTableViewをアウトレット接続し、各ボタンをアクション接続する
下記画像参照(画像では Tab Bar も実装しているが、この記事では必要ない)
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 "友達一覧"
}
}
}