Struggling with the following code:
var connected = false
while !connected {
let msg = "Press cancel"
let alert = UIAlertController(title: "Test", message: msg, preferredStyle: .alert)
let action = UIAlertAction(title: "Cancel", style: .default) { (action:UIAlertAction) in
connected = true
}
alert.addAction(action)
print ("Hello")
present(alert, animated: true, completion: nil)
}
The UIAlertController is never shown, "Hello" is printed over and over again
If I insert "connected = true" after the while clause then the UIAlertController is shown, but I'm not able to show it again by changing the action to "connected = false"
What am I doing wrong?
First of all, as mentioned in the comments, its not a good idea to present your alert controller continuously in a while loop. I believe your intended functionality is to display an alert whenever the connected variable becomes false.
To accomplish this use NotificationCenter to respond as follows:
In viewDidLoad:
NotificationCenter.default.addObserver(self, selector: #selector(ViewController.displayAlert), name: NSNotification.Name(rawValue: "connectionDropped"), object: nil)
Add a willSet property observer to connected:
var connected: Bool! {
willSet {
if newValue == false && oldValue != false {
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "connectionDropped"), object: nil)
}
}
}
Then whenever you set self.connected = false, you will run this method:
#objc func displayAlert() {
let msg = "Press cancel"
let alert = UIAlertController(title: "Test", message: msg, preferredStyle: .alert)
let action = UIAlertAction(title: "Cancel", style: .default) { (action:UIAlertAction) in
self.connected = true
}
alert.addAction(action)
print ("Hello")
present(alert, animated: true, completion: nil)
}
Just make sure you set connected after the view hierarchy has been loaded e.g in viewDidAppear.
Once you're done with the view you can then remove the observer:
deinit {
NotificationCenter.default.removeObserver(self, name: NSNotification.Name(rawValue: "connectionDropped"), object: nil)
}
EDIT:
The functionality you need is provided with the Reachability framework, in particular with the reachabilityChanged notification. You can then call the displayAlert using a similar approach that I outlined above; this is documented on their README document.
The UIAlertController is never shown, "Hello" is printed over and over again
It is inappropriate to keep presenting the alert in an endless while loop, probably, it leads to facing "AlertController is not in the window hierarchy" issue.
If I insert "connected = true" after the while clause then the UIAlertController is shown, but I'm not able to show it again by
changing the action to "connected = false"
Of course you won't be able to show it again by default, the chunk of code is already been executed.
Solution:
Presenting the alert should be related to triggering a specific condition, not in a while loop.
For instance, if you are aiming to notify the user that the device is unconnected, You could use Reachability and observe reachabilityChanged to present the alert, for more details you could check the library - Example - notifications section.
I managed to solve it using a recursive call to the function:
func showDlg(){
ssid = getWiFiSsid() ?? "AAA"
ssid = ssid[ssid.startIndex..<ssid.index(ssid.startIndex, offsetBy: 2)]
if ssid != "GP" {
print ("SSID: " + ssid)
let msg = "\nNot connected to GoPro camera\n\nPlease connect to camera wireless network and revert\n "
alert = UIAlertController(title: "GoPro Golfswing Analyser", message: msg, preferredStyle: .alert)
let action = UIAlertAction(title: "Try again", style: .default) { (action:UIAlertAction) in
print("Testing network again")
self.showDlg()
}
alert.addAction(action)
let action1 = UIAlertAction(title: "Cancel", style: .default) { (action:UIAlertAction) in
print("Cancel - exiting")
}
alert.addAction(action1)
present(alert, animated: true, completion: nil)
} else{
print("Connected to GoPro")
}
}
Related
I have an app that allows users to save their profile. In order for them to be able to sign up, I want to check and see if they have agreed to the apps terms and conditions. The issue I am having is if the user doesn't agree to them, they will see an alertController telling them to agree. However, the app still continues to execute the remainder of the code.
func checkIfChecked() {
if self.checkbox.imageView.isHidden == true {
let alert = UIAlertController(title: "Hold up!",message:" You must agree to our Community Guidelines before you can sign up.", preferredStyle: UIAlertController.Style.alert)
let continueButton = UIAlertAction(title: "Got it!", style: .default, handler: {(_ action: UIAlertAction) -> Void in
})
continueButton.setValue(GREEN_Theme, forKey: "titleTextColor")
alert.addAction(continueButton)
self.present(alert, animated: true, completion: nil)
}
if self.checkbox2.imageView.isHidden == true {
let alert = UIAlertController(title: "Hold up!",message:" You must agree to our Terms & Conditions before you can sign up.", preferredStyle: UIAlertController.Style.alert)
let continueButton = UIAlertAction(title: "Got it!", style: .default, handler: {(_ action: UIAlertAction) -> Void in
})
continueButton.setValue(GREEN_Theme, forKey: "titleTextColor")
alert.addAction(continueButton)
self.present(alert, animated: true, completion: nil)
}
}
#objc func handleRegister() {
checkIfChecked()
let hud = JGProgressHUD(style: .dark)
hud.textLabel.text = "Registering!"
hud.show(in: view)
guard let email = emailTextField.text, let password = passwordTextField.text, let name = nameTextField.text, let phonenumber = phonenumberTextField.text else {
print("Error")
return
the remainder of code....
}
}
if the checkBoxs are checked, there is no issue. But if they are not checked, then the users information will still be saved to the data base without them logging in. So I am trying to stop the execution of handleRegister after checkIfChecked is called only if the boxs were not checked.
Not sure if this is the safest way to fix the issue I am having, but what I did to fix the problem is inside of of the handleRegister, I added
checkIfChecked()
this has to be after the checkIfChecked that way the alertControllers can show.
if self.checkbox.imageView.isHidden == true {
return
} else if self.checkbox2.imageView.isHidden == true {
return
}
it does stop the execution of code if these lines are true.
I'm extremely new to iOS. I'm trying to show a dialog to the user to get some input, but the actions are never triggered. I've been searching on the net for hours and no answer seem to work for me.
Here's the function I'm trying to use to show the dialog:
private func showAmountDialog(type: String, onComplete: #escaping (Double) -> Void) {
let alert = UIAlertController(title: "Enter an amount", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: LzStrings.Common_Cancel, style: .cancel, handler: nil))
alert.addTextField(configurationHandler: { textField in
textField.placeholder = "0.00 \(type)"
textField.keyboardType = .decimalPad
})
alert.addAction(UIAlertAction(title: LzStrings.Common_OK, style: .default) { (UIAlertAction) in
if let input = alert.textFields?.first?.text, let amount = Double(input) {
print("Your amount: \(amount)")
}
})
self.present(alert, animated: true)
}
self here is my ViewController which has a parent of UIViewController type and several other protocols.
What I might be doing wrong?
EDIT: The way I knew it isn't executing is using break-points and not by relying on print("...")
Also, since I added the TextField right before adding the action, the nullability check is useless and the textFields.first is never nil, so in both cases, a break-point should be triggered or the print("...") should be executed, which neither of them is happening.
EDIT 2: Since the if statement can do a little distraction, I edited my code this way and tested again:
alert.addAction(UIAlertAction(title: LzStrings.Common_OK, style: .default) { (UIAlertAction) in
if let input = alert.textFields?.first {
if let amount = Double(input.text ?? "") {
print("Your amount: \(amount)")
} else {
print("Can't cast this string to double")
}
} else {
print("Text field is null")
}
})
Still, no feedback from the dialog.
PS: Even the Cancel button doesn't work.
EDIT 3: My dismiss function is overridden in the super class, but it passes completion closure normally:
override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
if let navigationController = self.navigationController as? NavigationController {
navigationController.dismiss(animated: flag, completion: completion)
} else {
super.dismiss(animated: flag, completion: completion)
}
}
After having a conversation with one of my colleagues, we found out that to show standard UIAlertController we must use this:
self.view.window!.rootViewController?.present(alert, animated: true, completion: nil)
Instead of this
self.present(alert, animated: true, completion: nil)
It fixed my issue. I hope someone will find this helpful.
Another option is to use an extention for ViewController:
extension UIViewController {
//Show a basic alert
func showAlert(alertText : String, alertMessage : String) {
let alert = UIAlertController(title: alertText, message: alertMessage, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Got it", style: UIAlertActionStyle.default, handler: nil))
//Add more actions as you see fit
self.present(alert, animated: true, completion: nil)
}
}
Alright, I've looked up a lot of documentation on how the UIAlertController's work and I have a rather unique scenario. My application is being designed to work with a HID Bluetooth scanner. When I use:
preferredStyle: UIAlertControllerStyle.Alert
After I generate an alert that the item I have scanned is incorrect. Cool, alert is happening, problem is if I scan again ( which emulates keyboard input ), the return key is being sent to the alert and the alert is running the dismiss action.
If I use:
preferredStyle: UIAlertControllerStyle.ActionSheet
Then the alert is staying where it should be and ignoring scans while the alert window is up just how I want it.
So my question is, how do I capture the return key and prevent the Alert from calling the dismiss action? I'm a bit new with swift and I have a feeling the answer is simple, but I've tried half a dozen things that just isn't working.
If there is a setting to prevent all user input to the alert window or anything solution, I'm all for any method. I just rather not use the ActionSheet, and prefer to use the iOS alerts instead of creating my own screen. If this is not possible, I'm sure I can build my own 'alerts' window.
Code, that I'm calling from a simple Alerts class I made.
import UIKit
class Alerts {
var controller: UIViewController
var message: String
var title: String
init?(title: String, message: String, controller: UIViewController){
if title.isEmpty || message.isEmpty {
return nil
}
self.title = title
self.message = message
self.controller = controller
}
func save_alert(input_field:UITextField, callable: (Bool)->Void ){
let action = UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default) {
UIAlertAction in
callable(false)
input_field.enabled = true
input_field.becomeFirstResponder()
print("DISMISS CALLED")
}
let alert = UIAlertController(title: self.title,message:self.message,preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(action)
self.controller.presentViewController(alert,animated:true, completion: nil)
}
}
Try something like this.
func save_alert(input_field:UITextField, callable: (Bool)->Void ){
let action = UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default) {
UIAlertAction in
callable(false)
input_field.enabled = true
input_field.becomeFirstResponder()
print("DISMISS CALLED")
showAlert()
}
showAlert()
}
func showAlert() {
let alert = UIAlertController(title: self.title,message:self.message,preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(action)
self.controller.presentViewController(alert,animated:true, completion: nil)
}
This is how I simply create UIAlertController and present it on the screen:
private class func showAlertWithTitle(title: String, message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
//alert.accessibilityLabel = "my string here" //doesnt work
let action = UIAlertAction(title: "OK", style: .Default) { action in
alert.dismissViewControllerAnimated(true, completion: nil)
}
alert.addAction(action)
UIStoryboard.topViewController()?.presentViewController(alert, animated: true, completion: nil)
}
and this is how I access it under UITests:
emailAlert = app.alerts["First Name"] //for title "First Name"
but I would like to set there custom identifier and access this by firstName like this:
emailAlert = app.alerts["firstName"]
Is it possible?
This is an old thread but someone might use this.
I was able to set the accessibility identifier like this:
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.view.accessibilityIdentifier = "custom_alert"
alert.view.accessibilityValue = "\(title)-\(message)"
alert.addAction(
UIAlertAction(
title: "ALERT_BUTTON_OK".localized,
style: .default,
handler: handler
)
)
present(alert, animated: true)
That way I can access the alert by accessibility identifier and check its contents in accessibility value.
It is not perfect of course, but it works - at least for my testing using Appium.
The only way I figured out to do this was to use Apple's private APIs. You call valueForKey on the UIAlertAction object with this super secret key: "__representer" to get whats called a _UIAlertControllerActionView.
let alertView = UIAlertController(title: "This is Alert!", message: "This is a message!", preferredStyle: .Alert)
let okAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
alertView.addAction(okAction)
self.presentViewController(alertView, animated: true, completion: {
let alertButton = action.valueForKey("__representer")
let view = alertButton as? UIView
view?.accessibilityIdentifier = "okAction_AID"
})
This has to be done in the completion handler because that that _UIAlertControllerActionView won't exist until the view is presented. On a side note in my project I used these following extensions to make things easier / more readable:
extension UIAlertController {
func applyAccessibilityIdentifiers()
{
for action in actions
{
let label = action.valueForKey("__representer")
let view = label as? UIView
view?.accessibilityIdentifier = action.getAcAccessibilityIdentifier()
}
}
}
extension UIAlertAction
{
private struct AssociatedKeys {
static var AccessabilityIdentifier = "nsh_AccesabilityIdentifier"
}
func setAccessibilityIdentifier(accessabilityIdentifier: String)
{
objc_setAssociatedObject(self, &AssociatedKeys.AccessabilityIdentifier, accessabilityIdentifier, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
}
func getAcAccessibilityIdentifier() -> String?
{
return objc_getAssociatedObject(self, &AssociatedKeys.AccessabilityIdentifier) as? String
}
}
So the above code would be rewritten:
let alertView = UIAlertController(title: NSLocalizedString("NMN_LOGINPAGECONTROLLER_ERROR_TITLE", comment: ""), message: message as String, preferredStyle:.Alert)
let okAction = UIAlertAction(title: NSLocalizedString("NMN_OK", comment: ""), style: .Default, handler: nil)
okAction.setAccessibilityIdentifier(InvalidLoginAlertView_AID)
alertView.addAction(okAction)
self.presentViewController(alertView, animated: true, completion: {
alertView.applyAccessibilityIdentifiers()
})
My first attempt involved trying to navigate the view hierarchy but that became difficult since UIAlertControllerActionView was not a part of the public API. Anyway I'd probably would try to ifdef out the valueForKey("__representer") for builds submitted for the app store or Apple might give you a spanking.
Right now I have a UIAlertAction called addCamera and I'm just doing:
addCamera.accessibilityLabel = "camera-autocomplete-action-photo"
That allows me to tap it in UI Tests as follows:
app.sheets.buttons["camera-autocomplete-action-photo"].firstMatch.tap()
From Apple docs...
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/UIKitUICatalog/UIAlertView.html
Making Alert Views Accessible
Alert views are accessible by default.
Accessibility for alert views pertains to the alert title, alert message, and button titles. If VoiceOver is activated, it speaks the word “alert” when an alert is shown, then speaks its title followed by its message if set. As the user taps a button, VoiceOver speaks its title and the word “button.” As the user taps a text field, VoiceOver speaks its value and “text field” or “secure text field.”
I am trying to disable a menu button is the array is shows is empty.
This is my code.
#IBAction func likedmenubuttontouched(sender: AnyObject) {
if Globals.likedArray.isEmpty {
likedMenuButton.userInteractionEnabled = false
let ac = UIAlertController(title: "No liked quotes yet", message: "No liked quotes have been chosen, go explore!", preferredStyle: .Alert)
ac.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
presentViewController(ac, animated: true, completion: nil)
return
} else {
likedMenuButton.userInteractionEnabled = true
}
}
And in ViewDidLoad()
likedMenuButton.userInteractionEnabled = false
I have managed to disable the button, but I want to send a message alerting the user why the button is disabled, otherwise, its a little confusing.
How would I go about doing this?
Thanks.
As by default the the user Interaction is true you need not to make it true
#IBAction func likedmenubuttontouched(sender: AnyObject) {
if Globals.likedArray.isEmpty {
let ac = UIAlertController(title: "No liked quotes yet", message: "No liked quotes have been chosen, go explore!", preferredStyle: .Alert)
ac.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
presentViewController(ac, animated: true, completion: nil)
} else {
//segue to the other view
}
}
Also check your array count, that is why your wrong condition is executing