CMan
CMan

Reputation: 71

How to display an Alert from a different class

I created a Utilities class to hold some common functions, one of which is an alertUser function that if called, will display an Alert box to the user with the provided title and message text. In another class file, I am validating some text field entries and if the validation doesn't pass, then I want to use the alertUser function from the Utilities class. However, when I do this, I get the following error message in the Xcode log:

Warning: Attempt to present <UIAlertController: 0x7f9c4be0b140> on <MyAppName.Utilities: 0x7f9c4be1cb60> whose view is not in the window hierarchy!

The calling code is in a UIViewController class file. Here's the code which is in the class ItemSettingsVC: UIViewController:

private func validateNameField() -> Bool {
    var passed = false

    if (nameField.hasText) {
        passed = true
    } else {
        Utilities().alertUser(strTitle: "Alert", strMessage: strInvalidNameFieldErrorMsg)
        passed = false
    }
    return passed
}

Here's the alertUser function which is in the class Utilities: UIViewController:

public func alertUser(strTitle: String, strMessage: String) {
    let myAlert = UIAlertController(title: strTitle, message: strMessage, preferredStyle: UIAlertControllerStyle.alert)
    let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil)
    myAlert.addAction(okAction)
    self.present(myAlert, animated: true, completion: nil)
}

This is running on iOS. I'm using Xcode 8 and swift 3. Any help is greatly appreciated. Thanks.

Upvotes: 4

Views: 4026

Answers (4)

Virajkumar Patel
Virajkumar Patel

Reputation: 1562

Use this class for easy to show Alert or ActionSheet

UIAlertController Extension

public extension UIAlertController {
    public func showAlert(animated: Bool = true, completionHandler: (() -> Void)? = nil) {
        guard let rootVC = UIApplication.shared.keyWindow?.rootViewController else {
            return
        }
        var forefrontVC = rootVC
        while let presentedVC = forefrontVC.presentedViewController {
            forefrontVC = presentedVC
        }
        forefrontVC.present(self, animated: animated, completion: completionHandler)
    }
}

AppAlert Class Create For UIAlertController Show

public class AppAlert {
    private var alertController: UIAlertController

    public init(title: String? = nil, message: String? = nil, preferredStyle: UIAlertControllerStyle) {
        self.alertController = UIAlertController(title: title, message: message, preferredStyle: preferredStyle)
    }

    public func setTitle(_ title: String) -> Self {
        alertController.title = title
        return self
    }

    public func setMessage(_ message: String) -> Self {
        alertController.message = message
        return self
    }

    public func setPopoverPresentationProperties(sourceView: UIView? = nil, sourceRect:CGRect? = nil, barButtonItem: UIBarButtonItem? = nil, permittedArrowDirections: UIPopoverArrowDirection? = nil) -> Self {

        if let poc = alertController.popoverPresentationController {
            if let view = sourceView {
                poc.sourceView = view
            }
            if let rect = sourceRect {
                poc.sourceRect = rect
            }
            if let item = barButtonItem {
                poc.barButtonItem = item
            }
            if let directions = permittedArrowDirections {
                poc.permittedArrowDirections = directions
            }
        }

        return self
    }

    public func addAction(title: String = "", style: UIAlertActionStyle = .default, handler: @escaping ((UIAlertAction!) -> Void) = { _ in }) -> Self {
        alertController.addAction(UIAlertAction(title: title, style: style, handler: handler))
        return self
    }

    public func addTextFieldHandler(_ handler: @escaping ((UITextField!) -> Void) = { _ in }) -> Self {
        alertController.addTextField(configurationHandler: handler)
        return self
    }

    public func build() -> UIAlertController {
        return alertController
    }
}

Used For Open AlertBox

AppAlert(title: "Question", message: "Are you sure?", preferredStyle: .alert)
    .addAction(title: "NO", style: .cancel) { _ in
        // action
    }
    .addAction(title: "Okay", style: .default) { _ in
         // action
    }
    .build()
    .showAlert(animated: true)

Used For ActionSheet Open

if UIDevice.current.userInterfaceIdiom != .pad {
    // Sample to show on iPhone
    AppAlert(title: "Question", message: "Are you sure?", preferredStyle: .actionSheet)
        .addAction(title: "NO", style: .cancel) {_ in
            print("No")
        }
        .addAction(title: "YES", style: .default) { _ in
            print("Yes")
        }
        .build()
        .showAlert(animated: true)
} else {
    // Sample to show on iPad
    AppAlert(title: "Question", message: "Are you sure?", preferredStyle: .actionSheet)
        .addAction(title: "Not Sure", style: .default) {
            _ in
            print("No")
        }
        .addAction(title: "YES", style: .default) { _ in
           print("Yes")
        }
        .setPopoverPresentationProperties(sourceView: self, sourceRect: CGRect.init(x: 0, y: 0, width: 100, height: 100), barButtonItem: nil, permittedArrowDirections: .any)
        .build()
        .showAlert(animated: true)
}

Upvotes: 1

Arnav
Arnav

Reputation: 688

First find out the topmost viewController on your window .

Get the top ViewController in iOS Swift

and then present your alert on that viewController.No need to pass any parameter.

public func alertUser(strTitle: String, strMessage: String) {
let myAlert = UIAlertController(title: strTitle, message: strMessage, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil)
myAlert.addAction(okAction)
topmostVC().present(myAlert, animated: true, completion: nil)

}

Upvotes: 0

Ace Rivera
Ace Rivera

Reputation: 620

You have to add an additional parameter in your alertUser function, which would be the VC that will present the alert controller.

for example:

public func alertUser(strTitle: String, strMessage: String, viewController: UIViewController) {
    let myAlert = UIAlertController(title: strTitle, message: strMessage, preferredStyle: UIAlertControllerStyle.alert)
    let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil)
    myAlert.addAction(okAction)
    viewController.present(myAlert, animated: true, completion: nil)
}

But I would recommend that you just make an extension of UIViewController and add your func alertUser()* there because you would surely use this alertUser in different VCs and complexity wise in my opinion, this would be more optimized.

Like this:

extension UIViewController {

  func showAlert(title: String, message: String, callback: @escaping () -> ()) {
     let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
     alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: {
       alertAction in
       callback()
     }))

     self.present(alert, animated: true, completion: nil)
   }

   //add additional functions here if necessary
   //like a function showing alert with cancel
}

NOTE : Please don't make your Utilities class a subclass of UIViewController, it would also be better to make it a struct handling static functions and/or variables

Upvotes: 2

Bobby
Bobby

Reputation: 6255

This should do it:

public func alertUser(strTitle: String, strMessage: String) {
    let myAlert = UIAlertController(title: strTitle, message: strMessage, preferredStyle: UIAlertControllerStyle.alert)
    let okAction = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil)
    myAlert.addAction(okAction)
    UIApplication.shared.delegate?.window??.rootViewController?.present(myAlert, animated: true, completion: nil)
}

Upvotes: 4

Related Questions