Attempt to present whose view is not in the window hierarchy - ios

I am trying to create the alert controller class in swift
//AppDelegate.swift:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
window = UIWindow(frame:UIScreen.mainScreen().bounds)
let loginVC = ViewControllerForLogin (nibName:"ViewControllerForLogin", bundle:nil)
navigationObject = UINavigationController(rootViewController: loginVC)
window?.rootViewController = loginVC
window?.makeKeyAndVisible()
return true
}
//SPSwiftAlert.swift
class SPSwiftAlert: UIViewController {
//#MARK: - Members
internal var defaultTextForNormalAlertButton = "OK"
static let sharedObject = SPSwiftAlert()
//#MARK: Functions
func showNormalAlert(controller: UIViewController, title: String, message: String) {
// create the alert
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
// add an action (button)
alert.addAction(UIAlertAction(title: defaultTextForNormalAlertButton, style: UIAlertActionStyle.Default, handler: nil))
// show the alert
controller.presentViewController(alert, animated: true, completion: nil)
}
}
the above class is used display the alert with provided message and
title with single button as-
SPSwiftAlert.sharedObject.showNormalAlert(self, title: "Invalid input", message: "Entered email address is not valid")
but this giving me runtime error as
Attempt to present <UIAlertController: 0x7f8c805e8e80> on <Swaft_Login_Demo.ViewControllerForLogin: 0x7f8c8042d4b0> whose view is not in the window hierarchy!
How should i resolve this ?

So, when I saw your code I dont understand the part where you put to window.rootViewController your loginVC instead of the navigation..
window?.rootViewController = navigationObject
Then, it seems you are not in the window's view hierarchy when you call your alert.
Try to write this call to the viewDidAppear: method.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
dispatch_async(dispatch_get_main_queue()) {
SPSwiftAlert.sharedObject.showNormalAlert(self, title: "Invalid input", message: "Entered email address is not valid")
}
}
NOTE: This generally happens when we try to show (present/push) the view
controller
over another view controller but the presenter view controller is
currently not active view controller (means the presenter view
controller view must be top view on the screen)

First why are you not presenting the alert controller on your view controller itself rather than making a new view controller and passing self(your viewcontroller) in that for alert to be displayed.
As far as the problem is concerned, you have never added the "SPSwiftAlert" view to any view.
// add an action (button)
alert.addAction(UIAlertAction(title: defaultTextForNormalAlertButton, style: UIAlertActionStyle.Default, handler: nil))
// add view
// add self.view subview to controller.view
// show the alert
self.presentViewController(alert, animated: true, completion: nil)

I guess your ViewControllerForLogin is presented already or it is not having segue in storyboard.

Related

iOS Can't dismiss view controller

I have issue with my app. Scenario is simple, after successfully account creation i wan't to dismiss current page or navigate to login page. My storyboard looks like this:
After successful account creation i having a popup with some info about it's ok, we send you verification email and after this popup i want to go to the page second from left - it's my main application page (now called "View Controller").
I tried dismiss window, but i have no effect there, it can only dismiss my popup window.
When i trying to redirect then i have issue with back button when is pressed,it lead to Sign Up page. There is some code:
// Create new user and send verification email
Auth.auth().createUser(withEmail: userEmail, password: userPassword) { user, error in if error == nil && user != nil {
self.sendVerificationMail();
self.displayAlertMessage(alertTitle: "Success", alertMessage: "Your account created successfully. We send you a verification email.");
// Redirect to View Controller
} else {
self.displayAlertMessage(alertTitle: "Unhandled error", alertMessage: "Undefined error #SignUpViewController_0002");
}
}
...
func displayAlertMessage(alertTitle: String, alertMessage:String, alertRedirection:String = ""){
let alert = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: UIAlertController.Style.alert);
let okAction = UIAlertAction(title:"Ok", style: UIAlertAction.Style.default, handler: nil);
alert.addAction(okAction);
self.present(alert, animated:true, completion:nil);
}
If i add this:
self.view.window!.rootViewController?.dismiss(animated: false, completion: nil)
After alert, it close only alert, before alert, it do nothing ( same as dismiss).
To dismiss and pop to main view you can use alert button action handler.
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: { (action) in
self.dismiss(animated: true, completion: {
self.navigationController?.popToRootViewController(animated: true)
})
}))
Or you can use the navigation to specific view controller using below lines.
for viewController in self.navigationController!.viewControllers {
if viewController.isKind(of: <Your_Main_ViewController_Class_Name>.self) {
self.navigationController?.popToViewController(viewController, animated: true)
break
}
}
Your_Main_ViewController_Class_Name is the view controller that within your navigation controller stack to which you need to navigate. (ie) main view
To blindly navigate to main view once alert popup displayed, you can use completion handler while present the alert.
self.present(alert, animated: true, completion: {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
self.navigationController?.popToRootViewController(animated: true)
}
})
well, you are using a navigation controller, for "present" a new view controller, you need to push it, for example.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("IDOFYOURVIEW") as CLASS_NAME_OFYOUR_VIEWCONTROLLER
navigationController?.pushViewController(vc, animated: true)
with the last code you can "present" (push) a new view controller
Now, if you want to make a other action when your press backbutton, try with this lines
override func viewDidLoad {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
let newBackButton = UIBarButtonItem(title: "Back", style: UIBarButtonItemStyle.Bordered, target: self, action: "back:")
self.navigationItem.leftBarButtonItem = newBackButton
}
func back(sender: UIBarButtonItem) {
//in this part you can move to other view controller, examples
// Go back specific view in your navigation controller
for controller in self.navigationController!.viewControllers as Array {
if controller.isKind(of: NAMECLASS_OFYOUR_VIEWCONTROLLER.self) {
_ = self.navigationController!.popToViewController(controller, animated: true)
break
}
}
// Perform your custom actions
// ...
// Go back to the previous ViewController
self.navigationController?.popViewControllerAnimated(true)
}
Regards

