How to get UIViewController in a singleton? - ios

I'm using a singleton class to validate a network response. Inside the function (validResponse()) I'm calling in the singleton, I call another function which pops up an alert box, to let the user know there was an error.
The function inside my singleton class:
func validResponse(data: Data?, response: URLResponse?, error: Error?, viewController: UIViewController, context: String?) -> Bool {
...
DispatchQueue.main.async {
AlertHelper.showAlertWrapper(viewController: viewController, alertTitle: "Error", alertMessage: self.genericError)
}
}
The AlertHelper code:
class AlertHelper {
static func showAlertWrapper(viewController: UIViewController, alertTitle: String, alertMessage: String) {
let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert);
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil);
alertController.addAction(okAction);
viewController.present(alertController, animated: true, completion: nil);
}
}
Calling validResponse():
let result = self.networkHelper.validResponse(data: data, response: response, error: error, viewController: self, context: "Delete section")
In the above instance self is not going to work, and is just temporarily there until I figure out what to do. I understand I could just pass the relevant UIViewController in viewController, like I did for showAlertWrapper. However this is a bit messy.
Is there some way I can reference the currently present view controller in my singleton class, so that I dont have to pass it in validResponse()?

You may want to get the top most view controller currently showing in your app. You can do this by retrieving the top most view controller when your singleton wants to display an error.
class AlertHelper {
static func showAlertWrapper(alertTitle: String, alertMessage: String) {
let alertController = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert);
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil);
alertController.addAction(okAction);
if var topController = UIApplication.sharedApplication().keyWindow?.rootViewController {
while let presentedViewController = topController.presentedViewController {
topController = presentedViewController
}
// viewController should now be your topmost view controller
viewController.present(alertController, animated: true, completion: nil);
}
}
}
And call your showAlertWrapper:
AlertHelper.showAlertWrapper(alertTitle: "Error", alertMessage: self.genericError)

A workaround and maybe swiftier way would be to have the showAlertWrapperin the UIViewController:
extension UIViewController {
func showAlertWrapper(title: String, message: String) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert);
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil);
alertController.addAction(okAction);
present(alertController, animated: true, completion: nil);
}
}
Then you would just do
DispatchQueue.main.async {
viewController.showAlertWrapper(title: "Error", message: self.genericError)
}

Related

Passing the function of the UIAlertAction to the UIAlertController extension

I want to have a base UIAlertController and I want to use it in different classes by just passing the buttons and their closures. To achieve this, I created an extension from UIAlertController like below:
extension UIAlertController {
func showAlert(buttons: [ButtonsAction]?) -> UIAlertController {
let alert = self
guard let alertButtons = buttons else {
return alert
}
for button in alertButtons {
let alertAction = UIAlertAction(title: button.title, style: button.style, handler: {action in
button.handler()
})
alert.addAction(alertAction)
}
return alert
}
}
for my buttons I have a struct:
struct ButtonsAction {
let title: String!
let style: UIAlertAction.Style
let handler: () -> Void
}
In one of my viewControllers I have a function which shows the alert. In that function I have a title and a message then I want to have 1 button to dismiss the alert. The function is something like this:
func fetchFaild(title: String, message: String) {
let buttons = ButtonsAction.init(title: "cancel", style: .cancel, handler: {action in
//here I want to dissmiss the alert I dont know how
})
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert).showAlert(buttons: buttons)
alert.show(self, sender: nil)
}
I have problem adding buttons to the Alert and I don't know how to add actions to the buttons.
I know this is not the best practice here. If any one knows any example or any tutorial that can help me achieve this I really appreciate it.
An extension of UIViewController might be a more reasonable solution and the ButtonsAction struct seems to be redundant.
extension UIViewController {
func showAlert(title: String, message: String, actions: [UIAlertAction], completion: (() -> Void)? = nil) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
actions.forEach{alertController.addAction($0)}
self.present(alertController, animated: true, completion: completion)
}
}
class MyController : UIViewController {
func fetchFailed(title: String, message: String) {
let actions = [UIAlertAction(title: "Cancel", style: .cancel, handler: { (action) in
print("Cancel tapped")
})]
showAlert(title: title, message: message, actions: actions)
}
}

UIAlertController is not working in model class

