追記。書きましたがログイン済みユーザーの場合、Observe(Listen)が2回走ってしまう(ViewControllerのviewDidLoadが2回呼ばれるため)のでおかしくなりますし、やはりListenはviewWillAppearでやるのが正しいみたいです。
https://firebase.googleblog.com/2015/10/best-practices-for-ios-uiviewcontroller_6.html
ただタブ遷移で毎回Listenとremoveを繰り返してしまいますので、これを辞めたい場合は別の方法で回避する必要がありそうです。
一応しばらく残して置きますが、後ほど消すか書き直します。
前提
タブバーのあるアプリでFirebaseAuthを使ったログインを実装。Initial View をMyTabBarControllerにしている。ホームとしてMyTabBarControllerの最初のViewをViewControllerにしている。
もしユーザー認証されていなかったら、LoginView/SignUpViewをPresentModallyで開いて、ログイン完了したらdissmissで閉じる仕様(その方がログイン済みユーザーにとっては余計なparentや処理がないかなと思って)。
viewのライフサイクルのおさらい
ログイン後にホームのcollectionViewが更新されない
Initial View にしているMyTabBarControllerと、その子のViewControllerのviewDidLoad, viewDidAppearは最初に呼ばれる。ログイン後にdissmissしても、子であるViewControllerのviewDidLoadやviewWillAppear, viewDidAppearは呼ばれない。そこでログインしたらMyTabBarControllerか、その子をリロード(初期化)してやる必要があった。
解決方法
dismiss後のMyTabBarControllerのviewDidAppearにて最初の子供だけリロードしてやった。
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard let _ = Auth.auth().currentUser else {
self.presentWelcomeView()
return
}
self.viewControllers?.first?.loadView()
self.viewControllers?.first?.viewDidLoad()
}
全体のコード
import UIKit
import FirebaseAuth
class MyTabBarController: UITabBarController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
guard let _ = Auth.auth().currentUser else {
self.presentWelcomeView() // ここで呼ばないと whose view is not in the window hierarchy! のエラーが出て怒られる(viewDidLoadではViewはロードされただけで生成はされてないかららしい)
return
}
// self.setViewControllers(self.viewControllers, animated: true) //これではダメ
self.viewControllers?.first?.loadView()
self.viewControllers?.first?.viewDidLoad()
}
func presentWelcomeView() {
guard let controller = storyboard?.instantiateViewController(identifier: "WelcomeNavigation") else { return }
controller.modalPresentationStyle = .fullScreen
self.present(controller, animated: true, completion: nil)
}
}
import UIKit
import FirebaseAuth
import FirebaseFirestore
class ViewController: UIViewController {
@IBOutlet weak var collectionView: UICollectionView!
var posts: [Post] = []
var user: User!
let db = Firestore.firestore()
var userRef: DocumentReference? = nil
var listener: ListenerRegistration?
override func viewDidLoad() {
super.viewDidLoad()
guard let user = Auth.auth().currentUser else {
return
}
self.user = user
self.userRef = db.collection("users").document(self.user.uid)
startObservingDatabase() // 省略:addSnapshotListenerによるリッスン処理
self.collectionView.delegate = self
self.collectionView.dataSource = self
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 公式ではここでAuth.auth().currentUserを読んでいる
// しかしここはタブを押すたびに呼ばれるので毎回collectionViewが最新に更新されて本意ではない
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
self.listener?.remove()
}
}
import UIKit
import FirebaseAuth
class LoginViewController: UIViewController {
func signIn() {
//if let tabvc = self.navigationController?.parent as? UITabBarController { // ここがそもそもnilでダメ
// tabvc.viewControllers?.first?.loadView()
// tabvc.viewControllers?.first?.viewDidLoad()
//}
self.navigationController?.dismiss(animated: true, completion: nil) // 今回はたまたま親にNavigationControllerを持たせていたので、ないならself.dismissでもOKなはず
}
}
