UIAlertController whose view is not in the window hierarchy - swift

I'm trying to create an app with Swift and Parse. I'm using Xcode 7 and Swift 2.
I want to show a alert message when User is login failed, here is my function:
func logInViewController(logInController: PFLogInViewController, didFailToLogInWithError error: NSError?){
let alertLoginFailed = UIAlertController(title: "Login Failed", message: "Your username or password is invalid!", preferredStyle: UIAlertControllerStyle.Alert)
alertLoginFailed.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alertLoginFailed, animated: true, completion: nil)
print("Login failed.........!")
}
But I've got this error when run in the emulator:
2015-10-02 11:32:39.988 RedString[2089:886501] Warning: Attempt to present <UIAlertController: 0x7a934400> on <MyProject.ViewController: 0x7b985150> whose view is not in the window hierarchy!
Login failed.........!
I've googled about it, but I didn't found the clear solution.
Here is my whole class:
import UIKit
import Parse
import ParseUI
class ViewController: UIViewController, PFLogInViewControllerDelegate, PFSignUpViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(animated: Bool) {
self.setupLoginView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func logInViewController(logInController: PFLogInViewController, didLogInUser user: PFUser){
self.dismissViewControllerAnimated(true, completion: nil)
}
func logInViewController(logInController: PFLogInViewController, didFailToLogInWithError error: NSError?){
let alertLoginFailed = UIAlertController(title: "Login Failed", message: "Your username or password is invalid!", preferredStyle: UIAlertControllerStyle.Alert)
alertLoginFailed.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alertLoginFailed, animated: true, completion: nil)
print("Login failed.........!")
}
func signUpViewController(signUpController: PFSignUpViewController, didSignUpUser user: PFUser){
self.dismissViewControllerAnimated(true, completion: nil)
}
func signUpViewController(signUpController: PFSignUpViewController, didFailToSignUpWithError error: NSError?){
print("Sign up failed.........!")
}
func setupLoginView(){
if(PFUser.currentUser() == nil){
let loginViewController = PFLogInViewController()
loginViewController.delegate = self
let signUpViewController = PFSignUpViewController()
signUpViewController.delegate = self
loginViewController.logInView!.logo = UIImageView(image: UIImage(named:"logo.png"))
loginViewController.signUpController = signUpViewController
self.presentViewController(loginViewController, animated: true, completion: nil)
}else{
print("login as: " + PFUser.currentUser()!.username!)
//prepare new view here
}
}
}

when
func logInViewController(logInController: PFLogInViewController, didFailToLogInWithError error: NSError?){
let alertLoginFailed = UIAlertController(title: "Login Failed", message: "Your username or password is invalid!", preferredStyle: UIAlertControllerStyle.Alert)
alertLoginFailed.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alertLoginFailed, animated: true, completion: nil)
print("Login failed.........!")
}
is called, your view controller is not on screen. Therefore you could either
dismiss the logInViewController, then display the alert and pass
setUpLogInView as the handler.
or in the above function, try logInController.presentViewController
instead of self.presentViewController

You have to call presentViewController:animated:completion on your login view controller.
Here is a simplified version of your class to show you what I mean:
class ViewController: UIViewController {
weak var loginViewController: UIViewController?
func setupLoginViewController() {
let loginVC = PFLogInViewController()
// setup loginVC
loginViewController = loginVC // store the reference
}
func loginDidFail() {
let alertVC = UIAlertController(...)
// setup alertVC
loginViewController?.presentViewController(...) // present the alert from the login view controller
}
}

I had the same problem in Objective-C, it occurs when the parent window has not yet been drawn to the screen. I solved the problem by calling the code from within viewDidAppear rather than viewDidLoad. See UIAlert error, whose view is not in the window hierarchy

Related

Cannot find 'UIAlertController' in scope error in swift?