I want to show an alert. But I want to show it by creating a function in another class and call that function from a viewcontroller. But it does not work.
Here is code from my LoginViewController:
class LoginViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
LoginModel().show_alert()
}
}
Here is code from my LoginModel:
class LoginModel{
let controller = LoginViewController()
public func show_alert(){
let alert = UIAlertController(title: "Title", message: "Some Message",
preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler:
nil))
controller.present(alert, animated: true, completion: nil)
}
}
You need to pass the reference of UIViewController subclass to your LoginModel class, to present the UIAlertViewController on LoginViewController. You should call show alert once the LoginViewController view is appeared on the screen, move the call to ViewWillApear or viewDidApear method
final class LoginViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidApear(animated)
LoginModel().show_alert(on: self)
}
}
final class LoginModel {
public func show_alert(on vc: UIViewController) {
let alert = UIAlertController(title: "Title", message: "Some Message",
preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler:
nil))
vc.present(alert, animated: true, completion: nil)
}
}
ideally you should not create the UI related methods in model classes, they should be on on UIViewController/UIView classes or their extension methods. Model classes should not know anything about UI stuff. So you can easily create simple extension method on UIViewController and call the showAlert method from viewController.
extension UIViewController {
func showAlert(_ title: String = "Alert", message: String) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler:
nil))
present(alert, animated: true, completion: nil)
}
}
you can call this method from UIViewController like
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
showAlert(message: "This is alert message")
}
Reminder: Your model shouldn't know about any UI related stuffs. Instead you should create an extension to UIViewController, or create free function
As a free function
func showAlertViewOnto(controller: UIViewController, detailInfo: (title: String?, message: String?), handler: ((UIAlertAction) -> Void)? = nil ) {
let alert = UIAlertController(title:detailInfo.title , message: detailInfo.message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler:
handler))
controller.present(alert, animated: true, completion: nil)
}
As an extension to your UIViewController
extension UIViewController {
func showAlertView(detailInfo: (title: String?, message: String?), handler: ((UIAlertAction) -> Void)? = nil ) {
let alert = UIAlertController(title:detailInfo.title , message: detailInfo.message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler:
handler))
self.present(alert, animated: true, completion: nil)
}
}
To Use it
showAlertViewOnto(controller: self, detailInfo: (title: "Hello ", message: "welcome to our service"), handler: { _ in
// here you can add code once ok is pressed
})
There is a logical error in your code. An instance of LoginViewController is already present (in the navigation stack or is the initial view controller) which appears on the screen.
You created a new instance of LoginViewController in your model class
let controller = LoginViewController()
which is not added to your navigation stack, so you don’t see it on the screen.
controller.present(alert, animated: true, completion: nil)
Presenting UIAlertController over the new instance will not show user an alert since the controller here itself is not present on the screen.
You will need to present UIAlertController from the instance visible on screen(the initial one). You could change the functionshow_alert to the following:
class LoginModel{
func showAlert(forController controller: UIViewController /*you could add title, message and other stuff here if needed.*/){
let alert = UIAlertController(title: "Title", message: "Some Message",
preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler:
nil))
controller.present(alert, animated: true, completion: nil)
}
And modify your call as follows:
LoginModel().showAlert(forController: self)

UIViewController and inheritance

I have multiple view controllers in my application. And in each of them I have to show alerts based on some conditions. Instead of adding alert controllers in each of them, I tried using inheritance as follows.
UIExtension.swift
class UIExtension: UIViewController {
func prepareAlert(title: String, message: String) -> UIAlertController {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
return alert
}
}
FirstViewController.swift
class FirstViewController: UIExtension {
//somewhere inside used the following
present(prepareAlert(title: "Error Validation", message: "invalid fields"), animated: true, completion: nil)
}
Similarly, used UIExtension in other viewcontrollers to show alerts. Is this way recommended?
For something like this, you are better off adding your prepareAlert method to a UIViewController extension. No subclassing required.
extension UIViewController {
func prepareAlert(title: String, message: String) -> UIAlertController {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
return alert
}
}
Then your view controller:
class FirstViewController: UIViewController {
//somewhere inside used the following
present(prepareAlert(title: "Error Validation", message: "invalid fields"), animated: true, completion: nil)
}
This allows you to use prepareAlert from any view controller include UITableViewController, UICollectionViewController, etc.
The approach is technically correct, although if you consider extending all UIViewController instances, regardless of any conditions, then it's more handy to extend it directly:
extension UIViewController {
func prepareAlert(title: String, message: String) -> UIAlertController {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
return alert
}
}
rmaddy was faster. But I decided not to delete the answer, but add another idea.
Another approach is to use protocol as a wrapper for certain functionality, and this is also widely used.
Say, you have a protocol, associated with some functionality, like generating alert, in this case:
protocol Alertable {} // or whatever else name
extension Alertable {
func prepareAlert(title: String, message: String) -> UIAlertController {
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
return alert
}
}
Then, whenever you want certain UIViewController instance (or any other class, you get the idea) to be associated with this functionality, simply do:
class FirstViewController: UIViewController, Alertable {
// Now you can do the same:
present(prepareAlert(title: "Error Validation", message: "invalid fields"), animated: true, completion: nil)
}
To sum up, making up a protocol and extending it, and then associating certain classes with it - to expose that functionality - is a very handy and useful practice. In particular, this is a good way to encapsulate some functionality, for example, if you don't mean global/class-wide access to it.
Some extension method I am sharing with you that is used frequently in most application you can use it in any where of UIViewController class and enjoy :)
extension UIViewController {
let kAPPNAME = "Your App name"
func showOkAlert(_ msg: String) {
let alert = UIAlertController(title:
kAPPNAME, message: msg, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
}
func showOkAlertWithHandler(_ msg: String,handler: #escaping ()->Void){
let alert = UIAlertController(title: kAPPNAME, message: msg, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default) { (type) -> Void in
handler()
}
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
}
func showAlertWithActions(_ msg: String,titles:[String], handler:#escaping (_ clickedIndex: Int) -> Void) {
let alert = UIAlertController(title: kAPPNAME, message: msg, preferredStyle: .alert)
for title in titles {
let action = UIAlertAction(title: title, style: .default, handler: { (alertAction) in
//Call back fall when user clicked
let index = titles.index(of: alertAction.title!)
if index != nil {
handler(index!+1)
}
else {
handler(0)
}
})
alert.addAction(action)
}
present(alert, animated: true, completion: nil)
}
func showOkCancelAlertWithAction(_ msg: String, handler:#escaping (_ isOkAction: Bool) -> Void) {
let alert = UIAlertController(title: kAPPNAME, message: msg, preferredStyle: .alert)
let okAction = UIAlertAction(title: "OK", style: .default) { (action) -> Void in
return handler(true)
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action) -> Void in
return handler(false)
}
alert.addAction(cancelAction)
alert.addAction(okAction)
present(alert, animated: true, completion: nil)
}
}
USES
class FirstViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Only Info
self.showOkAlert("Hello")
//Info with Okay button
self.showOkAlertWithHandler("Hello Again") {
print("Tap to Okay")
}
//Show alert with Okay and cancel
self.showOkCancelAlertWithAction("Hello with Cancel") { (isOk) in
if isOk {
print("Okay")
}
else {
print("Cancel")
}
}
//Show alert with actions
self.showAlertWithActions("Hello with action", titles: ["Allow","Don't Allow", "Cancel"]) { (tapIndex) in
if tapIndex == 1 {
print("Allow")
}
}
}
}

