Most effective way to give certain ViewControllers access to functions - ios

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.

Related

How to display (NS)Errors in UIAlertController?

I have found myself repeatedly writing the following lines of code
let alert = UIAlertController(title: "Something went wrong", message: "This is why something went wrong", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true)
And then I would populate the titles and message of the alert and buttons from NSError objects. In search of a better, more efficient way I came across Apple's Error Handling Programming Guide, which seemed promising:
Note: Beginning with OS X version 10.4, you can use the alertWithError:class method of NSAlert as a convenience for creating NSAlert objects to use when displaying alert dialogs or sheets. The method extracts the localized information from the passed-in NSError object for its message text, informative text, and button titles. You may also use the presentError: message to display error alerts.
The NSError class contains properties specifically meant to be displayed in an alert view (at least for OS X). Unfortunately, I was not able to find a similar approach for iOS.
Is there an easy, convenient way to display Error objects in an Alert in iOS?
Write your own extension to UIAlertController:
extension UIAlertController {
func alert(with error: Error) -> UIAlertController {
// Create and setup the alert as needed using the error
var res = UIAlertController(title: "Something went wrong", message: "Some message", preferredStyle: .alert)
res.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
return res
}
}
Update the parameter to use NSError if desired and/or add additional parameters as needed to suit your needs.
Then you can use it as follows:
let alert = UIAlertController.alert(with: someError)
self.present(alert, animated: true)
Is there an easy, convenient way to display Error objects in an Alert in iOS?
UIAlertController does not have a convenient (auto-parsing) way to display an Error/NSError atm. UIKit does not provide any, so you will end up with a custom solution.
NSError has localizedDescription property which can be shown in UIAlertController's message as follow,
let alert = UIAlertController(title: "Something went wrong", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true)

Whats the Swift best practice for reusable UIAlertController configuration via Enum