Why I am getting Cannot find 'UIAlertController' in scope? error if i write alert function in separate swift file or if i use extension its not coming to another viewcontroller why?
code:
func showAlert(title: String, message: String) {
let alertController = UIAlertController(title: title, message:
message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: {action in
}))
self.present(alertController, animated: true, completion: nil)
}
error:
Cannot find 'UIAlertController' in scope,
Cannot find 'UIAlertAction' in scope
Cannot find 'self' in scope
Probably you forgot this in your viewController:
import UIKit
so your file has no idea what UIAlertController or UIAlertAction is
if you want to create an alert view controller to be used across multiple UIViewController you can do this:
import UIKit
class CustomAlertController: NSObject {
let message:String?
let title:String?
init(title:String, message:String) {
self.message = message
self.title = title
}
func showAlert()->UIAlertController {
let alertController = UIAlertController(title: self.title, message: self.message, preferredStyle: .alert)
// you can further customize your buttons, buttons' title etc
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: {action in
}))
return alertController
}
}
then your view controllers
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let alert = CustomAlertController(title: "Hello", message: "My message to the world")
DispatchQueue.main.async {
self.present(alert.showAlert(), animated: true, completion: nil)
}
}
}

How to rectify error when the app is uninstalled and then reinstalled and goes to the home page

