7

I am using swift 4 and I am trying to create an alertView when I there is an error while signing up a user using Firebase. I have an IBAction for the sign up button which will sign the user up using text from two textfields, one for email and one for password.

I am basically trying to show an alertview when there is an error with the sign up process, for example there is an empty textfield.

enter image description here

I have attached a screenshot of the function to where that is occuring. I know that I am in fact getting an error because the print statement outputs an error if there is one.

Regardless of if there is an error or not, there is no alert view showing up and the app performs the segue regardless.

2019-01-15 21:40:26.368924-0500 Pronto[9036:225268] Warning: Attempt to present on whose view is not in the window hierarchy

This is the output that I am getting for the alertview now showing up. I have looked at all the other posts about this same issue but none seem to work.

Kaushik Makwana
  • 1,329
  • 2
  • 14
  • 24
Steve Sahayadarlin
  • 1,164
  • 3
  • 15
  • 32

4 Answers4

29

This issue happens due to your view hierarchy.

You need to find out what is your Current/Topmost view controller in view hierarchy and present your alert over it.

To find out topmost view controller use following code:

func getTopMostViewController() -> UIViewController? {
    var topMostViewController = UIApplication.shared.keyWindow?.rootViewController

    while let presentedViewController = topMostViewController?.presentedViewController {
        topMostViewController = presentedViewController
    }

    return topMostViewController
}

And present your alert over topmost view controller and use main thread to present an alert because closures may have working on another thread.

DispatchQueue.main.async { 
    getTopMostViewController()?.present(alertController, animated: true, completion: nil)
}

Please refer to this stack answer: Swift 3 Attempt to present whose view is not in the window hierarchy

Jarvis The Avenger
  • 2,750
  • 1
  • 19
  • 37
  • Thank you for your answer, it seemed to help me show the alert view. However, the alert view is showing up after i segue to screen. I need it to not segue if there is an alert being shown up – Steve Sahayadarlin Jan 16 '19 at 14:48
  • @SteveSahayadarlin glad to hear that, you can stop performing segue if topmost view controller is type of AlertViewController. you just need to add a check. – Jarvis The Avenger Jan 17 '19 at 03:58
  • What I basically did I remove the segue in storyboard, then called a present function and segued programmatically and everything works fine now. Thank you – Steve Sahayadarlin Jan 17 '19 at 04:00
  • closures doesn't always guaranteed for sure forever to run on another thread. See https://stackoverflow.com/questions/52011252/are-completion-handler-closures-always-running-in-the-background-thread – CyberMew Oct 03 '19 at 05:32
  • 2
    **DispatchQueue.main.async** is the key bro! Awesome answer! saved a lot of time for me. Many thanks! – sandpat Jan 10 '20 at 07:41
  • 1
    This solved a lot of headache for me trying to change storyboards from within a custom class. Great work. – Terry Carter Mar 19 '20 at 03:08
3

Try using ViewDidAppear instead of View did Load.

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    let alertViewController = UIAlertController(title: "Any", message: "Any Custom Message", preferredStyle: .alert)

    alertController.addAction(UIAlertAction(title: "ANy", style: .cancel, handler: nil))
    present(alertViewController, animated: true, completion: nil)
}
Kaushik Makwana
  • 1,329
  • 2
  • 14
  • 24
Nishant Pathania
  • 349
  • 1
  • 14
2

You can get the top most view controller and have that view controller present the alert. So instead of self.present use this approach and see if it works:

let topViewController = UIApplication.shared.keyWindow?.rootViewController
topViewController?.present(alertController, animated: true, completion: nil)

Also try presenting on the main thread, since you're trying to show the alert in createUser completion handler:

DispatchQueue.main.async { 
    self.present(alertController, animated: true, completion: nil)
}
M Reza
  • 18,350
  • 14
  • 66
  • 71
  • 1
    just tried this, but it still gave me the same problem with view not in hierarchy – Steve Sahayadarlin Jan 16 '19 at 03:10
  • @SteveSahayadarlin Ok, have you tried presenting on the main thread? – M Reza Jan 16 '19 at 03:11
  • not sure what you mean by that, could you clarify or elaborate? – Steve Sahayadarlin Jan 16 '19 at 03:12
  • Found that on another post, but tried it again like your updated answer just to be sure. unfortunately, same problem – Steve Sahayadarlin Jan 16 '19 at 03:15
  • @SteveSahayadarlin Have you tried `showMessagePrompt`? Check this link https://firebase.google.com/docs/auth/ios/email-link-auth it's the same alertConroller implemented in firebase. – M Reza Jan 16 '19 at 03:25
  • @ MohammadRF I am getting "Value of type 'SignUpViewController' has no member 'showMessagePrompt'" – Steve Sahayadarlin Jan 16 '19 at 03:31
  • @SteveSahayadarlin It should be just implemented in the examples then. I thought it's built in the framework, as you can see here in all the examples: https://github.com/firebase/quickstart-ios/blob/master/authentication/AuthenticationExampleSwift/EmailViewController.swift – M Reza Jan 16 '19 at 03:33
  • yes, i think they might have their own function for examples, not for framework. thank you for your replies. – Steve Sahayadarlin Jan 16 '19 at 03:34
-1

Check if you have "double tap" issue:

  1. You double tap button accidentally
  2. signUpBtnPressed is called twice
  3. First request is executed correctly, thus launching the segue, and the error is nil
  4. Second request returns error like 'user already exists', then trying to show alert from current controller, but segue is already launched and next controller is already presented

This is fixed by using loader with UI blocking (for example SVProgressHUD) - start loader at the beginning of the method and dismiss it in callback.

medvedNick
  • 4,512
  • 4
  • 32
  • 50