create alert function in all view controllers - swift

I'm trying to declare a function for showing alerts in my app. To avoid repeating work, i'm trying to use same function for all my app. I tried to do that by creating a class with function showNotification. but when i create an object of that class and call the method, nothing happens. How can i do that?
class SharedPropertiesAndMetods : UIViewController {
func showNotification(title: String, message: String)
{
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "تائید", style: .default, handler: nil)
alertController.addAction(defaultAction)
present(alertController, animated: true, completion: nil)
}
}
Use an extension like this
extension UIViewController {
func showAlert(title: String, message: String) {
let alertController = UIAlertController(title: title, message:
message, preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: {action in
}))
self.present(alertController, animated: true, completion: nil)
}
}
call the function like this
self.showAlert(title: "hi", message: "test")
What I would do is to create a 'generic' view controller that do the job and than inherit from it:
1. If you want to display alert each time view did appear:
class GenericViewController: UIViewController {
// MARK: - View lifecycle -
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if let notification = self.shouldDisplayAlertNotification() {
self.showNotification(notification)
}
}
// MARK: - Internal methods -
func shouldDisplayAlertNotification() -> AlertNotification? {
return nil
}
// MARK: - Private methods -
private func showNotification(_ alertNotification: AlertNotification) {
}
}
class MyController: GenericViewController {
override func shouldDisplayAlertNotification() -> AlertNotification? {
return AlertNotification(title: "Title", message: "Message")
}
}
Where AlertNotification is your custom model class:
class AlertNotification {
var title: String
var message: String
init(title: String, message: String) {
self.title = title
self.message = message
}
}
In this way, only VC that overrides shouldDisplayAlertNotificationwill display alert.
2. If you want to display alert on 'demand':
As suggested, extend UIViewController
extension UIViewController {
func showNotification(title: String, message: String) {
}
}
Actually you can declare a simple method anywhere outside class.
func showAlertWithCompletion(message:String,okTitle:String,cancelTitle:String?,completionBlock:#escaping (_ okPressed:Bool)->()){
let alertController = UIAlertController(title: AppName, message: message, preferredStyle: .alert)
let okAction = UIAlertAction(title: okTitle, style: .default) { (ok) in
completionBlock(true)
}
alertController.addAction(okAction)
if let cancelTitle = cancelTitle{
let cancelOption = UIAlertAction(title: cancelTitle, style: .cancel, handler: { (axn) in
completionBlock(false)
})
alertController.addAction(cancelOption)
}
if let topController = UIWindow.topViewController(){
topController.present(alertController, animated: true, completion: nil)
}
}
This way wherever you call it, you will get ok button pressed callback in completion handle or even make Extension as described by #Ganesh Kumar
Why not just an extension
extension UIViewController {
func showNotification(title: String, message: String)
{
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let defaultAction = UIAlertAction(title: "تائید", style: .default, handler: nil)
alertController.addAction(defaultAction)
present(alertController, animated: true, completion: nil)
}
}
You can use this view controller extension to present alert view across the application.
https://github.com/SumitKr88/UIViewController-ShowAlertView/blob/master/UIViewController%2BExtensions.swift
extension UIViewController {
/// Show alert view
/// - Parameter title: title of alert
/// - Parameter message: message of alert
/// - Parameter actionTitles: List of action button titles(ex : "OK","Cancel" etc)
/// - Parameter style: Style of the buttons
/// - Parameter actions: actions repective to each actionTitles
/// - Parameter preferredActionIndex: Index of the button that need to be shown in bold. If nil is passed then it takes cancel as default button.
/**
Example usage:-
Just make sure actionTitles and actions array the same count.
/********** 1. Pass nil if you don't need any action handler closure. **************/
self.showAlert(title: "Title", message: "message", actionTitles: ["OK"], style: [.deafult], actions: [nil])
/*********** 2. Alert view with one action **************/
/// let okActionHandler: ((UIAlertAction) -> Void) = {(action) in
//Perform action of Ok here
}
self.showAlert(title: "Title", message: "message", actionTitles: ["OK", "CANCEL"], style: [.default, .cancel], actions: [okayActionHandler, nil])
/********** 3.Alert view with two actions **************/
let okActionHandler: ((UIAlertAction) -> Void) = {(action) in
//Perform action of ok here
}
let cancelActionHandler: ((UIAlertAction) -> Void) = {(action) in
//Perform action of cancel here
}
self.showAlert(title: "Title", message: "message", actionTitles: ["OK", "CANCEL"], style: [.default, .cancel], actions: [okActionHandler,cancelActionHandler], preferredActionIndex: 1)
*/
public func showAlert(title: String?,
message: String?,
actionTitles: [String?],
style: [UIAlertAction.Style],
actions: [((UIAlertAction) -> Void)?],
preferredActionIndex: Int? = nil) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
for (index, title) in actionTitles.enumerated() {
let action = UIAlertAction(title: title, style: style[index], handler: actions[index])
alert.addAction(action)
}
if let preferredActionIndex = preferredActionIndex { alert.preferredAction = alert.actions[preferredActionIndex] }
self.present(alert, animated: true, completion: nil)
}}
You could create extension to alertController and also have option for action handler. This will allow to use two different Alert controller based on handler is required or not.
extension UIAlertControler {
class func genericErrorAlert(forAlert message: String, completion: ((UIAlertAction) -> Void)? = nil )
let alert = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: completion))
return alert
}
}
You can also create one util file in your app, in that you can add any reusable method or function and use it anywhere in your app Like,
import Foundation
import UIKit
//MARK: - ALERT
func showMessage(title: String, message: String!, VC: UIViewController) {
let alert : UIAlertController = UIAlertController(title: "", message: message, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) {
UIAlertAction in
}
alert.addAction(okAction)
VC.present(alert, animated: true, completion: nil)
}