Conditional loading of ViewControllers: Temporary display of wrong ViewController

I am trying to write a login process for my app. I have embedded a navigation controller to HomeViewController and set it as the initial ViewController. How can I fix it such that when a user enters the wrong credentials the HomeViewController will not be shown at all?
This is what it is doing:
Correct credentials entered
Display LoginViewController -> User inputs credentials -> Display HomeViewController
Wrong credentials entered
Display LoginViewController -> User inputs credentials -> Display HomeViewController -> Display LoginViewController
Code for LoginViewController (look at the last block of code)
func handlingAuthentication(notification: NSNotification) {
let dict = notification.object as! NSDictionary
if dict["error"]! as! Bool == true {
let errorMessage = dict["message"] as! String
//initialize Alert Controller
let alertController = UIAlertController(title: "Authentication error", message: errorMessage, preferredStyle: .Alert)
//Initialize Actions
let okAction = UIAlertAction(title: "Ok", style: .Default){
(action) -> Void in
self.dismissViewControllerAnimated(true, completion: nil)
}
//Add Actions
alertController.addAction(okAction)
//Present Alert Controller
self.presentViewController(alertController, animated: true, completion: nil)
}
else
{
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "isUserLoggedIn")
NSUserDefaults.standardUserDefaults().synchronize()
self.dismissViewControllerAnimated(true, completion:nil)
}
}
Code for HomeViewController
override func viewDidAppear(animated: Bool) {
let isUserLoggedIn = NSUserDefaults.standardUserDefaults().boolForKey("isUserLoggedIn")
if(!isUserLoggedIn){
self.performSegueWithIdentifier("toLoginVC", sender: self)
}
}
UPDATE
I've tried placing the code block in ViewDidLoad but I am still getting the same issue (in fact now I'm stuck on the homePage)
override func viewDidLoad() {
super.viewDidLoad()
let isUserLoggedIn = NSUserDefaults.standardUserDefaults().boolForKey("isUserLoggedIn")
if(!isUserLoggedIn){
self.performSegueWithIdentifier("toLoginVC", sender: self)
}
usernameLabel.text = Data.sharedInstance.userName
getTaskDetails()
displayTask.dataSource = self
}
If the main view controlled decides and displays the login you will inevitable see it on screen because it's already in the process of displaying - so it shouldn't do it. You should have some other controller, perhaps a splash view controller, which decides to show either the login or the main view.
In your login view controller the alert OK button calls dismiss, this is the reason the login controller disappears and re-appears again (after showing the main controller for a short time).

Don't show Alert when the ViewController is not in Window hierarchy

