Attempting to create an object to act as an UIAlertController wrapper - ios

I am currently trying to create a class that will simplify the process of defining an UIAlert.
As the traditional way of initializing an alert is
let alert = UIAlertController(title:"hello world", message: "how are you",preferedStyle: .actionSheet)
let ok = UIAlertAction(title:"ok",style:.default,handler: {(action) -> Void in print("ok")})
alert.addAction(ok)
self.presentViewController(alert,animated:true,completion:nil)
However, as i am going to be having the same format of alert in alot of places through out my app, I was thinking of making a single class that contains my entire alert object with all actions added so i can simply do:
let alert = MyAlert()
self.presentViewController(alert,animated:true,completion:nil)
I have
class myAlert: UIAlertController{
init() {
super.init(title:"hello world", message: "how are you", preferredStyle: .actionSheet)
}
}
But I seem to be getting an error "Must call a designated initliazer of the superclass 'UIAlertController'
Can you explain to me what I am doing wrong and send me in the right direction. I am fairly new to swift so any help is much appreciated. Thank you

You could just create an extension and whenever you want to display a UIAlertController, just call the method. With an extension, it can be used throughout your app.
extension UIViewController {
func displayMessageAlert(withAlertTitle alertTitle: String, andMessage message: String) {
let alert = UIAlertController(title: alertTitle, message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .destructive, handler: nil)
alert.addAction(okAction)
self.present(alert, animated: true, completion: nil)
}
}
Usage on a UIViewController:
self.displayMessageAlert(withAlertTitle: "Your Title", andMessage: "Display your message here.")

Related

"Instance member 'makeAlert' cannot be used on type 'alert' " I can't fix this error

I created a new swift file and class. I want to use alert function all of my viewcontrollers. But I have an error. Here this is my class and function.
class alert {
func makeAlert(titleInput:String, messageInput:String){
let alert = UIAlertController(title: titleInput, message: messageInput, preferredStyle: UIAlertController.Style.alert)
let okButton = UIAlertAction(title: "Ok", style: UIAlertAction.Style.default, handler: nil)
alert.addAction(okButton)
}
}
I want to use my function here like this.
alert.makeAlert(titleInput: "Error", messageInput: " name or Location can't be empty")
And here my error message:
Instance member 'makeAlert' cannot be used on type 'alert'; did you mean to use a value of this type instead?
alert is a type and makeAlert is an instance member. As the error says
Instance member 'makeAlert' cannot be used on type 'alert'
To show the options let me first rename some terms of the code to avoid confusion
class Alert {
func makeAlert(titleInput: String, messageInput: String) {
let alertController = UIAlertController(title: titleInput, message: messageInput, preferredStyle: UIAlertController.Style.alert)
let okButton = UIAlertAction(title: "Ok", style: .default, handler: nil)
alertController.addAction(okButton)
}
}
Either create an instance of Alert
let alert = Alert()
alert.makeAlert(titleInput: "Foo", messageInput: "Bar")
or make makeAlert a static function
static func makeAlert(titleInput:String, messageInput:String) { ...
then you can use 'makeAlert' on the type
Alert.makeAlert(titleInput: "Foo", messageInput: "Bar")
For more information please read Methods in the Language Guide
But an extra Alert class is not very useful and also the fact that the function doesn't have a return value. A better solution is an extension of UIViewController because you are able to present the alert controller in the current view controller
extension UIViewController {
func presentAlert(title: String, message: String, completion: (() -> Void)? = nil) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let okButton = UIAlertAction(title: "Ok", style: .default, handler: nil)
alertController.addAction(okButton)
self.present(alertController, animated: true, completion: completion)
}
}

Most effective way to give certain ViewControllers access to functions