Make a class comprises of the functions like UIAlertView, UIActivityIndicator and call them back in various viewControllers

This is my current code:
import UIKit
class classViewController: UIViewController {
// The function i want to call in other view controllers..
func alertView(title: String, message: String) {
var alert:UIAlertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: { (action) -> Void in
self.dismissViewControllerAnimated(true, completion: nil)
}))
self.presentViewController(alert, animated: true, completion: nil)
}
}
In the other view controller, where I've made an IBAction to perform this alertView, I have done this:
#IBAction func button(sender: AnyObject) {
classViewController().alertView("title", message: "message")
}
When I run the app, after tapping the button I get this error, but no alertView:
Warning: Attempt to present on
whose view is not in the
window hierarchy!
Right. If you want to make a global class that displays alerts, you need to pass in a reference to the current view controller, and use that instead of "self" in calls like presentViewController.
Your class should probably not be a subclass of UIViewController, since it looks like you're never displaying it to the screen.
I created a Utils class that is a subclass of NSObject.
It has a method showAlertOnVC that looks like this:
class func showAlertOnVC(targetVC: UIViewController?, var title: String, var message: String)
{
title = NSLocalizedString(title, comment: "")
message = NSLocalizedString(message, comment: "")
if let targetVC = targetVC
{
let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.Alert)
let okButton = UIAlertAction(
title:"OK",
style: UIAlertActionStyle.Default,
handler:
{
(alert: UIAlertAction!) in
})
alert.addAction(okButton)
targetVC.presentViewController(alert, animated: true, completion: nil)
}
else
{
println("attempting to display alert to nil view controller.")
println("Alert title = \(title)")
println("Alert message = \(message)")
}
}

Resources