If I uninstalls the app and then reinstall my app then it directly goes to the home page of the app. It doesn't ask the user to login.
I wrote the authStateListener for the current user to monitor when the app is removed in the background and then reinstalled, the app will show the login page instead of the Home page. When the user logs out then the app shows the login page otherwise the app goes to the home page. So when the user is logged out from the app and then uninstalls and reinstalls the app, it works correctly.
But my problem is if user isn't logged out, and uninstalls and reinstalls the app then it shows the home page of the app instead of the log in page. How do I solve this?
The code is :
class ViewController: UIViewController {
var db : Firestore!
var handle:AuthStateDidChangeListenerHandle?
#IBOutlet weak var email: UITextField!
#IBOutlet weak var pswd: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
db=Firestore.firestore()
print(currentReachabilityStatus != .notReachable)
let token = Messaging.messaging().fcmToken
let authid = Auth.auth().currentUser?.uid
print("token\(String(describing: token))")
let docRef = self.db.collection("deyaPayUsers").document(authid!)
docRef.setData(["FCMToken":token as Any],options:SetOptions.merge())
self.handle = Auth.auth().addStateDidChangeListener { auth, user in
if user != nil {
if (user?.isEmailVerified)!{
let myVC = self.storyboard?.instantiateViewController(withIdentifier: "deyaPay") as!deyaPay
self.navigationController?.pushViewController(myVC, animated:true)
}
else{
let alertVC = UIAlertController(title: "Error", message: "Sorry. Your email address has not yet been verified. Do you want us to send another verification email to \(String(describing: self.email.text!)).", preferredStyle: .alert)
let alertActionCancel = UIAlertAction(title: "Cancel", style: .default, handler: nil)
//alertVC.addAction(alertActionOkay)
alertVC.addAction(alertActionCancel)
self.present(alertVC, animated: true, completion: nil)
}
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewDidAppear(_ animated: Bool) {
if currentReachabilityStatus != .notReachable
{
print("Connected")
}
else
{
let controller = UIAlertController(title: "No Internet Detected", message: "This app requires an Internet connection", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
controller.addAction(ok)
controller.addAction(cancel)
present(controller, animated: true, completion: nil)
}
Auth.auth().addStateDidChangeListener{ auth, user in
if Auth.auth().currentUser != nil {
let vc = self.storyboard?.instantiateViewController(withIdentifier: "deyaPay")as!deyaPay
//self.present(vc, animated: true, completion: nil)
let navigationController = UINavigationController(rootViewController: vc)
self.present(navigationController, animated: true, completion: nil)
} else {
print("signout")
}
}
}
}

use same UIAlertController in different ViewControllers

I have used side navigation menu(SWReveal). I have 4 ViewControllers. How can use same alertAction in different views.
You can create UIViewController extension like below:
extension UIViewController {
func showAlert(title: String?, message: String?, actionTitles:[String?], actions:[((UIAlertAction) -> Void)?]) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
for (index, title) in actionTitles.enumerated() {
let action = UIAlertAction(title: title, style: .default, handler: actions[index])
alert.addAction(action)
}
self.present(alert, animated: true, completion: nil)
}
}
And you can use this alert in UIViewController like below:
showAlert(title: "Your Title", message: "Your custom Message", actionTitles: ["Ok","Cancel"], actions: [{ action1 in
//OK Action
}, { action2 in
// Cancel Action
}
])
Hope will get your solution.
You can also use like this way.
class IOSPublicDefaultAlert: NSObject{
var viewController: UIViewController?
var actionCompletion: ((String) -> ())?
var alertTitle: String?
var alertMessage : String?
var alertType: UIAlertControllerStyle?
var actionTitleAndType: [String: UIAlertActionStyle]?
init(viewController : UIViewController,alertTitle: String?,alertMessage : String?,alertType: UIAlertControllerStyle = .alert,actionTitleAndType: [String: UIAlertActionStyle] ,actionCompletion : ((String)->())?){
super.init()
self.viewController = viewController
self.actionCompletion = actionCompletion
self.alertTitle = alertTitle
self.alertMessage = alertMessage
self.alertType = alertType
self.actionTitleAndType = actionTitleAndType
showAlert()
}
func showAlert(){
let alert = UIAlertController.init(title: alertTitle, message: alertMessage, preferredStyle: self.alertType ?? .alert)
for (actionTitle, actionType) in actionTitleAndType!{
let action = UIAlertAction(title: actionTitle, style: actionType) { (action) in
if let com = self.actionCompletion{
com(actionTitle)
}
}
alert.addAction(action)
}
viewController?.present(alert, animated: true, completion: nil)
}
}
and use add where you like as below sample
_ = IOSPublicDefaultAlert.init(viewController: self, alertTitle: "Warning!!!", alertMessage: alertMessage, actionTitleAndType: ["Ok" : .destructive, "Cancel" : .default], actionCompletion: { [unowned self] (title) in
if title == "Ok"{
}
})
In swift, your project, you can create a new .swift file and in this file create a class:
import UIKit
import Foundation
class yourFileName {
//Create a class function alerview
class func displayAlert(title: String, withMessage msg: String, andbtnTitle btntitle: String, in vc: UIViewController) {
let alert = UIAlertController(title: title, message: msg, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: btntitle, style: UIAlertActionStyle.default, handler: nil))
appDelegate.window?.rootViewController?.present(alert, animated: true, completion: nil)
}
}
//and now your any ViewController.swift file or any other file in your project you can access alert following way.
class viewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
yourfilename.displayAlert(title: "Alert", withMessage msg: "my alert view display", andbtnTitle btntitle: "Ok", in vc: self) // access your alertview
}
}
I hope it's work for you.
Create BaseController with a method that can show alert.
//Copyright © 2017 dip. All rights reserved.
import UIKit
class BaseController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
///This is common method to show alert with same action
func showAlert() {
let alert = UIAlertController(title: "Alert", message: "my msg on alert", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
///This will be common alert ok aciton for all child controllers.
print("Do some userful common work..")
}))
self.present(alert, animated: true, completion: nil)
}
}
Inherit Your 4 controllers from BaseController
// Copyright © 2017 dip. All rights reserved.
//
import UIKit
class ChildVC: BaseController {
override func viewDidLoad() {
super.viewDidLoad()
//call show alert when ever you wish
///This method will call showAlert() method on super class (BaseController)
self.showAlert()
}
}
Call self.showAlert() method from child when you want show alert with common action.
// MARK: - Alertable View
protocol AlertableView {
// Use handler if need catch cancel alert action
typealias CompletionHandler = (() -> Void)
func displayAlert(with title: String, message: String, actions: [UIAlertAction]?)
func displayAlert(with title: String, message: String, style: UIAlertControllerStyle, actions: [UIAlertAction]?, completion: CompletionHandler?)
}
extension AlertableView where Self: UIViewController {
func displayAlert(with title: String, message: String, actions: [UIAlertAction]?) {
self.displayAlert(with: title, message: message, style: .alert, actions: actions, completion: nil)
}
func displayAlert(with title: String, message: String, style: UIAlertControllerStyle, actions: [UIAlertAction]?, completion: CompletionHandler?) {
let alertCancelAction = UIAlertAction(title: "Cancel".localized, style: .cancel) { (action) in
guard let completion = completion else { return }
completion()
}
let alertController = UIAlertController(title: title, message: message, preferredStyle: style)
if let actions = actions {
for action in actions {
alertController.addAction(action)
}
alertController.addAction(alertCancelAction)
} else {
// If not any custom actions, we add OK alert button
let alertOkAction = UIAlertAction(title: "OK".localized, style: .cancel) { (action) in
guard let completion = completion else { return }
completion()
}
alertController.addAction(alertOkAction)
}
self.present(alertController, animated: true, completion: nil)
}
}
Create a common function ,
import UIKit
class AlertClass: NSObject {
func showAlertWithVC(_ VC : UIViewController, andMessage message: String ){
DispatchQueue.main.async {
let alert = UIAlertController(title: "APPLICATION_NAME", message: message , preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil))
VC.present(alert, animated: true, completion: nil)
}
}
}
Simply call AlertClass().showAlertWithVC() where you want to show Alert.

