The issue in question involves an AddItemView which is presented modally by its delegate and contains a tableView. When the user selects an item from the tableView, it triggers an action on the delegate. Depending on the response from the server, the delegate may present a either another modal view or a UIAlertView on top of the current modal.
Important Note: This UIAlertView needs to be presented while the modal is still on screen. The modally presented view containing the tableView cannot be dismissed after user selection because the user needs to be able to select multiple items from the table and, one by one, send them back to the delegate for processing.
Currently, the UIAlerView is not being displayed and I suspect it is because the already-presented modal is preventing that. Is there a workaround to present the UIAlertView from the delegate when the delegate is sitting underneath a modal and without dismissing that modal?
The UIAlertView is currently displayed like so by the delegate, while the delegate is sitting under a modal:
var alert = UIAlertController(title: "Error", message: "Error message from server", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "actionOne", style: .Default, handler: { action in
// perform some action
}))
alert.addAction(UIAlertAction(title: "actionTwo", style: .Destructive, handler: { action in
// perform some action
}))
self.presentViewController(alert, animated: true, completion: nil)
Here is the error that is returned when the UIAlertView is presented by the delegate:
Warning: Attempt to present <UIAlertController: 0x156da6300> on <productionLINK_Scanner.ContainerContents: 0x156e65b20> whose view is not in the window hierarchy!
If possible, please provide answer using Swift.
Solved:
Used the following extension, thanks to yonat on GitHub:
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
Within the delegate in question, it was implemented like so:
var alert = UIAlertController(title: "Alert Title", message: "Message Body", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: { action in
}))
if let topController = UIApplication.topViewController(base: self) {
topController.presentViewController(alert, animated: true, completion: nil)
} else {
// If all else fails, attempt to present the alert from this controller.
self.presentViewController(alert, animated: true, completion: nil)
}
This now allows the following process:
ContainerView is loaded as a delegate of ItemTableView
User clicks searchButton
ContainerView modally presents a list of items ItemTableView
Each time a use selects a row in ItemTableView, the didSelectItem function is called in the instance of ContainerView that presented the ItemTableView. The ItemTableView does NOT get dismissed - the user can continue selecting items.
ContainerView submits a request to the server
Depending on the response, the ContainerView may present a UIAlertView.
The alertView is properly displayed using the above-mention code on top of whatever view is top-most in the hierarchy.
"When the user selects an item, it triggers and action on the delegate"
set a break point at start of the delegate method which is triggered by selecting an item. check whether that delegate method is being called or not ?
and test this too. (ACTION :UIAlertAction!)in
Related
I have this alert controller setup to appear as soon as the view controller is loaded. However, it does not appear.
I believe I have all the facets covered - title, message, alert style, action button and present... but still does not appear.
Unsure what I'm missing.
let array = quoteBank()
print(array.sarcasticQuotes[0].quote)
let title = "Message"
let message = array.sarcasticQuotes[0].quote
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(.init(title: "OK", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
Attempting to show an alert in viewDidLoad is too soon. The view controller isn't displayed yet. Use viewDidAppear.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Use this if statement to only show the alert once
if self.isBeingPresented || self.isMovingToParentViewController {
// show your alert here
}
}
You need to perform that on the main thread:
DispatchQueue.main.async {
present(alert, animated: true, completion: nil)
}
The reason behind that is that the view controller's hierarchy will be set after viewDidLoad is finished. So by doing this, you're scheduling the presentation of the alert on the main thread to the time the main thread is finished from executing viewDidLoad.
New swift user here. I would like to open a pop up (controlled by a separate view controller) over the normal view controller as part of a function call (i.e. there is no button that is pressed or similar). How do I write this? I would also like to send information to that influences the images that are shown in the pop up.
I have previously managed to open a modal pop-up via a button press. I don't really know if there is something peculiar about these kinds of popups but if there are I'd like to have the pop up above look the same.
I hope this is clear enough for someone to understand what I need.
You can add a method in super view controller like this:
class MainViewController: UIViewController {
// MARK: - View methods
override func viewDidLoad() {
super.viewDidLoad()
}
/// This Method is used for Alert With Action Method
func showAlertViewWithPopAction(message: String, title: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title: "OK", style: .default) { [weak self] _ in
///add you logic here
}
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
}
}
And Inherit this controller in every controller like this and call this method from the controller like this:
class FirstViewController: MainViewController {
// MARK: - View methods
override func viewDidLoad() {
super.viewDidLoad()
///Call alert method according to your use
Self.showAlertViewWithPopAction(message: "This is demo alert", title: "Alert")
}
}
Trying to figure out why I am getting this error. I have a single view application with two viewcontrollers. I have a segue to the second vc on a button touch:
#IBAction func showAccount(_ sender: Any) {
performSegue(withIdentifier: "toAccountFromRoot", sender: self)
}
I have a facade pattern set up with a Controller file and several helper files. I set the Controller up on the AccountViewController file:
let myController = Controller.sharedInstance
One of the helper files is to set up a central AlertController for simplified alerts (no extra buttons). It has one method, presentAlert that takes a few arguments, alert title, messsage, presenter.
On my AccountViewController I am attempting to present an alert using this code:
myController.alert.presentAlert("No Agent", "You have not registered an agent yet.", self)
and in the AlertManager file, presentAlert looks like this:
func presentAlert(_ title:String, _ message:String, _ presenter:UIViewController) {
let myAlert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
myAlert.addAction(okAction)
presenter.present(myAlert, animated:true, completion:nil)
}
I've done this in quite a few apps without an issue so I am confused as to why the compiler thinks the AccountViewController view is not in the window hierarchy.
Dang it, typed all that and then I solved it 5 seconds later. Just in case someone else runs into the same problem, I was trying to present the alert in viewDidLoad, should of been doing it in viewDidAppear.
I am having trouble presenting a CustomAlert, subclass of UIViewController, from a UINavigationController stack view. To be more descriptive, I have a UITabBarController that wraps a UINavigationController. navController, has a rootView and a another pushed to the stack view, leafView of type UITableViewController. From leafView, I would like to present an alertView of type UIViewController which is transparent for the most part and with a opaque view inside. The problem is that when presenting, the background of this alertView is no longer transparent, but black, and upon dismissal it does not return to the leaf controller, but to the rootView. I think I am not presenting my alert from the correct view. How should I fix this?
searchController.present(friendAlert, animated: false, completion: nil)
For alerts, it is much easier to present them from the "top view controller" instead of searching for the responsible controller in your hierarchy. A genius solution to find the controller is shown in this answer:
extension UIApplication {
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}
}
You can then show your alert from anywhere and after dismissing your alert you will get right back to the current view controller:
DispatchQueue.main.async {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { _ in }))
UIApplication.topViewController()?.present(alert, animated: true, completion: nil)
}
I'm new to Swift (and programming in general). I'm having some difficulties in Xcode with the login feature. What I want is that the user should be logged in if no errors was returned and the user should be sent to another controller. I've read some of the documentation from Apple and the performSegueWithIdentifier (and of course some questions asked here), but it still does not work when I use a segue with push or modal that are given an identifier. Either the app crashes, or the user is sent to my UITabBarController even if the credentials was incorrect.
This is my LogInViewController.swift:
#IBAction func loginAction(sender: AnyObject)
{
if self.emailField.text == "" || self.passwordField.text == ""
{
let alertController = UIAlertController(title: "Oops!", message: "Please enter an email and password.", preferredStyle: .Alert)
let defaultAction = UIAlertAction(title: "OK", style: .Cancel, handler: nil)
alertController.addAction(defaultAction)
self.presentViewController(alertController, animated: true, completion: nil)
}
else
{
FIRAuth.auth()?.signInWithEmail(self.emailField.text!, password: self.passwordField.text!) { (user, error) in
if error == nil
{
self.emailField.text = ""
self.passwordField.text = ""
}
else
{
let alertController = UIAlertController(title: "Oops!", message: error?.localizedDescription, preferredStyle: .Alert)
let defaultAction = UIAlertAction(title: "OK", style: .Cancel, handler: nil)
alertController.addAction(defaultAction)
self.presentViewController(alertController, animated: true, completion: nil)
}
// User Logged in
self.performSegueWithIdentifier("LoggedIn", sender: self)
}
}
}
The error I get in the console:
2016-09-04 14:55:30.019 DrinkApp[37777:1006336] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Could not find a navigation controller for segue 'LoggedIn'. Push segues can only be used when the source controller is managed by an instance of UINavigationController.'
There are two main ways to send a user to another View Controller. The first is performSegueWithIdentifier. Here's how you would go about using that:
First of all, control-drag from the yellow circle of the original VC to the target VC in the interface builder.
Next, click on the 'segue arrow' that appears between the two View Controllers.
In the identifier field, type in mySegue.
Next, go back to your code. When you reach the sign in section, add the following code:
self.performSegueWithIdentifier("mySegue", sender: nil)
The second method is a bit more advanced. Here's how that would work:
First, click on the yellow circle of the destination VC. Click on the newspaper, then call the storyboard ID myVC.
Make sure to click on Storyboard ID
Go back to the same part of the code and copy and paste this code:
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let initViewController: UIViewController = storyboard.instantiateViewControllerWithIdentifier("myVC") as UIViewController
self.presentViewController(initViewController, animated: true, completion: nil)
Comment down below if you have any questions. Good luck!
Here is a Firebase login example:
func login() {
FIRAuth.auth()?.signInWithEmail(textFieldLoginEmail.text!, password: textFieldLoginPassword.text!) { (user, error) in
if error != nil {
let alert = UIAlertController(title: "User Authentication error",
message: error?.localizedDescription,
preferredStyle: .Alert)
let OKAction = UIAlertAction(title: "OK",
style: .Default) { (action: UIAlertAction!) -> Void in
}
let resetPasswordAction = UIAlertAction(title: "OK",
style: .Default) { (action: UIAlertAction!) -> Void in
}
alert.addAction(OKAction)
guard let errorCode = FIRAuthErrorCode(rawValue: (error?.code)!) else { return }
switch errorCode {
case .ErrorCodeWrongPassword:
alert.addAction(resetPasswordAction)
case .ErrorCodeNetworkError:
print("Network error")
default:
print("Other error\n")
break
}
self.presentViewController(alert, animated: true, completion: nil)
} else { // User Logged in
self.performSegueWithIdentifier("MySegue", sender: self)
}
}
}
I only handled two errors for this example.
If you use push segue, you need to have navigation controller, and your root segue should lead to the login view controller, then another segue to the next view controller. This last segue must have an identifier (in this case it's MySegue)
Edit:
Since it seems that your problem is not with Firebase, but with setting up the storyboard. Here is an example:
Set up one UINavigationController two UIViewControllers
Control Drag between the UINavigationController to the middle UIViewControllers and choose root view controller from the popup menu
Control Drag between the UIViewController to the last UIViewControllers and choose show
select the segue added in the previous step and change its identifier (in the attributes inspector) to MySegue.
5.Choose the Middle UIViewControllers and change the class name (in the identity inspector) to your login view controller class.
Here is the full setup:
control drag menu (step 2):
Segue attributes inspector (step 4):
I found this answer mentioned here to be super elegant.
https://fluffy.es/how-to-transition-from-login-screen-to-tab-bar-controller/
it works with tab bars, and nav bars. you create a function in the scene delegate, that switches the root view controller. you access the scene delgates functions and windows with (UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?