I am having a NavigationController. In the ThirdViewController I am performing some task and on failure, I show Alert messages using UIAlertController.
Sometimes, when I start the task and come back to SecondViewController, I get the error message displayed on SecondViewController and on clicking OK, everything gets black below Navigation bar. I am left with only Navigation bar and if I go back again to FirstViewController, it also has the same black view except Navigation bar.
Presenting Alert of the ViewController which is not in the window hierarchy creates the issue. I do not want the Alert to be presented if I am not on the screen.
It is easily reproducible if I go back swiping the ViewController slowly.
What is the best way to handle it?
Sharing my code,
Button action in ThirdViewController
func buttonTapped() {
APIManager.sharedManager.getDetails(completion: { (details ,error) -> Void in
guard error == nil else {
Alert.errorMsg(error!.localizedDescription, viewController: self, goBack: false)
return
}
print(details)
}
}
class Alert: NSObject {
/* Error message */
class func errorMsg(message: String, viewController: UIViewController?, goBack: Bool = false) {
let alertView = UIAlertController(title: "Error", message: message, preferredStyle: .Alert)
let action = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) { (alert: UIAlertAction) -> Void in
if goBack == true && viewController != nil {
viewController!.navigationController?.popToRootViewControllerAnimated(true)
}
}
alertView.addAction(action)
let controller = viewController ?? UIApplication.sharedApplication().keyWindow?.rootViewController
controller!.presentViewController(alertView, animated: true, completion: nil)
}
}
I created a CustomViewController and added a property 'isUnloading'. In viewWillDisappear, I set isUnloading = true. I check the property before presenting the Alert.
Since you did not share any code we don't know exactly what happens there. But if you do not want to show the alert if the view controller is not in the window hierarchy you could check if viewController.view.window is set before showing the alert view and show it only if it is set.
you can do something like,
class AlertHelper {
func showAlert(fromController controller: UIViewController) {
var alert = UIAlertController(title: "abc", message: "def", preferredStyle: .Alert)
controller.presentViewController(alert, animated: true, completion: nil)
}
}
called alert as,
var alert = AlertHelper()
alert.showAlert(fromController: self)
refer this link for more detail.
Hope this will help :)

UIAlert Controller not working in AppDelegte didFinishLaunchingWithOptions method - in Swift

I am trying to make a UIAlertController pop up one time when the user first downloads the app. However, I get the following error when I put the code in didFinishLaunchingWithOptions,
2015-01-15 23:54:45.306 WeddingApp Warning: Attempt to present UIAlertController: on UITabBarController: whose view is not in the window hierarchy!
My code is below:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
if window == nil {
println("window is nil");
return true;
}
if window!.rootViewController == nil {
println("window!.rootViewController is nil");
return true;
}
//TabBarController//
let tabBarController: UITabBarController = window!.rootViewController! as UITabBarController;
UITabBar.appearance().tintColor = UIColor.magentaColor();
UITabBar.appearance().translucent = false;
/* tableView cells are now completely visable. They do not hie behind the tabBar */
tabBarController.viewControllers = [
TwelveToTenMonths(nibName: nil, bundle:nil),
NineToSevenMonths(nibName: nil, bundle:nil),
SixToFourMonths(nibName: nil, bundle:nil),
ThreeToOneMonth(nibName: nil, bundle:nil),
];
var alert = UIAlertController(
title: "Welcome to WeddingApp!",
message: "Thank You for choosing the \nWedding App for your organization needs. \n\nFor more great tools please visit our website www.wedme.com",
preferredStyle: UIAlertControllerStyle.Alert);
alert.addAction(UIAlertAction(title: "Continue", style: UIAlertActionStyle.Default, handler: nil));
self.window!.rootViewController!.presentViewController(alert, animated: true, completion: nil);
return true
}
***NOTE: I also tried adding the following to the appDelegate instead of the above code but the alert continues to appear whenever I return to the app.....
func applicationDidBecomeActive(application: UIApplication) { //Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
showAlert();
}
func showAlert(){
println("alert");
var alert = UIAlertController(
title: "Welcome to WeddingApp!",
message: "Thank You for choosing the \nWedding App for your organization needs. \n\nFor more great tools please visit our website www.wedme.com",
preferredStyle: UIAlertControllerStyle.Alert);
alert.addAction(UIAlertAction(title: "Continue", style: UIAlertActionStyle.Default, handler: nil));
self.window!.rootViewController!.presentViewController(alert, animated: true, completion: nil);
}
Does anyone know a way around this???
In the first case, you never added the UIAlertController (you could try adding it as a child view controller).
https://stackoverflow.com/a/17012439/3324388
In the second case you assigned the root controller to the UIAlertController and never assign the root controller back to the UITabBarController. You need to set it back.
Have you tried using a UIAlertView instead?
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIAlertView_Class/index.html
Try something like this:
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
if self.window!.rootViewController as? UITabBarController != nil {
var tababarController = self.window!.rootViewController as UITabBarController
.
.
.
let alertController = UIAlertController(title: "Welcome...", message: "Thank You...", preferredStyle: .Alert)
tababarController.presentViewController(alert, animated: true, completion: nil);
}
return true
}