Call function in UIViewController from an extension

I have a UIViewController which loads up some json data from the server. If the server is down or the user has data turned off I throw up an alert telling the user such. This is done using a UIAlertController. This works great. So I put this into an extension since it is used by all UIViewControllers which need data. Now the UIAlertController has an action set as well
Alert code
extension UIViewController {
func connectionLost(){
var message = "Your device has lost connection to the server. Check that you have a valid internet connection and then retry."
let alertController = UIAlertController( title: "Connection Lost",
message: message,
preferredStyle: .alert)
let retryAction = UIAlertAction(title:"Retry", style: .default, handler: {
action in
//call function in the viewcontroller that raised this alert to reload the data
})
alertController.addAction(retryAction)
self.present(alertController, animated: true, completion: nil)
}
}
When the user taps the retry button I want to call a function in the uiviewcontroller that raised the alert.
I tried creating a delegate in the extension but struggled with getting it wired up like you do in a class. What sort of approaches are there to call a function from an extension in the viewcontroller that raised the alert?
You should create a BaseViewController and use Inheritance. It could be useful for other implementations too.
class BaseViewController: UIViewController {
func onRetryClick() {
// override to customize or write here the common behaviour
}
}
class FirstViewController: BaseViewController {
override func onRetryClick() {
// do something specific for FirstViewController
}
}
class SecondViewController: BaseViewController {
override func onRetryClick() {
// do something specific for SecondViewController
}
}
class ThirdViewController: BaseViewController {
// if you don't override this method, super class (BaseViewController) implementation will be executed
}
extension BaseViewController {
func connectionLost(){
var message = "Your device has lost connection to the server. Check that you have a valid internet connection and then retry."
let alertController = UIAlertController( title: "Connection Lost",
message: message,
preferredStyle: .alert)
let retryAction = UIAlertAction(title:"Retry", style: .default, handler: { action in
self.onRetryClick()
})
alertController.addAction(retryAction)
self.present(alertController, animated: true, completion: nil)
}
}
Hope this makes sense.
class MyVC: UIViewController {
func retry() {
}
func checkConnection() {
connectionLost { (retry) -> (Void) in
if retry {
self.retry()
}
}
}
}
extension UIViewController {
func connectionLost(completion: #escaping (_ retry: Bool) -> (Void)) {
let message = "Your device has lost connection to the server. Check that you have a valid internet connection and then retry."
let alertController = UIAlertController( title: "Connection Lost",
message: message,
preferredStyle: .alert)
let retryAction = UIAlertAction(title:"Retry", style: .default, handler: {
action in
completion(true)//may be 'false', you decide
})
alertController.addAction(retryAction)
self.present(alertController, animated: true, completion: nil)
}
}

Why can't won't the MFMailComposerViewController be dismissed?

Whenever I press "Cancel" then "Delete Draft", the mail composer won't be dismissed. The error I'm getting is "Thread 1: EXC_BAD_ACCESS (code=1, address=0x40363380)"
In my TableViewController I have:
#IBAction func mailButton(sender: AnyObject) {
let emailComposer = EmailComposer()
if email != "" {
print(email)
if emailComposer.canSendMail() {
emailComposer.setRecipient(email)
let configuredMailComposeViewController = emailComposer.configuredMailComposeViewController()
presentViewController(configuredMailComposeViewController, animated: true, completion: nil)
}
} else {
let alertController = UIAlertController(title: "Sorry!", message: "No email found for this contact", preferredStyle: .Alert)
alertController.addAction(UIAlertAction(title: "OK", style: .Default, handler: { (action) -> Void in
//do nothing
}))
self.presentViewController(alertController, animated: true, completion:nil)
}
}
For those who don't know, EXC_BAD_ACCESS means its trying to access something in memory that is no longer there. I wrongfully created the EmailComposer() object after the button tap so it was going out of scope. So this:
let emailComposer = EmailComposer()
...should have been created here, for example:
class TableViewController: UITableViewController {
let emailComposer = EmailComposer()