Home>

There is an error that whose view is not in the window hierarchy! and it seems to occur by performing screen transition with viewDidLoad, but since the transition processing is done in the main thread, the cause of the error cannot be identified I am.

When presentLoginScreen() was called in the handleLogout method of ProfileControllerDelgate of ConversationController, an error occurred. If it was not called, the transition did not occur and no error occurred.

profileControllerDelegate

Warning: Attempt to present<UINavigationController: 0x10b867a00>on<FirebaseChat.conversationsViewController: 0x10a520990>whose view is not in the window hierarchy!
Applicable source code Controller in question
import Firebase
import UIKit
private let reuseIdentifier = "ConversationCell"
class conversationsViewController: UIViewController {
    //MARK:-Property
    private let tableView = UITableView()
    private var conversations = [Conversation]()
    private let newMessageButton: UIButton = {
        let button = UIButton(type: .system)
        button.setImage(UIImage(systemName: "plus"), for: .normal)
        button.backgroundColor = .systemPurple
        button.tintColor = .white
        button.imageView?.setDimensions(height: 24, width: 24)
        button.addTarget(self, action: #selector(showNewMessage), for: .touchUpInside)
        return button
    }()
    //MARK:-View LifeCycle
    override func viewDidLoad() {
        super.viewDidLoad()
        configureUI()
        authenticateUser()
    }
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        fetchConversations()
        configureNavigationBar(Title: "Message", prefersLargeTitle: true)
        navigationController?.navigationBar.isHidden = false
    }
    //MARK:-Selector's
    @objc func showProfile() {
        let controller = ProfileController(style: .insetGrouped) //MARK:-Point TableView has a margin.
        controller.delegate = self
        let nav = UINavigationController(rootViewController: controller)
        nav.modalPresentationStyle = .fullScreen
        present(nav, animated: true, completion: nil)
    }
    @objc func showNewMessage() {
        //Transition after adding navigationBar
        let controller = NewMessageController()
        controller.delegate = self
        let nav = UINavigationController(rootViewController: controller)
        nav.modalPresentationStyle = .fullScreen
        present(nav, animated: true, completion: nil)
    }
    //MARK:-API
    func fetchConversations() {
        Service.fetchConversation {(conversations) in
            self.conversations = conversations
            self.tableView.reloadData()
        }}
    func authenticateUser() {
        if Auth.auth().currentUser?.uid == nil {
            presentLoginScreen()
        }else{
            print("DEBUG :current userId =>\(Auth.auth().currentUser!.uid)")
        }
    }
    //MARK:-User Interface's
    private func configureUI() {
        // Style Property
        let bgColor: UIColor = .white
        view.backgroundColor = bgColor
        configureGradientLayer()
        configureNavigationBar(Title: "Message", prefersLargeTitle: true)
        configureTableView()
        navigationItem.leftBarButtonItem = UIBarButtonItem(image: UIImage(systemName: "person.circle.fill"), style: .plain, target: self, action: #selector(self.showProfile))

        view.addSubview(newMessageButton)
        newMessageButton.setDimensions(height: 56 ,width: 56)
        newMessageButton.layer.cornerRadius = 56/2 // button height at this point is 0 ×: newMessageButton.frame.height
        newMessageButton.anchor(bottom: view.safeAreaLayoutGuide.bottomAnchor, right: view.rightAnchor,
                                 paddingBottom: 16, paddingRight: 24)
    }
    private func configureTableView() {
        //Style Property
        let tableViewBgColor: UIColor = .white
        tableView.register(ConversationCell.self, forCellReuseIdentifier: reuseIdentifier)
        tableView.backgroundColor = tableViewBgColor
        tableView.rowHeight = 80
        tableView.tableFooterView = UIView()
        tableView.delegate = self
        tableView.dataSource = self
        tableView.frame = self.view.frame
        self.view.addSubview(self.tableView)
    }
    //MARK:-Helpers
    private func presentLoginScreen() {
        DispatchQueue.main.async {
            let controller = LoginController()
            let nav = UINavigationController(rootViewController: controller)
            nav.modalPresentationStyle = .fullScreen
            self.present(nav, animated: true, completion: nil)
        }
    }
    private func showChatController(for user user: User) {
        let chatController = ChatController(user: user)
        self.navigationController?.pushViewController(chatController, animated: true)
    }
}
//MARK:-UITableviewDatasource
extension conversationsViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) ->Int {
        return conversations.count
    }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier, for: indexPath) as! ConversationCell
        cell.conversation = conversations[indexPath.row]return cell
    }
}
//MARK:-UITableView Delegate
extension conversationsViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        showChatController(foruser: conversations[indexPath.row].user)
    }
}
extension conversationsViewController: NewMessageControllerDelegate {
    func controller(_ controller: NewMessageController, wantsToStartWith user: User) {
        controller.dismiss(animated: true, completion: nil) // Point: After returning from NewMessageController to ConvaseationsController, pushViewController.
        showChatController(foruser: user)
    }
}
//MARK:-ProfileController Delegate
extension conversationsViewController: ProfileControllerDelegate {
    func handleLogout() {
        AuthService.shared.logout {(error) in
            if let error = error {
                self.showHud(error: true, title: "Logout Error", Description: error.localizedDescription)
                return
            }
            self.presentLoginScreen()// When this method is called, an error occurs and there is no transition
        }
    }
}
AuthService Logout()
func logout(completion: @escaping(_ error: Error?) ->Void) (
        do{
            try Auth.auth().signOut()
            completion(nil)
        }catch{
            let error = CustomError.logout
            completion(error)
        }
    }
ProfileDelegate HandleLogout()
protocol ProfileControllerDelegate: class {
    func handleLogout()
}
//profileFooterView's DelegateMethod name is also only called when the handleLogout button is pressed.
extension ProfileController: ProfileFooterDelegate{
    func handleLogout() {
        let alert = UIAlertController(title: nil, message: "Are you sure I want to logout ??", preferredStyle: .actionSheet)
        alert.addAction(UIAlertAction(title: "Log Out", style: .default, handler: {(_) in
            self.delegate?.handleLogout() // ProfileControllerDelegate's handleLogout()
        }))
        alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
        present(alert, animated: true, completion: nil)
    }
}
What I tried

I changed authenticateUser() of ConversationController to Viewdidappear(), but it didn't matter.

Supplemental information (FW/tool ​​version, etc.)

swift5
xcode 11

  • Answer # 1

      func handleLogout() {
            let alert = UIAlertController(title: nil, message: "Are you sure you want to logout ??", preferredStyle: .actionSheet)
            alert.addAction(UIAlertAction(title: "Log Out", style: .default, handler: {(_) in
                self.dismiss(animated: true) {
                    self.delegate?.handleLogout()
                }
            }))
            alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
            present(alert, animated: true, completion: nil)
        }
    }

    Calling dismiss before delegating the delegate worked fine.