presentViewController not working in Swift

Thank you for reading this. I would like to have a functions Swift file where I put all of the functions for my project into, that the other Swift files could call. I am trying to create an alert function in the functions file that, when I pass in a specific string, it shows a specific alert. It was working when it was in the main file, but when I moved it to the functions file, presentViewController is giving me an error, saying "Use of unresolved identifier 'presentViewController'." Please help! Here is my code:
in the functions file:
import Foundation
import UIKit
/**********************************************
Variables
***********************************************/
var canTapButton: Bool = false
var tappedAmount = 0
/**********************************************
Functions
***********************************************/
//the alert to ask the user to assess their speed
func showAlert(alert: String) -> Void
{
if(alert == "pleaseAssessAlert")
{
let pleaseAssessAlert = UIAlertController(title: "Welcome!", message: "If this is your firs time, I encourage you to use the Speed Assessment Tool (located in the menu) to figure which of you fingers is fastest!", preferredStyle: .Alert)
//ok button
let okButtonOnAlertAction = UIAlertAction(title: "Done", style: .Default)
{ (action) -> Void in
//what happens when "ok" is pressed
}
pleaseAssessAlert.addAction(okButtonOnAlertAction)
presentViewController(pleaseAssessAlert, animated: true, completion: nil)
}
else
{
println("Error calling the alert function.")
}
}
Thanks!
The presentViewController is the instance method of UIViewController class. So you can't access it on your function file like this.
You should change the function like:
func showAlert(alert : String, viewController : UIViewController) -> Void
{
if(alert == "pleaseAssessAlert")
{
let pleaseAssessAlert = UIAlertController(title: "Welcome!", message: "If this is your firs time, I encourage you to use the Speed Assessment Tool (located in the menu) to figure which of you fingers is fastest!", preferredStyle: .Alert)
//ok button
let okButtonOnAlertAction = UIAlertAction(title: "Done", style: .Default)
{ (action) -> Void in
//what happens when "ok" is pressed
}
pleaseAssessAlert.addAction(okButtonOnAlertAction)
viewController.presentViewController(pleaseAssessAlert, animated: true, completion: nil)
}
else
{
println("Error calling the alert function.")
}
}
Here, you are passing a UIViewController instance to this function and calling the presentViewController of that View Controller class.
In Swift 3:
The method presentViewController is replaced by present.
You can use it like the old one:
self.present(viewControllerToPresent, animated: true, completion: nil)
First, you need to check your NavigationController is appropriate or not?
If Yes, then Here is code for present and dismiss presentviewcontroller
For presenting PresentViewController :
let next = self.storyboard?.instantiateViewControllerWithIdentifier("Your view controller identifier") as! Yourviewcontroller
self.presentViewController(next, animated: true, completion: nil)
Dismiss Presentviewcontroller
self.dismissViewControllerAnimated(true, completion: nil)
I would say go with MidHun MP method above but, if you are looking for another way to do this without bringing in the UIViewController then:
func showAlert(alert : String) {
var window: UIWindow?
if(alert == "pleaseAssessAlert")
{
let pleaseAssessAlert = UIAlertController(title: "Welcome!", message: "If this is your firs time, I encourage you to use the Speed Assessment Tool (located in the menu) to figure which of you fingers is fastest!", preferredStyle: .Alert)
//ok button
let okButtonOnAlertAction = UIAlertAction(title: "Done", style: .Default)
{ (action) -> Void in
//what happens when "ok" is pressed
}
pleaseAssessAlert.addAction(okButtonOnAlertAction)
self.window?.rootViewController?.presentViewController(pleaseAssessAlert, animated: true, completion: nil)
}
else
{
println("Error calling the alert function.")
}
}
Presenting & navigation view controller has a problem with layoutsubviews function while using self.view or viewcontroller.view, so one must avoid those function.
Check:
func layoutsubviews not allows to provide the viewcontroller.view to work on it

Resources