はじめに
今回、Firebaseを使ってiOS版簡易SNSを作成する記事になっています。
この記事では タイムライン上に編集可能なユーザー名を表示できる ・ ユーザーがログアウトできる などを行うまでの部分を行なっています。
また、前回の続きという扱いで進めていきます。
タイムラインにユーザー名を表示
AppUserクラスをデータベースに保存 & 取得
- TimelineViewControllerのviewWillAppearメソッドでAppUserをデータベースに保存。
usersコレクションの中にme.userID
をキーとして保存します。この時点ではユーザー名は作成していないのでuserID
のみ保存します。
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
(省略)
// 追加
database.collection("users").document(me.userID).setData([
"userID": me.userID
], merge: true)
}
- Postクラスの
userName
にデフォルト値をいれます。
仕様として、Post.userName
に値が入っていなければ匿名
と表示するようにします。
このとき、現在の実装だとas! String
なので、Post.userName
がnilであるとクラッシュしてしまうので、Postクラスを次のように変更します。
userName = data["userName"] as! String
userName = data["userName"] as? String ?? "匿名"
- 加えて、AppUserをデータベースから取得するコードを同じくviewWillAppearメソッド内に記述します。
// 追加
database.collection("users").document(me.userID).getDocument { (snapshot, error) in
if error == nil, let snapshot = snapshot, let data = snapshot.data() {
self.me = AppUser(data: data)
}
}
コラム: Firestoreからデータを取得するコード
- 1つのドキュメントを取得する場合
Firestore.firestore().collection("コレクション名").document("ドキュメント名").getDocument { (snapshot, error) in
// ここに通信後の処理を書く。 snapshot.data()でドキュメントのデータが取得可能
}
- コレクション内の全てのドキュメントを取得する場合
Firestore.firestore.collection("コレクション名").getDocuments { (snapshots, error) in
// ここに通信後の処理を書く。
// snapshot.documentsで全てのドキュメントが取得できる。
// document.data()でドキュメントのデータが取得可能
for document in snapshot!.documents {
let data = document.data()
}
}
セル1つ1つに対して、ユーザーデータを取得し、ユーザー名を表示する。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
cell.textLabel?.text = postArray[indexPath.row].content
// 追加。それぞれの記事を投稿したユーザーをPostクラスのsenderIDを元に取得している。
database.collection("users").document(postArray[indexPath.row].senderID).getDocument { (snapshot, error) in
if error == nil, let snapshot = snapshot, let data = snapshot.data() {
let appUser = AppUser(data: data)
cell.detailTextLabel?.text = appUser.userName // 今回は、ユーザー名をdetailTextLabelに表示。
}
}
return cell
}
細かくみていきます。
- 記事を投稿したデータの取得
これは、取得したいユーザーのIDをキーとしているドキュメントをusers
コレクション内から指定してあげる必要があります。
postArray[indexPath.row].senderID
これでその投稿に適したユーザーIDを取得することは可能です。
なので、以下のように書くことでその投稿に適切な投稿者のデータを取ってこれます。
database.collection("users").document(postArray[indexPath.row].senderID).getDocument { (snapshot, error) in
}
- 取得したユーザー名をセルに反映
今回は、デフォルトのセルを使用しています。柔軟性は低いので実際の場面ではTimelineTableViewCell
などを作成してUIをカスタマイズしていくことが好ましいですが、現状はデフォルトのセルを使用します。ここでcell.detailTextLabel
というものを使用します。
detailTextLabelの作成の仕方
下のようにDetail
が追加されていればうまくいっています。
用意したdetailTextLabel
にユーザー名を表示する。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
cell.textLabel?.text = postArray[indexPath.row].content
database.collection("users").document(postArray[indexPath.row].senderID).getDocument { (snapshot, error) in
// ここを追加。
// エラーがnilで、データが入っていることを確認。
if error == nil, let snapshot = snapshot, let data = snapshot.data() {
let appUser = AppUser(data: data)
cell.detailTextLabel?.text = appUser.userName
}
}
return cell
}
- 現在の状態
ユーザー情報変更画面を作成
今回は、TimelineViewControllerに新たにスワイプジェスチャーを作成して、下から上にスワイプを行うと、ユーザー情報変更画面に遷移するようにしたいと思います。
- Main.storyboardに
UIViewController
を1つ追加し以下の部品を追加します。
部品名 | 個数 |
---|---|
UILabel | 1 |
UITextField | 1 |
UIButton | 3 |
以下のように配置してみます。大体自由で大丈夫です。
-
TimelineViewControllerから新しく作成した画面をセグエで繋ぎます。セグエのIdentifierは
Settings
とします。 -
SettingsViewController
を作成・編集
まずは、以下の状態から始めていきます。
import UIKit
import Firebase
class SettingsViewController: UIViewController, UITextFieldDelegate {
@IBOutlet var userNameTextField: UITextField! // 変更するユーザー名を入力するところ
override func viewDidLoad() {
super.viewDidLoad()
userNameTextField.delegate = self // delegate指定
}
// returnキーを押したときの処理
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder() // キーボードを閉じる
return true
}
// 前の画面に戻るボタン
@IBAction func back() {
dismiss(animated: true, completion: nil)
}
// 保存ボタンを押したときに呼ばれる。
@IBAction func save() {
}
// ログアウトボタンを押したときに呼ばれる。
@IBAction func logout() {
}
}
画面遷移を行う
-
1.5秒間画面を長押ししたときに
TimelineViewController
からSettingsViewController
に画面遷移する。
まずは、長押しを検知するためにUILongPressGestureRecognizer
を追加し、view
(画面全体)にジェスチャーを追加します。
override func viewDidLoad() {
super.viewDidLoad()
// ここから下を追加
let press = UILongPressGestureRecognizer(target: self, action: #selector(pressScreen))
press.minimumPressDuration = 1.5
view.isUserInteractionEnabled = true
view.addGestureRecognizer(press)
}
次に、pressScreen
メソッドを実装していきます。と言っても内容は簡単でperformSegue
を呼ぶだけです。Identifier
は先ほど設定したSettings
にし、sender
は次の画面でもme
(ユーザー自身の情報)っは使用したいので、me
を引数に渡します。
@objc
func pressScreen() {
performSegue(withIdentifier: "Settings", sender: me)
}
その次に、prepare
メソッドを呼んで、値渡しの処理を書いていきます。
現在、prepareメソッドは以下のようになっていると思います。
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let destination = segue.destination as! AddViewController
destination.me = sender as! AppUser
}
これを次のように変更します。
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Add" {
let destination = segue.destination as! AddViewController
destination.me = sender as! AppUser
} else if segue.identifier == "Settings" {
let destination = segue.destination as! SettingsViewController
destination.me = me
}
}
segue.identifierでどのセグエが呼ばれていたかを分けることができます。これは2つ以上のセグエが現在のViewControllerから呼ばれる可能性がある場合に有効です。
-
SettingsViewController
にme
プロパティを追加します。
class SettingsViewController: UIViewController, UITextFieldDelegate {
(省略)
var me: AppUser! // 追加
(省略)
}
現在のユーザー名を初期値としてUITextField.text
に設定します。
変更画面で今のユーザー名を間違えないようにユーザー名を初期値としてテキストフィールドに表示します。
class SettingsViewController: UIViewController, UITextFieldDelegate {
@IBOutlet var userNameTextField: UITextField! // 変更するユーザー名を入力するところ
var me: AppUser!
override func viewDidLoad() {
super.viewDidLoad()
userNameTextField.delegate = self // デリゲートを指定
userNameTextField.text = me.userName // 追加: 現在のユーザー名をテキストに表示するコード
}
}
変更したユーザー名を保存する
@IBAction func save() { }
にコードを追加していきます。
//
// SettingsViewController.swift
// SNSApp
//
// Created by Fumiya Tanaka on 2019/09/02.
// Copyright © 2019 Fumiya Tanaka. All rights reserved.
//
import UIKit
import Firebase
class SettingsViewController: UIViewController, UITextFieldDelegate {
(省略)
// 保存ボタンを押したときに呼ばれる。
@IBAction func save() {
let newUserName = userNameTextField.text!
Firestore.firestore().collection("users").document(me.userID).setData([
"userName": newUserName
], merge: true) { error in // ここの merge: true がポイント
if error == nil {
self.dismiss(animated: true, completion: nil) // errorがなく、正常に終了していたらタイムラインの画面に戻る
}
}
}
(省略)
}
- コードのミソは、
merge: true
です。
merge
をtrueにすると、データを部分的に更新することができます。
update
だと、更新されないフィールドを消してしまいます。
user1:
"a": 1,
"b": 2,
"c": 3,
これを "c": 4
にアップデートする時に、merge: true
にすると、
user1:
"a": 1,
"b": 2,
"c": 4,
となって良い感じですが、updateData
を使用すると
user1:
"c": 4,
となって、更新されなかったフィールドは残りません。注意が必要ですが基本的にはmerge: true
で問題ないと思います。
logout機能を追加
-
@IBAction func logout() { }
にコードを追加していきます。
ログアウトする処理は下の一行で可能です。
try? Auth.auth().signOut()
なので、以下のようにコードを書きます。
@IBAction func logout() {
try? Auth.auth().signOut()
let accountViewController = UIStoryboard(name: "Main", bundle: nil).instantiateInitialViewController() as! AccountViewController
present(accountViewController, animated: true, completion: nil)
}
StoryboardでSettingsViewControllerの関連付けを行う
- クラスを
SettingsViewController
に設定する。
- それぞれの部品に対して関連付けを行う。
実行してみましょう。
現在のDemo
ここで、気づいた方もいるかもしれませんが、このアプリは現在ログイン機能がないのにログアウト機能だけがある状態です。
また、
Auth.auth.createUser
メソッドは既に使用されているメールアドレスでの新規登録ができないので、ログイン機能(サインイン機能)を実装していきます。
サインイン機能
サインイン画面の作成・Segueの作成
- 以下のGifのように新しく作成したViewControllerに
AccountViewController
からSegueを作成します。
- AccountViewController.swiftを編集
AccountViewControllerのprepareメソッドを以下のように編集します。
これによって、Timeline画面に遷移するときのみ、値渡しを行うことができます。
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Timeline" {
let nextViewController = segue.destination as! TimelineViewController
let user = sender as! User
nextViewController.me = AppUser(data: ["userID": user.uid])
}
}
- UI部品を配置します。
- TimelineViewControllerにSegueで繋ぎます。SegueのIdentifierを
Timeline
に設定します。
SignInViewControllerを作成
SignInViewController.swift
import UIKit
import Firebase
class SignInViewController: UIViewController, UITextFieldDelegate {
@IBOutlet var emailTextField: UITextField!
@IBOutlet var passwordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
emailTextField.delegate = self
passwordTextField.delegate = self
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "Timeline" {
let user = sender as! User
let destination = segue.destination as! TimelineViewController
destination.me = AppUser(data: ["userID": user.uid])
}
}
@IBAction func tappedSignInButton() {
let email = emailTextField.text!
let password = passwordTextField.text!
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if error == nil, let result = result, result.user.isEmailVerified {
self.performSegue(withIdentifier: "Timeline", sender: result.user)
}
}
}
}
基本的な部分はAccountViewController.swift
とほとんど同じです。
違いとしては、
Auth.auth.createUser
で新規アカウント登録なのか、Auth.auth.signIn
でサインインするのかという違いです。
これで、関連付けを行えばサインインが実装できました。ログアウトができるけど、サインインができないという変なアプリではなくりました!
最後に
この記事を通して
- Userの表示名を変更
- ログアウト・サインイン処理
といったことが可能になったと思います。
いいね機能やブロック機能やプロフィール画像などまだSNSとしてやりきれていない部分が多いので、緩く残りを更新して行けたらいいなと感じています。最後まで読んでいただきありがとうございました。