Trouble with UIAlerts - ios

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.

Related

Display an alert using storyboard and custom UITabBarController

I am dealing with a problem using UITabBarController. I have a small project using storyboards (XCode 13, IOS 15 as base system). I created a TabBarController but I later discovered I could not manage it effectively programmatically. Reading various docs, I discovered I could use two scenes from my storyboard and creating the tabbar programmatically.
So I did this in SceneDelegate.swift:
let queryViewControllerTab = storyBoard.instantiateViewController(withIdentifier: "QueryViewController")
let settingsViewControllerTab = storyBoard.instantiateViewController(withIdentifier: "SettingsViewController")
let starredViewControllerTab = storyBoard.instantiateViewController(withIdentifier: "StarredViewController")
starredViewControllerTab.tabBarItem.title = "Starred"
starredViewControllerTab.tabBarItem.image = UIImage(systemName: "star")
// TODO: Discover why first two views keep reading image I setup previously in storyboard
let tabBarController = UITabBarController()
tabBarController.viewControllers = [queryViewControllerTab, settingsViewControllerTab, starredViewControllerTab]
tabBarController.selectedViewController = settingsViewControllerTab
self.window?.rootViewController = tabBarController
self.window?.makeKeyAndVisible()
This works perfectly and I can easily put a condition whether userDefaults are not set, load directly the settings.
In my class SettingsViewController I want to add an action where, upon pressing the button, you get an alert:
#IBAction func saveButtonPressed(_ sender: UIButton) {
// keychain.set(tokenInput.text ?? "", forKey: keychainKey)
let alert = UIAlertController(title: "My Alert", message: "This is an alert.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Default action"),
style: .default, handler: { _ in
NSLog("The \"OK\" alert occured.")
}))
tabBarController.present(alert, animated: true, completion: nil)
}
But this makes the app crashing with unrecognized selector sent to instance 0x7f82f9705c30'
I've tried to debug the problem, and I understood I can't make the alert in this way because the view is really the tabBar and not the my scene. But here I got stuck.
I tried to implement the UITabBarControllerDelegate, in StarredViewController, but I can't get it working.
extension StarredViewController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
print("did select tab bar item!")
}
}
I start thinking my main setup with SceneDelegate and AppDelegate is wrong.
Most of previous tutorials or threads I've found seems to fail even to compile because using deprecated versions.
This is a way to present an alert from any presented View Controller.
Add some extensions:
import UIKit
extension UIViewController {
var customVisibleViewController: UIViewController? {
if let navigationController = self as? UINavigationController {
return navigationController.topViewController?.customVisibleViewController
} else if let tabBarController = self as? UITabBarController {
return tabBarController.selectedViewController?.customVisibleViewController
} else if let presentedViewController = presentedViewController {
return presentedViewController.customVisibleViewController
} else if self is UIAlertController {
return nil
} else {
return self
}
}
}
extension UIApplication {
/// The top most view controller
static var topMostViewController: UIViewController? {
return UIApplication.shared.keyWindow?.rootViewController?.customVisibleViewController
}
}
Now you can show your alert in this way:
let alert = UIAlertController(title: "My Alert", message: "This is an alert.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: "Default action"),
style: .default, handler: { _ in
NSLog("The \"OK\" alert occured.")
}))
UIApplication.topMostViewController?.present(alert, animated: true, completion: nil)
This is the code to trigger an alert. With addAction, you can add possible answers.
do {
try //some method call or something else
} catch {
let alert = UIAlertController(title: "There was an error while saving!", message: "Please try again", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "I understand", style: .cancel, handler: nil))
}
you can find more information here:
how to show an alert
To get to the root Controller you can use the following code:
let viewController = UIApplication.shared.windows.first!.rootViewController as! YourViewController
I solved the problem. Actually, all my assumptions and the question were wrong.
TL;DR the storyboard is corrupted or got damaged when I removed the tab bar from it to make it programmatically.
Here the long version. Before entering in this trouble, I had a storyboard with two views and a tab bar controller. It was working perfectly. At one point, I decided I wanted to make a choice during the app starting and, in case of missing defaults, load immediately the settings view. I found that, to do this, I had to move my tab bar down to the scene delegate and remove it from storyboards. I did it, so storyboard was showing to views no linked, and I instantiated the tab bar from the scene delegate.
Weirdly, the tab bar being rendered was still showing some properties previously set on the storyboard, even if that component was deleted.
Then, you know the problem. My reasoning did not make any sense. The UITabBarController can't show any alert. Alerts can be presented on a UIViewController only. So, it was pointless to keep trying to make an alert out from a tab bar.
This wrong understanding led me also to wrong research which reported various similar questions (probably misleading).
I finally made a counter test. Created a brand new project with storyboard. Created two views on the storyboard and defined a tab bar controller on the scene delegate. It worked as expected. I linked each view to a specific UIViewController. Created a button on the view, added the IBAction and it worked. Then, I created the alert in the IBAction and, this time, worked exactly.
I ended with the same code, and the only different is that I did not create and removed a tab bar from the storyboard.
I knew that storyboard can get damaged and, probably, I did it.

Display alert from custom cell in swift 3

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.

Swift - How do I place a page-view within a UIAlertController?

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

Unable to present UIAlertController within a UITableViewController in Swift

I have got a UIAlertController that I would like to present within a UITableViewController. However when I run the code I get the following error:
Warning: Attempt to present on
whose view is not in the
window hierarchy!
My code seem to reflect what suggested in this answer. What am I doing wrong?
override func viewDidLoad() {
super.viewDidLoad()
// Check for force touch feature, and add force touch/previewing capability.
if traitCollection.forceTouchCapability == .available {
registerForPreviewing(with: self, sourceView: view)
}
else {
// Create an alert to display to the user.
alertController = UIAlertController(title: "3D Touch Not Available", message: "Unsupported device.", preferredStyle: .alert)
self.present(alertController!, animated: false, completion: nil)
}
Suppose, in viewDidLoad you still don't have a view in the window hierarchy, like said in message. Try to move your code to viewDidAppear instead

Swift, iOS: How to use UIButton to trigger UIAlertController

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)
}

Resources