Firebaseを用いたユーザーログイン機能のエラーについて
解決したいこと
Firebaseを用いたemailとpasswordでのログイン認証アプリを作成中です。
現在アカウントの作成はできるのですが、
既存のアカウントを使用したログインが下記エラーで止まってしまうためできません
ググって調べても情報もあまりなく、解決方法を教えていただきたいです。
よろしくお願い致します。
発生している問題・エラー
@main の行で下記のエラーが出ている。
Thread 1: "-[youtubeLoginApp.LoginViewController tappedRegisterButton:]: unrecognized selector sent to instance 0x7fb2a8d32070"
出ているエラーメッセージを入力
tapped Login Button
2021-07-29 21:26:59.557052+0900 youtubeLoginApp[14321:374339] -[youtubeLoginApp.LoginViewController tappedRegisterButton:]: unrecognized selector sent to instance 0x7fd80257df30
2021-07-29 21:26:59.564312+0900 youtubeLoginApp[14321:374339] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[youtubeLoginApp.LoginViewController tappedRegisterButton:]: unrecognized selector sent to instance 0x7fd80257df30'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff20422fba __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007fff20193ff5 objc_exception_throw + 48
2 CoreFoundation 0x00007fff20431d2f +[NSObject(NSObject) instanceMethodSignatureForSelector:] + 0
3 UIKitCore 0x00007fff246f60f1 -[UIResponder doesNotRecognizeSelector:] + 292
4 CoreFoundation 0x00007fff204274cf ___forwarding___ + 1455
5 CoreFoundation 0x00007fff204297a8 _CF_forwarding_prep_0 + 120
6 UIKitCore 0x00007fff246c7937 -[UIApplication sendAction:to:from:forEvent:] + 83
7 UIKitCore 0x00007fff23fe845d -[UIControl sendAction:to:forEvent:] + 223
8 UIKitCore 0x00007fff23fe8780 -[UIControl _sendActionsForEvents:withEvent:] + 332
9 UIKitCore 0x00007fff23fe707f -[UIControl touchesEnded:withEvent:] + 500
10 UIKitCore 0x00007fff24703d01 -[UIWindow _sendTouchesForEvent:] + 1287
11 UIKitCore 0x00007fff24705b8c -[UIWindow sendEvent:] + 4792
12 UIKitCore 0x00007fff246dfc89 -[UIApplication sendEvent:] + 596
13 UIKitCore 0x00007fff247727ba __processEventQueue + 17124
14 UIKitCore 0x00007fff24768560 __eventFetcherSourceCallback + 104
15 CoreFoundation 0x00007fff20390ede __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
16 CoreFoundation 0x00007fff20390dd6 __CFRunLoopDoSource0 + 180
17 CoreFoundation 0x00007fff2039029e __CFRunLoopDoSources0 + 242
18 CoreFoundation 0x00007fff2038a9f7 __CFRunLoopRun + 875
19 CoreFoundation 0x00007fff2038a1a7 CFRunLoopRunSpecific + 567
20 GraphicsServices 0x00007fff2b874d85 GSEventRunModal + 139
21 UIKitCore 0x00007fff246c14df -[UIApplication _run] + 912
22 UIKitCore 0x00007fff246c639c UIApplicationMain + 101
23 libswiftUIKit.dylib 0x00007fff53fcbf42 $s5UIKit17UIApplicationMainys5Int32VAD_SpySpys4Int8VGGSgSSSgAJtF + 98
24 youtubeLoginApp 0x000000010bf4e30a $sSo21UIApplicationDelegateP5UIKitE4mainyyFZ + 122
25 youtubeLoginApp 0x000000010bf4e27e $s15youtubeLoginApp0C8DelegateC5$mainyyFZ + 46
26 youtubeLoginApp 0x000000010bf4e3a9 main + 41
27 libdyld.dylib 0x00007fff2025abbd start + 1
)
libc++abi: terminating with uncaught exception of type NSException
terminating with uncaught exception of type NSException
CoreSimulator 757.5 - Device: iPhone 11 (00FF170C-32E4-41F3-9FEA-D5E2323AA02F) - Runtime: iOS 14.5 (18E182) - DeviceType: iPhone 11
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[youtubeLoginApp.LoginViewController tappedRegisterButton:]: unrecognized selector sent to instance 0x7fd80257df30'
〜動作流れ〜
1.Xcodeでシュミレータでアプリを起動 (下記画像参照)
2.ログインをタップして画面を遷移
3.すでに登録してあるemailとpasswordを入力する
4.ログインをタップする
5.ログイン完了できず、エラーで止まってしまう。
該当するソースコード
AppDelegate.swift
import UIKit
import Firebase
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
FirebaseApp.configure()
return true
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
ViewController.swift
import UIKit
import Firebase
import PKHUD
struct User {
// 構造の変数定義はFireBaseから取得したデータ(変数:data)と同じ変数を使用すること
let name: String
let createdAt: Timestamp
let email: String
init(dic: [String: Any]) {
self.name = dic["name"] as! String
self.createdAt = dic["createdAt"] as! Timestamp
self.email = dic["email"] as! String
}
}
class ViewController: UIViewController {
@IBOutlet weak var registerButton: UIButton!
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var usernameTextField: UITextField!
@IBAction func tappedRegisterButton(_ sender: Any){
handleAuthToFirebase() // ???
print("登録ボタンを押したよ!")
}
@IBAction func tappedHaveAccountButton(_ sender: Any) {
let storyBoard = UIStoryboard(name: "Login", bundle: nil) // name: "Home" のところはエラーがでたので name: "Main" に変えた
let homeViewController = storyBoard.instantiateViewController(identifier: "LoginViewController") as! LoginViewController
navigationController?.pushViewController(homeViewController, animated: true)
// self.present(homeViewController, animated: true, completion: nil)
}
private func handleAuthToFirebase(){
HUD.show(.progress, onView: view)
guard let email = emailTextField.text else {return}
guard let password = passwordTextField.text else {return}
Auth.auth().createUser(withEmail: email, password: password) { (res, err) in
if let err = err {
print("認証情報の保存に失敗しました。\(err)") // "\(err)"はエスケープシーケンス -> 詳しくは"memo.swift"で
HUD.hide{ (_) in
HUD.flash(.error, delay: 1)
}
return
}
print("認証情報の保存に成功しました!")
self.addUserInfoToFirestore(email: email)
}
}
private func addUserInfoToFirestore(email: String){
guard let uid = Auth.auth().currentUser?.uid else { return }
guard let name = self.usernameTextField.text else { return }
let docData = ["email": email, "name": name, "createdAt":Timestamp()] as [String : Any]
let userRef = Firestore.firestore().collection("users").document(uid)
userRef.setData(docData) { (err) in // HP "FireBase"内で作成したデータベースのコレクション=colection ("users") = users
if let err = err {
print("FireBaseStoreの保存に失敗しました。\(err)") // "\(err)"はエスケープシーケンス -> 詳しくは"memo.swift"で
HUD.hide{ (_) in
HUD.flash(.success, delay: 1)
}
return
}
print("FireStoreの保存に成功しました!")
userRef.getDocument{ (snapshot, err) in // firebaseからデータを引っ張ってくる処理
if let err = err {
print("ユーザー情報の取得に失敗しました。 \(err)")
HUD.hide{ (_) in
HUD.flash(.error, delay: 1)
}
return
}
guard let data = snapshot?.data() else { return }
let user = User.init(dic: data)
print("ユーザー情報の取得に成功しました。\(user.name)")
HUD.hide{ (_) in
// HUD.flash(.success, delay: 1)
HUD.flash(.success, onView: self.view, delay: 1) { (_) in
self.presentToHomeViewController(user: user)
}
}
// "present" の引数について
// 第一引数: 遷移先のUIViewController
// 第二引数: アニメーションの指定(true: アニメーション有 / false: アニメーション無)
// 第三引数: コールバック関数
}
}
}
private func presentToHomeViewController(user: User) {
let storyBoard = UIStoryboard(name: "Main", bundle: nil) // name: "Home" のところはエラーがでたので name: "Main" に変えた
let homeViewController = storyBoard.instantiateViewController(identifier: "HomeViewController") as! HomeViewController
homeViewController.user = user
homeViewController.modalPresentationStyle = .fullScreen
self.present(homeViewController, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
registerButton.isEnabled = false
registerButton.layer.cornerRadius = 10
registerButton.backgroundColor = UIColor.rgb(red: 255, green: 221, blue: 187)
emailTextField.delegate = self
passwordTextField.delegate = self
usernameTextField.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(showKeyboard), name: UIResponder.keyboardWillShowNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(hideKeyboard), name: UIResponder.keyboardWillHideNotification, object: nil)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
navigationController?.navigationBar.isHidden = true
}
@objc func showKeyboard(notification: Notification) {
let keyboardFrame = (notification.userInfo![UIResponder.keyboardFrameEndUserInfoKey] as AnyObject).cgRectValue
guard let keyboardMinY = keyboardFrame?.minY else { return }
let registerButtonMaxY = registerButton.frame.maxY
let distance = registerButtonMaxY - keyboardMinY + 20
let transform = CGAffineTransform(translationX: 0, y: -distance)
// キーボードで入力場所が隠れてしまわないように画面を上にずらす処理 登録ボタン下側とキーボード上側
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [], animations: {
self.view.transform = transform
})
print("keyboardMinY : ", keyboardMinY, "registerButtonMaxY: ", registerButtonMaxY) // 基準の位置がきちんと取得できているか
print("keyboardFrame : ", keyboardFrame) // 値が取得できているか確認
print("showKeyboard is showing.") // キーボードがきちんと見える動作をしているかの確認
}
@objc func hideKeyboard() {
// キーボードを閉じた時に画面位置を元の高さに戻す。
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: [], animations: {
self.view.transform = .identity
})
print("hideKeyboard is hide")
}
// 画面内のキーボード以外のところを選択するとキーボードが消える
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
}
extension ViewController: UITextFieldDelegate {
func textFieldDidChangeSelection(_ textField: UITextField) {
print("textField.text: ", textField.text)
// ?? true は emailTextField.text?.isEmpty が nil であった場合に強制的に true にさせる指定
let emailIsEmpty = emailTextField.text?.isEmpty ?? true
let passwordIsEmpty = passwordTextField.text?.isEmpty ?? true
let usernameIsEmpty = usernameTextField.text?.isEmpty ?? true
if emailIsEmpty || passwordIsEmpty || usernameIsEmpty {
registerButton.isEnabled = false
// UIColor.swift のファイルを作成し、その中で color の数値を定義する関数を記述
registerButton.backgroundColor = UIColor.rgb(red: 255, green: 221, blue: 187)
} else {
registerButton.isEnabled = true
registerButton.backgroundColor = UIColor.rgb(red: 255, green: 141, blue: 0)
}
print("textField.text: ", textField.text)
}
}
LoginViewController.swift
import UIKit
import Firebase
import PKHUD
class LoginViewController: UIViewController {
@IBOutlet weak var emailTextField: UITextField!
@IBOutlet weak var passwordTextField: UITextField!
@IBOutlet weak var loginButton: UIButton!
@IBOutlet weak var dontHaveAccountButton: UIButton!
@IBAction func tappedDontHaveAccountButton(_ sender: Any) {
// 戻るボタンを押した時、前の画面に戻る
navigationController?.popViewController(animated: true)
}
@IBAction func tappedLoginButton(_ sender: Any) {
HUD.show(.progress, onView: self.view)
print("tapped Login Button")
guard let email = emailTextField.text else { return }
guard let password = passwordTextField.text else { return }
Auth.auth().signIn(withEmail: email, password: password) { (res,err) in
if let err = err {
print("ログイン情報の取得に失敗しました: ", err)
return
}
print("ログインに成功しました。")
guard let uid = Auth.auth().currentUser?.uid else {return}
let userRef = Firestore.firestore().collection("users").document(uid)
userRef.getDocument{ (snapshot, err) in // firebaseからデータを引っ張ってくる処理
if let err = err {
print("ユーザー情報の取得に失敗しました。 \(err)")
HUD.hide{ (_) in
HUD.flash(.error, delay: 1)
}
return
}
guard let data = snapshot?.data() else { return }
let user = User.init(dic: data)
print("ユーザー情報の取得に成功しました。\(user.name)")
HUD.hide{ (_) in
// HUD.flash(.success, delay: 1)
HUD.flash(.success, onView: self.view, delay: 1) { (_) in
self.presentToHomeViewController(user: user)
}
}
// "present" の引数について
// 第一引数: 遷移先のUIViewController
// 第二引数: アニメーションの指定(true: アニメーション有 / false: アニメーション無)
// 第三引数: コールバック関数
}
}
}
private func presentToHomeViewController(user: User) {
let storyBoard = UIStoryboard(name: "Home", bundle: nil) // name: "Home" のところはエラーがでたので name: "Main" に変えた
let homeViewController = storyBoard.instantiateViewController(identifier: "HomeViewController") as! HomeViewController
homeViewController.user = user
homeViewController.modalPresentationStyle = .fullScreen
self.present(homeViewController, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
loginButton.layer.cornerRadius = 10
loginButton.isEnabled = false
loginButton.backgroundColor = UIColor.rgb(red: 255, green: 221, blue: 187)
emailTextField.delegate = self
passwordTextField.delegate = self
}
}
// MARK: - UITextFieldDelegate
extension LoginViewController: UITextFieldDelegate{
func textFieldDidChangeSelection(_ textField: UITextField) {
print("textField.text: ", textField.text)
// ?? true は emailTextField.text?.isEmpty が nil であった場合に強制的に true にさせる指定
let emailIsEmpty = emailTextField.text?.isEmpty ?? true
let passwordIsEmpty = passwordTextField.text?.isEmpty ?? true
if emailIsEmpty || passwordIsEmpty {
loginButton.isEnabled = false
// UIColor.swift のファイルを作成し、その中で color の数値を定義する関数を記述
loginButton.backgroundColor = UIColor.rgb(red: 255, green: 221, blue: 187)
} else {
loginButton.isEnabled = true
loginButton.backgroundColor = UIColor.rgb(red: 255, green: 141, blue: 0)
}
print("textField.text: ", textField.text)
}
}
HomeViewController.swift
import Foundation
import UIKit
import Firebase
class HomeViewController: UIViewController {
var user: User? {
didSet {
print("user?.name: ",user?.name)
}
}
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var emailLabel: UILabel!
@IBOutlet weak var dateLabel: UILabel!
@IBOutlet weak var logoutButtton: UIButton!
@IBAction func tappedLogoutButton(_ sender: Any) {
handleLogout()
}
private func handleLogout() {
do {
try Auth.auth().signOut()
presentToMainViewController()
}catch (let err){
print("ログアウトに失敗しました: \(err)")
}
}
override func viewDidLoad() {
super.viewDidLoad()
logoutButtton.layer.cornerRadius = 10
if let user = user { // 条件分岐でnilチェックをする
nameLabel.text = user.name + "さんようこそ"
// nameLabel.text = user!.name + "さんようこそ"
emailLabel.text = user.email
let dateString = dateFormatterForCreatedAt(date: user.createdAt.dateValue())
// dateLabel.text = "作成日: " + user.createdAt.description
dateLabel.text = "作成日: " + dateString
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
confirmLoggedInUser()
}
private func confirmLoggedInUser() {
if Auth.auth().currentUser?.uid == nil || user == nil {
presentToMainViewController()
}
}
private func presentToMainViewController(){
let storyBoard = UIStoryboard(name: "Main", bundle: nil) // name: "Home" のところはエラーがでたので name: "Main" に変えた
let ViewController = storyBoard.instantiateViewController(identifier: "ViewController") as! ViewController
let navController = UINavigationController(rootViewController: ViewController)
navController.modalPresentationStyle = .fullScreen
self.present(navController, animated: true, completion: nil)
}
// シュミレータで作成日が数億秒で表示されるので、見やすくフォーマットする。
private func dateFormatterForCreatedAt(date: Date) -> String{
let formatter = DateFormatter()
formatter.dateStyle = .long
formatter.timeStyle = .none
// formatter.timeStyle = .long // localeのja_JPとセットで日本時間が表示される。
formatter.locale = Locale(identifier: "ja_JP")
return formatter.string(from: date)
}
}
0