I am building a Swift 3 app with the following Storyboard:
On the left (in green) is a UIPageViewController that holds the 2 NavigationController as 2 pages. This allows the user to swipe between 2 subparts of the app.
The problem is the following. I'm trying to display an alert in tha black UIViewController.
Here is the code to display the alert:
override func viewDidAppear(_ animated: Bool) {
let alert = UIAlertController(title: "Hello", message: "World", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: { action in
alert.dismiss(animated: true, completion: nil)
}))
self.present(alert, animated: true, completion: nil)
}
It works but I always get the the below warning:
Presenting view controllers on detached view controllers is discouraged
I also tried with DispatchQueue.main.async to present the view but I ran into the same warning.
However what I found is, if I set the NavigationController (bottom one) as the initial view controller, it works without the warning.
So, does using a UIPageViewController mean the pages will be kind of detached ?
What am I missing here ? Do I forgot to link stuff ?
You can try following.
[self.view.window.rootViewController presentViewController:alert animated:YES completion:nil];
When you are done you can dismiss it.
[self dismissViewControllerAnimated:YES completion:nil];
Let me know if this works.
Related
I have designed custom cells in listViewCell.swift and listViewCell.xib. In which i have textfield. When the user enters a number more than 100 then i want to show alert.
I have tableView in HomeViewController.
let alert = UIAlertController(
title: "Error",
message: "Please Insert value less than 100",
preferredStyle: UIAlertControllerStyle.alert
)
alert.addAction(UIAlertAction(
title: "Click",
style: UIAlertActionStyle.default,
handler: nil
))
UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil)
then it gives me Attempt to present <UIAlertController: 0x7f945a5a67d0> on <ProjName.LoginController: 0x7f945a407e70> whose view is not in the window hierarchy!
and when i change the code to
let alert = UIAlertController(
title: "Error", message: "Please Insert value less than 100",
preferredStyle: UIAlertControllerStyle.alert
)
alert.addAction(UIAlertAction(
title: "Click",
style: UIAlertActionStyle.default,
handler: nil
))
HomeViewController().present(alert, animated: true, completion: nil)
then it gives me unexpectedly found nil while unwrapping an Optional value
and points to
override func viewDidLoad() {
super.viewDidLoad()
**tableViewList.delegate = self**
tableViewList.dataSource = self
searchBar.delegate = self
}
How can i show a alert from a custom cell? or how can we create a common alert function in HomeViewController and show it form any swift file?
Thanks in Advance
You could go with making delegates of your custom cell. Set the delegate of your custom cells, let's say didEnterInvalidValue didEnterValidValue, to your HomeViewController. From those delegate implementations, show your UIAlertController with custom messages.
Or you can iterate over your self.navigationController?.viewControllers to find the top view controller and show UIAlertController on that view controller's view.
Most easiest would be
Step 1:
From your View controller where you have used your custom Cell, make a common delegate function for your textField which purpose is to notify you about text length.
You may need to extend UITextFieldDelegate for this.
Or u can assign a target function to your textField(Not sure if it works though)
How do you catch Specific TextField?
You need to assign a tag for every textField using indexPath and catch that specific textField using that tag.
Step 2:
In that function you can show your alert by only using
self.present(alert, animated: true, completion: nil)
I would go for the delegate answer. But for a faster solution, at several projects I create a singleton from the rootViewController (generally the UINavigationController) at AppDelegate so it can get the rootController any time. Using keyWindow, it's not very recommended since the keyWindow it's not always the one controlled by who, for example, alert dialog redefine it.
Another projects I create a AlertClass singletone, where it's initialised by the rootController, and it's always invoked from it.
I am using Swift 3, Xcode 8.2.
I have an application where the user launches the iPhone camera and I give them a popup with instructions on how to take a good picture. I want there to be a way to create pages within the UIAlertController. I did a quick sketch of what I want to achieve.
Code wise, I am not sure what to do:
func displayInstructions() {
let insController = UIAlertController(title: "Instructions", message: "Step 1: Do this.", preferredStyle: .alert)
let actionDone = UIAlertAction(title: "OK", style: .cancel) { (action:UIAlertAction) in
//This is called when the user presses the cancel button.
print("You've pressed the done button");
}
//Add the buttons
errorController.addAction(actionDone)
// Some way to addSubviews here??
let pageViewController: UIPageViewController
insController.addSubview(pageViewController)
//Present the instruction controller
self.present(insController, animated: true, completion: nil)
}
Any help would be greatly appreciated. Thank you!
There is a property on UIAlertController, not advertised in public API but that seem usable without trouble going through the app store review. So, using KVC, you can set the contentViewController of the alert controller.
let pageViewController: UIPageViewController
// configure pageViewController...
insController.setValue(pageViewController, forKey: "contentViewController")
You can also set the size of the contentViewController by setting preferredContentSize.height on it
pageViewController.preferredContentSize.height = 180
This is what the result looks like with an empty page view controller
I have encountered this warning:
pushViewController:animated: called on while an existing transition or presentation is
occurring; the navigation stack will not be updated.
While trying to call navigationController?.popViewControllerAnimated(false) from UIAlertController completion block.
This warning indicates that you are trying use UINavigationController wrongly:
pushViewController:animated: called on while an existing transition or presentation is occurring; the navigation stack will not be updated
You mentioned in the comments that you are trying to pop the
ViewController using
navigationController?.popViewControllerAnimated(false)
inside completion block of UIAlertController. Therefore, you are trying to unwind from the wrong view, UIAlertController is not part of the UINavigationController stack.
Try and close the UIAlertController first, then pop the current ViewController. In other words, remove the pop from the completion block and put it inside the OK block. or use unwind segue before the alert.
Another possibility, is that you have an unused or identical duplicate in storyboard. Hence, if the unwinding operation is triggered by storyboard button, select this button and check the connectivity inspector and removed unwanted connections.
For example: the red x marked was unnecessary in my case.
I solved this using DispatchQueue.main.asyncAfter call popviewcontroller after the UIAlertController's Transition complete
1) Show Alert
2) Call pop viewcontroller after delay
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.navigationController?.popToRootViewController(animated: true)
}
This is not best way, but it works!
Add the code of navigate controller on the ok action button handler. When Tap on ok button navigate the view controller
let okActionBtn = UIAlertAction(title: "Ok", style: .default, handler: {
self.navigationController?.popViewController(animated: true)
})
let cancelActionBtn = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
let alert = UIAlertController(title: "Title", message: "Message", preferredStyle: .alert)
alert.addAction(okActionBtn)
alert.addAction(cancelActionBtn)
self.present(alert, animated: true)
i am using dispatch_async and its working. i tried to navigate back but didn't worked because navigation stack will not be updated.
let destVC = self.storyboard?.instantiateViewControllerWithIdentifier("HomeViewController") as! HomeViewController
self.presentViewController(destVC, animated: true, completion: {() -> Void in
dispatch_async(dispatch_get_main_queue(), {() -> Void in
self.navigationController?.popViewControllerAnimated(true)!
})
})
My Solution would be to call dismissViewControllerAnimated: first and then pop the viewcontroller from navigation stack this works for me :-
[self dismissViewControllerAnimated:false completion:nil];
[myNavigationControllerInstance popToRootViewControllerAnimated:true]; // myNavigationControllerInstance = Your Navigation Controller Instance
Anyone needs go back to PreviousVC Use this code. Swift5
dismiss(animated: true) {
// If you need call someFunction()
self.someFunction()
self.navigationController?.popViewController(animated: true)
}
In my case I wrote like following.
secondVC.dismiss(animated : true)
self.navigationController?.pushViewController(thirdVC, animated: true)
Then I replace that to this and now it's working.
secondVC.dismiss(animated: true, completion: {
self.navigationController?.pushViewController(thirdVC, animated: true)
})
I have been having some trouble making UIAlerts work. I have looked at a couple SO questions that seem to solve this issue yet I still have a problem. The alert view seems to not be presented. Here is my code:
var inputTextField: UITextField?
let actionSheetController: UIAlertController = UIAlertController(title: "Create a password", message: "", preferredStyle: .Alert)
let save: UIAlertAction = UIAlertAction(title: "Save", style: .Default) { action -> Void in
if !(inputTextField?.text=="password"){
println(inputTextField?.text)
}else{
println("You have a really bad password")
}
}
actionSheetController.addAction(save)
actionSheetController.addTextFieldWithConfigurationHandler { textField -> Void in
inputTextField = textField
}
self.presentViewController(actionSheetController, animated: true, completion: nil)
Here is the error:
Attempt to present <UIAlertController: 0x7fa7016305e0> on <PassProtect.ViewController: 0x7fa701576600> whose view is not in the window hierarchy!
Does anybody know why this is not being presented?
Any help or advice is greatly appreciated!
It's to do with the fact that when you make the call to present the UIAlertController, self.view is not on screen. If you are writing this code in the viewDidLoad() section, this won't work as self.view is still off screen.
You can make this call once self.view is available, for example in viewDidAppear() or from any sort of UI action like clicking a button. Basically, anything that will occur before viewDidDisappear().
There is a similar question with similar information if you want to read it and also here for a more generalised case, i.e. trying to present any sort of view controller which isn't in the view hierarchy.
I want to use an UIButton to trigger a UIAlertController... in Swift
So in this example I have an "Agree" button below some text, and I want the user to click Agree and have a pop-up alert with 2 options confirming/canceling the agreement. How would I go about connecting the UIButton and the UIAlertController. Also if the user cancels, I want the alert to dismiss and remain on the current VC. If the user agrees, I want it to segue to another view controller.
I fairly new to Swift, so if the answer could be detailed that would be extremely appreciated!
You need to add an IBAction (Control drag from your UIButton on your XIB/Storyboard, to the viewController implementation to link the button to the method). Inside the method that you link to the action you need to present the viewController, similar to the below:
func showAlert(sender: UIButton!)
{
var alert = UIAlertController(title: "Title", message: "Some Message", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Agree", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}