I have an app with a large number of ViewControllers. There is also a collection of functions that return UIAlertControllers informing the user regarding events related to his account.
Here is an example of one such funtion:
func signInSuccessAlert() -> UIAlertController {
//signInSuccessAlert
let signInSuccessAlert = UIAlertController(title: "Success", message: "You have been successfully signed in!", preferredStyle: .alert)
signInSuccessAlert.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))
return signInSuccessAlert
}
My goal is to then be able to display these UIAlertControllers in one line. Like so:
present(signInSuccessAlert(), animated: true, completion: nil)
What is the best way to make these functions available only to the ViewControllers that need them? As opposed to declaring them globally.
You could create an extension of UIViewController and declare all those functions there.
Something like this:
extension UIViewController
{
func signInSuccessAlert() -> UIAlertController {
//signInSuccessAlert
let signInSuccessAlert = UIAlertController(title: "Success", message: "You have been successfully signed in!", preferredStyle: .alert)
signInSuccessAlert.addAction(UIAlertAction(title: "Okay", style: .default, handler: nil))
return signInSuccessAlert
}
}
This way all your viewcontrollers will have access to these functions. If you want want to give access only to some viewcontrollers you will have to use protocols like AgRizzo suggested in the comment.

Show two UIAlertController objects continuously using completion block

I'm trying to show two UIAlertController's instances continuously, which is like this code block below.
func showAlerts() {
let alertA = UIAlertController(title: "Alert A", message: "This is alert a...", preferredStyle: .alert)
let alertB = UIAlertController(title: "Alert B", message: "This is alert b...", preferredStyle: .alert)
let alertButton1 = UIAlertAction(title: "OK", style: .default, handler: nil)
let alertButton2 = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertA.addAction(alertButton1)
alertA.addAction(alertButton2)
alertB.addAction(alertButton1)
alertB.addAction(alertButton2)
self.present(alertA, animated: true) {
self.present(alertB, animated: true, completion: {
debugPrint("alerts are all shown")
})
}
}
I expect this code to show each alert continuously, which means alertB shows after alertA. But alertB doesn't appear as I expect, with warnings on console saying;
Warning: Attempt to present <UIAlertController: 0x7f7ffde0ace0> on <ContinuousUIAlertController_Experiment.ViewController: 0x7f7ffdd092d0> which is already presenting <UIAlertController: 0x7f7ffde09f90>
If I remember correctly, multiple UIAlertController objects cannot be existed at the same time. So I somehow understand what the warning above tells.
So, then, how can I implement continuous alert showing using completion of UIViewController::present(_:animated:completion:) or with nearly the same logic? (I prefer not to use UIAlertAction's handler)
If there is a solution, please let me know.
I'm struggling with this problem for a few days and I've not addressed yet.
You can't show 2 uialettcontrol at 1time.
But you can show 2nd uialettcontrol on 1st one's uialeraction.
For example
let alertController = UIAlertController(title: "iOScreator", message:
"Hello, world!", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Destructive,handler: { action in
self.pressed()
}))
func pressed()
{
print("you pressed")
}
On pressed event you can write code for 2nd uialettcontrol.

How can I set accessibilityIdentifier to UIAlertController?

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.”

Placing common methods in a separate file using Swift

I like to place often used methods in a separate file. I found this answer Use function from one class in another class in Swift but I get errors using it the way I want.
Say i want to create a method called msgBox that pops up an alert box.
I created a separate empty Swift file and place this code in it.
import UIKit
class Utils: UIViewController {
class func msgBox (titleStr:String = "Untitled", messageStr:String = "Alert text", buttonStr:String = "Ok") {
var alert = UIAlertController(title: titleStr, message: messageStr, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: buttonStr, style: .Default, handler: { (action) -> Void in
self.dismissViewControllerAnimated(true, completion: nil)
}))
self.presentViewController(alert, animated: true, completion: nil)
}
}
I want to call it like this, but I get errors doing so. Does anyone know what I'm doing wrong?
Utils.msgBox(titleStr: "Hello!", messageStr: "Are you sure?")
The error looks like this:
The error is because you are using self in a class method. There is no self instance, in this case.
One thing you could do in this situation is make a class extension. In the following example, you would be able to call the alert method from any UIViewController instance:
extension UIViewController {
func alert(title: String?, message: String?, buttonTitle: String = "OK") {
let alert = UIAlertController(title: title, message: message, preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: buttonTitle, style: .Default, handler: { action in
self.dismissViewControllerAnimated(true, completion: nil)
}))
self.presentViewController(alert, animated: true, completion: nil)
}
}
Notice that I changed a couple names and types, but you can use what you like.

Resources