#はじめに
Firebaseとチャット機能の学習としてLINEのクローンアプリを作成しているのですが、その際のFirebaseを用いた会員登録の実装を備忘録として投稿します。
初学者ですので訂正点ございましたら、ご指摘よろしくお願いします。
#概要
今回、会員登録機能を実装するにあたり下記のように順序立てしました。
1.FirebaseAuth
へユーザー登録
2.FirebaseStorage
へプロフィール画像を登録
3.FirebaseFirestore
へユーザー情報を登録
-
メールアドレス
、パスワード
、ユーザーネーム
の全てに記入がされていれば登録処理可(新規登録ボタン有効)としています。 -
1.
の処理の際に、承認メールを使い会員登録を完了させてから2.
の処理に移るのがよくある手法かと思われますが、今回の目的が学習のためでしたのでその辺りは考慮しておりません。
#実行環境
【Xcode】Version 12.0.1
【Swift】Version 5.3
【CocoaPods】version 1.9.3
【Firebase】version 6.29.0
#実装コード
import Foundation
import Firebase
//delegateはweak参照したいため、classを継承する
protocol SignUpModelDelegate: class {
func createImageToFirestorageAction()
func createUserToFirestoreAction(fileName: String?)
func completedRegisterUserInfoAction()
}
class SignUpModel {
// delegateはメモリリークを回避するためweak参照する
weak var delegate: SignUpModelDelegate?
func createUser(email: String, password: String) {
// FirebaseAuthへ保存
Auth.auth().createUser(withEmail: email, password: password) { (res, err) in
if let err = err {
print("FirebaseAuthへの保存に失敗しました。\(err)")
// ユーザー情報の登録が失敗した時の処理
return
}
print("FirebaseAuthへの保存に成功しました。")
// FirebaseAuthへ保存完了 -> FirebaseStorageへ保存処理
self.delegate?.createImageToFirestorageAction()
}
}
func creatrImage(fileName: String, uploadImage: Data) {
// FirebaseStorageへ保存
let storageRef = Storage.storage().reference().child("profile_image").child(fileName)
storageRef.putData(uploadImage, metadata: nil) { (metadate, err) in
if let err = err {
print("Firestorageへの保存に失敗しました。\(err)")
// ユーザー情報の登録が失敗した時の処理
return
}
print("Firestorageへの保存に成功しました。")
// FirebaseStorageへ保存完了 -> FirebaseFirestoreへ保存処理
self.delegate?.createUserToFirestoreAction(fileName: fileName)
}
}
func createUserInfo(uid: String, docDate: [String : Any]) {
// FirebaseFirestoreへ保存
Firestore.firestore().collection("users").document(uid).setData(docDate as [String : Any]) { (err) in
if let err = err {
print("Firestoreへの保存に失敗しました。\(err)")
// ユーザー情報の登録が失敗した時の処理
return
}
print("Firestoreへの保存に成功しました。")
// ユーザー情報の登録が完了した時の処理
self.delegate?.completedRegisterUserInfoAction()
}
}
}
import UIKit
import Firebase
import FirebaseStorage
import IQKeyboardManagerSwift
class SignUpViewController: UIViewController {
@IBOutlet weak var profileImageButton: UIButton!
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var userNameTextField: UITextField!
@IBOutlet weak var signUpButton: UIButton!
let signUpModel = SignUpModel()
override func viewDidLoad() {
super.viewDidLoad()
IQKeyboardManager.shared.enable = true
emailTextField.delegate = self
passwordTextField.delegate = self
userNameTextField.delegate = self
signUpModel.delegate = self
// 画面UIについての処理
setupUI()
}
// 画面UIについての処理
func setupUI() {
signUpButton.layer.cornerRadius = 3
signUpButton.isEnabled = false
profileImageButton.layer.masksToBounds = true
profileImageButton.layer.cornerRadius = 75
profileImageButton.layer.borderColor = UIColor.lightGray.cgColor
profileImageButton.layer.borderWidth = 0.1
}
// プロフィール画像の選択(フォトライブラリーへ遷移)
@IBAction func profileImageButtonAction(_ sender: Any) {
let imagePickerController = UIImagePickerController()
imagePickerController.allowsEditing = true
imagePickerController.delegate = self
self.present(imagePickerController, animated: true, completion: nil)
}
// 新規登録処理
@IBAction func signUpButtonAction(_ sender: Any) {
guard let email = emailTextField.text,
let password = passwordTextField.text
else { return }
// FirebaseAuthへ保存
signUpModel.createUser(email: email, password: password)
}
# ・・・省略・・・
// プロフィール画像をFirebaseStorageへ保存する処理
private func createImageToFirestorage() {
// プロフィール画像が設定されている場合の処理
if let image = self.profileImageButton.imageView?.image {
let uploadImage = image.jpegData(compressionQuality: 0.5)
let fileName = NSUUID().uuidString
// FirebaseStorageへ保存
signUpModel.creatrImage(fileName: fileName, uploadImage: uploadImage!)
} else {
print("プロフィール画像が設定されていないため、デフォルト画像になります。")
// User情報をFirebaseFirestoreへ保存
self.createUserToFirestore(profileImageName: nil)
}
}
// User情報をFirebaseFirestoreへ保存する処理
private func createUserToFirestore(profileImageName: String?) {
guard let email = Auth.auth().currentUser?.email,
let uid = Auth.auth().currentUser?.uid,
let userName = self.userNameTextField.text
else { return }
// 保存内容を定義する(辞書型)
let docData = ["email": email,
"userName": userName,
"profileImageName": profileImageName,
"createdAt": Timestamp()] as [String : Any?]
// FirebaseFirestoreへ保存
signUpModel.createUserInfo(uid: uid, docDate: docData as [String : Any])
}
}
extension SignUpViewController: UINavigationControllerDelegate, UIImagePickerControllerDelegate {
// 写真が選択された時に呼ばれるメソッド
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let editedImage = info[.editedImage] as? UIImage {
profileImageButton.setImage(editedImage.withRenderingMode(.alwaysOriginal), for: .normal)
} else if let originalImage = info[.originalImage] as? UIImage {
profileImageButton.setImage(originalImage.withRenderingMode(.alwaysOriginal), for: .normal)
}
dismiss(animated: true, completion: nil)
}
}
extension SignUpViewController: UITextFieldDelegate {
// textFieldでテキスト選択が変更された時に呼ばれるメソッド
func textFieldDidChangeSelection(_ textField: UITextField) {
// textFieldが空かどうかの判別するための変数(Bool型)で定義
let emailIsEmpty = emailTextField.text?.isEmpty ?? true
let passwordIsEmpty = passwordTextField.text?.isEmpty ?? true
let userNameIsEmpty = userNameTextField.text?.isEmpty ?? true
// 全てのtextFieldが記入済みの場合の処理
if emailIsEmpty || passwordIsEmpty || userNameIsEmpty {
signUpButton.isEnabled = false
signUpButton.backgroundColor = UIColor.systemGray2
} else {
signUpButton.isEnabled = true
signUpButton.backgroundColor = UIColor(named: "lineGreen")
}
}
// textField以外の部分を押したときキーボードが閉じる
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
}
extension SignUpViewController: SignUpModelDelegate {
// FirebaseAuthへ保存完了 -> FirebaseStorageへ保存処理
func createImageToFirestorageAction() {
print("FirebaseAuthへの保存に成功しました。")
self.createImageToFirestorage()
}
// FirebaseStorageへ保存完了 -> FirebaseFirestoreへ保存処理
func createUserToFirestoreAction(fileName: String?) {
print("Firestorageへの保存に成功しました。")
self.createUserToFirestore(profileImageName: fileName)
}
// ユーザー情報の登録が完了した時の処理
func completedRegisterUserInfoAction() {
// ChatListViewControllerへ画面遷移
let storyboard = UIStoryboard(name: "ChatList", bundle: nil)
let chatListVC = storyboard.instantiateViewController(withIdentifier: "ChatListVC") as! ChatListViewController
let nav = UINavigationController(rootViewController: chatListVC)
nav.modalPresentationStyle = .fullScreen
nav.modalTransitionStyle = .crossDissolve
self.present(nav, animated: true, completion: nil)
}
}
Firebaseに関しての処理にフォーカスを当てていますので下記項目の説明は省かせていただきます。
- エラー処理について
-
UIActivityIndicatorView
について -
UIImagePickerController
について -
IQKeyboardManagerSwift
について(詳細はこちら参照ください。)
#準備
###①前提条件
下記項目が完了しているのを前提条件としまして進めていきます。
- Firebaseプロジェクトの作成
- XcodeでのFirebaseSDK組み込み
このあたりがまだという方は、ドキュメントを参照よろしくお願いします。
###②Xcode側の準備
Podfile
に以下を追加して、ターミナルでpod install
をします。
pod 'Firebase/Analytics'
pod 'Firebase/Auth'
pod 'Firebase/Core'
pod 'Firebase/Firestore'
pod 'Firebase/Storage'
pod 'FirebaseUI/Storage'
###③Firebase側の準備
上図のようにSign-in method
タブからメール/パスワード
を選択します。鉛筆のアイコンで編集画面を開きます。
有効にしたら保存します。準備としてはこれで終わりになります。
#実装詳細①(FirebaseAuth編)
// 新規登録処理
@IBAction func signUpButtonAction(_ sender: Any) {
guard let email = emailTextField.text,
let password = passwordTextField.text
else { return }
// FirebaseAuthへ保存
signUpModel.createUser(email: email, password: password)
}
func createUser(email: String, password: String) {
// FirebaseAuthへ保存
Auth.auth().createUser(withEmail: email, password: password) { (res, err) in
if let err = err {
print("FirebaseAuthへの保存に失敗しました。\(err)")
// ユーザー情報の登録が失敗した時の処理
return
}
print("FirebaseAuthへの保存に成功しました。")
// FirebaseAuthへ保存完了 -> FirebaseStorageへ保存処理
self.delegate?.createImageToFirestorageAction()
}
}
#実装詳細②(FirebaseStorage編)
// FirebaseAuthへ保存完了 -> FirebaseStorageへ保存処理
func createImageToFirestorageAction() {
print("FirebaseAuthへの保存に成功しました。")
self.createImageToFirestorage()
}
// プロフィール画像をFirebaseStorageへ保存する処理
private func createImageToFirestorage() {
// プロフィール画像が設定されている場合の処理
if let image = self.profileImageButton.imageView?.image {
// 画像を圧縮
let uploadImage = image.jpegData(compressionQuality: 0.5)
// ユニークなIDを取得
let fileName = NSUUID().uuidString
// FirebaseStorageへ保存
signUpModel.creatrImage(fileName: fileName, uploadImage: uploadImage!)
} else {
print("プロフィール画像が設定されていないため、デフォルト画像になります。")
// User情報をFirebaseFirestoreへ保存
self.createUserToFirestore(profileImageName: nil)
}
}
func creatrImage(fileName: String, uploadImage: Data) {
// FirebaseStorageへ保存
let storageRef = Storage.storage().reference().child("profile_image").child(fileName)
storageRef.putData(uploadImage, metadata: nil) { (metadate, err) in
if let err = err {
print("Firestorageへの保存に失敗しました。\(err)")
// ユーザー情報の登録が失敗した時の処理
return
}
print("Firestorageへの保存に成功しました。")
// FirebaseStorageへ保存完了 -> FirebaseFirestoreへ保存処理
self.delegate?.createUserToFirestoreAction(fileName: fileName)
}
}
#実装詳細③(FirebaseFirestore編)
// FirebaseStorageへ保存完了 -> FirebaseFirestoreへ保存処理
func createUserToFirestoreAction(fileName: String?) {
print("Firestorageへの保存に成功しました。")
self.createUserToFirestore(profileImageName: fileName)
}
// User情報をFirebaseFirestoreへ保存する処理
private func createUserToFirestore(profileImageName: String?) {
guard let email = Auth.auth().currentUser?.email,
let uid = Auth.auth().currentUser?.uid,
let userName = self.userNameTextField.text
else { return }
// 保存内容を定義する(辞書型)
let docData = ["email": email,
"userName": userName,
"profileImageName": profileImageName,
"createdAt": Timestamp()] as [String : Any?]
// FirebaseFirestoreへ保存
signUpModel.createUserInfo(uid: uid, docDate: docData as [String : Any])
}
func createUserInfo(uid: String, docDate: [String : Any]) {
// FirebaseFirestoreへ保存
Firestore.firestore().collection("users").document(uid).setData(docDate as [String : Any]) { (err) in
if let err = err {
print("Firestoreへの保存に失敗しました。\(err)")
// ユーザー情報の登録が失敗した時の処理
return
}
print("Firestoreへの保存に成功しました。")
// ユーザー情報の登録が完了した時の処理
self.delegate?.completedRegisterUserInfoAction()
}
}
// ユーザー情報の登録が完了した時の処理
func completedRegisterUserInfoAction() {
// ChatListViewControllerへ画面遷移
# ・・・省略・・・
}
#参考