I'm trying to make my life a little bit easier and my app more maintainable while at the same time reducing the amount of duplicated code. So I thought it would be nice to put the some code to display a certain type of UIAlertController into its own class.
The thing is, that I have basically the same alert which only differs really slightly based on where I display it in my app. So I thought to myself: why not use a enum and every time I want to display an alert of that kind, just give it the enum value. Get rid of all the duplicated strings everywhere in my code.
I love enums in Swift. They are just so schwifty - ehh I mean swifty. So I came up with this example enum:
enum MyAlertType {
case a
case b
}
Next I created a simple class to handle the display:
class MyAlert {
static func showAlert(ofType type: MyAlertType, inViewController viewController: UIViewController, handler: ((UIAlertAction) -> ())? = nil, completion: (() -> ())? = nil) {
var message: String
switch type {
case .a:
message = "A is a nice letter!"
case .b:
message = "B is a nice letter!"
}
let alert = UIAlertController(title: "Do you know which letter is nice?", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: handler))
viewController.present(alert, animated: true, completion: completion)
}
}
//Somewhere in my code (obviously a UIViewController)
MyAlert.showAlert(ofType: .a), inViewController: self)
But wait, I still have to give this function the ViewController where I want to display the alert in. For me thats always the same (self) so the next logical step was to make this an extension:
extension UIViewController {
func showAlert(ofType type: MyAlertType, handler: ((UIAlertAction) -> ())? = nil, completion: (() -> ())? = nil) {
var message: String
switch type {
case .a:
message = "A is a nice letter!"
case .b:
message = "B is a nice letter!"
}
let alert = UIAlertController(title: "Do you know which letter is nice?", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: handler))
present(alert, animated: true, completion: completion)
}
}
//Somewhere in my UIViewController)
showAlert(ofType: .a)
But this makes the code available for all UIViewControllers, even those where I don't explicitly need/want to display that kind of alert. I mean, yeah sure, I am the developer and I can decide not to use this but on the other hand isn't it always a good practice to hide everything as much as possible and as least as necessary? Who knows who might in the future join my dev team and start misusing my beautiful code in ways I haven't thought of?
The other day I learned about Swifts protocol oriented programming approach (which to be honest I still have not fully understood by now) and now I think, I maybe should make this a protocol with a default implementation and then let the only those UIViewControllers implement the protocol, where I need to show this alert.
protocol MyAlertProtocol {
func showAlert(ofType type: MyAlertType, handler: ((UIAlertAction) -> ())?, completion: (() -> ())?)
}
extension MyAlertProtocol where Self : UIViewController {
func showAlert(ofType type: MyAlertType, handler: ((UIAlertAction) -> ())? = nil, completion: (() -> ())? = nil) {
var message: String
switch type {
case .a:
message = "A is a nice letter!"
case .b:
message = "B is a nice letter!"
}
let alert = UIAlertController(title: "Do you know which letter is nice?", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: handler))
present(alert, animated: true, completion: completion)
}
}
extension MyViewController: MyAlertProtocol {}
I know that this might sound as an opinion based question so I'm not asking you if you think if this or that is better but to tell me if there actually is a best practice for this scenario and how it looks like.
Protocol with default implementation via protocol extension? Simple UIViewController extension? Custom Enum, Struct or Class with static function (and if so, which one)? Or maybe even just a function somewhere in a Swift file? I feel overwhelmed. The agony of choice...
UPDATE/SOLUTION
After reading the given answers, I decided that my Sergeys answer was indeed the most suitable one. I wanted to make my like "easier" by having "less lines of duplicated code". For me this included the "presend(controller:animated:)" inside my ViewController.
However, I think you guys are right. I should use a struct (a class is really not necessary for one static function) with a static func to generate the alert man make it "ready to use" but still let the caller decide where to present it.
By doing it like this, I could use my alert-generation-struct anywhere I want and for example let a delegate present it or pass it around until I reach a UIViewController who can present it, if my caller isn't one.
Therefore in my very simple case, I'll go with:
enum MyAlertType {
case a
case b
}
struct MyAlert {
static func showAlert(ofType type: MyAlertType, handler: ((UIAlertAction) -> ())? = nil) -> UIAlertController {
var message: String
switch type {
case .a:
message = "A is a nice letter!"
case .b:
message = "B is a nice letter!"
}
let alert = UIAlertController(title: "Do you know which letter is nice?", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: handler))
return alert
}
}
Thanks to everybody participating in dissolving the blockade in my head.
Making static function is common and convenient approach. Please, pay attention that type you created is only for namespacing.
Alec O's answer is nice, but sometimes you want to pass some action you want to perform when you press OK button. Also I would pick struct instead of class.
Here is my version of making alert:
struct Alert {
static func errorAlert(title: String, message: String?, cancelButton: Bool = false, completion: (() -> Void)? = nil) -> UIAlertController {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
let actionOK = UIAlertAction(title: "OK", style: .default) {
_ in
guard let completion = completion else { return }
completion()
}
alert.addAction(actionOK)
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
if cancelButton { alert.addAction(cancel) }
return alert
}
And usage in ViewController's subclass:
// Creating alerts:
let simpleAlert = Alert.errorAlert(title: "Error", message: "Simple message")
let alertWithCompletionAndCancel = Alert.errorAlert(title: "Message", message: "Message", cancelButton: true) {
// do something awesome
}
// Presenting alerts:
present(simpleAlert, animated: true)
present(alertWithCompletionAndCancel, animated: true)
I would suggest returning the alert from the function for the view controller to show. The custom alert class should not be presenting anything, if your aim is to use best practices.
class MyAlert {
static func generateAlert(ofType type: MyAlertType) -> UIAlertController {
var message: String
switch type {
case .a:
message = "A is a nice letter!"
case .b:
message = "B is a nice letter!"
}
let alert = UIAlertController(title: "Do you know which letter is nice?", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: handler))
return alert
}
}
This will be the code that you repeat throughout the application. If multiple alerts are used in one view controller then you can initialized MyAlert globally for that view controller.
let myAlert = MyAlert()
let alert = myAlert.generateAlert(ofType: MyAlertType.a)
self.present(alert)
![This is the way I did
]1
When I want to present an alert, I did this:
let alertControl = AlertController.shared.alert(alertTitle: "Error Login", msg: "Please provide your email and password to login", style: .cancel, titleForAlertBtn: "OK")
present(alertControl, animated: true, completion: nil)

Attempting to create an object to act as an UIAlertController wrapper

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.")

Twitter sharing, SLComposeViewController

Having set up a Twitter sharing function in an iOS app, I have a question.
Here is the code, it is based on what I have found, browsing the net:
func shareImageOnTwitter() {
if SLComposeViewController.isAvailableForServiceType(SLServiceTypeTwitter){
let twit = SLComposeViewController(forServiceType: SLServiceTypeTwitter)
// Do all the necessary local cooking for whatever we want to share ………….
} else {
// In which case are we suppose to be here ?????
var alert = UIAlertController(title: "Accounts", message: "Please login to a Twitter account to share.", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
}
Let me say that it is basically working. But the question is when do we reach the second branch of the if.
That is where my comment (// In which case are we suppose to be here ?????) is.
I have made a few experiments to prevent Twitter from working, I then get other messages, but never the ones in the code above.
So when is exactly this code supposed to execute? In which exact conditions?

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