How can I output UIAlertController to a separate file and output data from there? - swift

I'm trying to output alert to a separate function, since there will be many similar ones.
Here is my alert:
extension UIViewController {
func alertEditSum(nameCell: String, completion: (() -> Void)) {
let alertController = UIAlertController(title: "Hello", message: "", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Save", style: .default , handler: { _ in
let nameFolderField = alertController.textFields![0] as UITextField
if nameFolderField.isTextFieldCheck(text: nameFolderField.text!) == true {
// -----here----
}
}))
alertController.addAction(UIAlertAction(title: "Cancel"
, style: .cancel, handler: nil))
alertController.addTextField(configurationHandler: { (nameField: UITextField!) -> Void in
nameField.clearButtonMode = .always
nameField.keyboardType = .decimalPad
})
self.present(alertController, animated: true)
}
}
and my piece of code is in another VC:
self.sortedDate[indexPath.section-1].personPayment = Double(nameFolderField.text!)!
do {
try! self.context.save()
collectionView.reloadData()
}
The problem is that I need to consider what exactly the user enters in UITextField. text! (nameFolderField.text!). I also can't add completion to the alert code, writes an error.
Completion should be added to the line where it says: / / - - - - - here----
Please tell me how to solve this problem?

This should do the trick:
func alertEditSum(nameCell: String, completion: #escaping ((String?) -> Void)) {
let alertController = UIAlertController(title: "Hello", message: "", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Save", style: .default, handler: { _ in
let nameFolderField = alertController.textFields![0] as UITextField
if nameFolderField.isTextFieldCheck(text: nameFolderField.text!) == true {
completion(nameFolderField.text)
}
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { _ in
completion(nil)
}))
alertController.addTextField(configurationHandler: { nameField in
nameField.clearButtonMode = .always
nameField.keyboardType = .decimalPad
})
self.present(alertController, animated: true)
}
To call it:
theViewController.alertEditSum(nameCell: "text") { text in
if let text = text {
//Do stuff
} else { //Text is invalid or user has cancel
}
}
Now, isTextFieldCheck(text:) is I guess a method on UITextField, since it's checking its own text, why giving it as a parameter?
Why not just func isTextValid()?
I would also avoid the force unwrap: !.
Going further, would be to use Result<String, Error> in the completion:
completion((Result<String, Error>) -> Void)) to have more infos if needed (user has canceled, text wasn't not valid for any reason, etc.)

You neeed #escaping completion to send value from inside a closure to a caller , Main changes completion:#escaping (String?) -> Void) and completion(nameFolderField.text)
extension UIViewController {
func alertEditSum(nameCell: String,completion:#escaping (String?) -> Void) {
let alertController = UIAlertController(title: "Hello", message: "", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Save", style: .default , handler: { _ in
let nameFolderField = alertController.textFields!.first!
if nameFolderField.isTextFieldCheck(text: nameFolderField.text!) {
completion(nameFolderField.text)
}
}))
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
alertController.addTextField(configurationHandler: { (nameField: UITextField!) -> Void in
nameField.clearButtonMode = .always
nameField.keyboardType = .decimalPad
})
self.present(alertController, animated: true)
}
}
Call
alertEditSum(nameCell:<#SomeValue#>) { result in
print(result)
}

Related

Passing selector to a different class in swift5

I have a view controller which with a button to restore the default settings. Contained within the same VC is the function restoreDefaults()
#IBAction func resetBtnAct(_ sender: Any) {
let alert = AlertGenFromVC(sendingVC: self).generateAlert(
titleIn: "Warning!",
messageIn: "You are about to reset to the default settings. You will not be able to undo this action.",
actionTitle1: "Reset", actionAct1: #selector(self.restoreDefaults), actionTitle2: "Cancel", actionAct2: nil)
self.present(
alert,
animated: false,
completion: nil)
}
I am passing this to another class which is then used to present the alert controller
class AlertGenFromVC {
var originVC: AnyObject?
var originTextField: UITextField?
init(sendingVC: AnyObject) {
originVC = sendingVC
}
func generateAlert(titleIn: String, messageIn: String, actionTitle1:String, actionAct1:Selector, actionTitle2:String?, actionAct2:Selector?) -> UIAlertController {
let alert = UIAlertController(
title: titleIn,
message: messageIn,
preferredStyle: .alert)
// Add first action
let action1 = UIAlertAction(title: actionTitle1, style: UIAlertAction.Style.default, handler: {(action) in actionAct1
})
alert.addAction(action1)
//Action 2
if actionTitle2 != nil && actionAct2 != nil {
print("act2")
let action2 = UIAlertAction(title: actionTitle2, style: UIAlertAction.Style.default, handler: {action in actionAct2
})
alert.addAction(action2)
} else if actionTitle2 != nil && actionAct2 == nil {
print("action 2 nil")
let action2 = UIAlertAction(title: actionTitle2, style: UIAlertAction.Style.default, handler: nil)
alert.addAction(action2)
}
let popover = alert.popoverPresentationController
popover?.sourceView = self.originVC as? UIView
popover?.sourceRect = CGRect(x: 32, y: 32, width: 64, height: 64)
return alert
}
}
Action 1 and 2 both show a warning "Expression of type 'Selector' is unused".
The alert is presented fine but there is no action on clicking the button. Am i defining the selectors incorrectly or am I doing something else wrong? All suggestions gratefully received.
An extra class to create an alert controller to be presented by an UIViewController is an unswifty approach.
A better one is an extension of UIViewController
extension UIViewController {
func showAlert(titleIn: String,
messageIn: String,
actionTitle1: String,
actionAct1: (() -> Void)? = nil,
actionTitle2: String?,
actionAct2: (() -> Void)? = nil) {
let alert = UIAlertController(
title: titleIn,
message: messageIn,
preferredStyle: .alert)
// Add first action
let action1 = UIAlertAction(title: actionTitle1, style: .default, handler: { _ in actionAct1?() })
alert.addAction(action1)
//Action 2
if let title2 = actionTitle2 {
print(title2)
let action2 = UIAlertAction(title: title2, style: .default, handler: { _ in actionAct2?() })
alert.addAction(action2)
}
let popover = alert.popoverPresentationController
popover?.sourceView = self.view
popover?.sourceRect = CGRect(x: 32, y: 32, width: 64, height: 64)
self.present(alert, animated: true, completion: nil)
}
and use it
func restoreDefaults() {
// do something
}
func resetBtnAct(_ sender: Any) {
self.showAlert(
titleIn: "Warning!",
messageIn: "You are about to reset to the default settings. You will not be able to undo this action.",
actionTitle1: "Reset",
actionAct1: restoreDefaults,
actionTitle2: "Cancel")
}
You can directly pass the function as a parameter. For example like this:
let alert = AlertGenFromVC(sendingVC: self).generateAlert(
titleIn: "Warning!",
messageIn: "You are about to reset to the default settings. You will not be able to undo this action.",
actionTitle1: "Reset", actionAct1: self.restoreDefaults, actionTitle2: "Cancel", actionAct2: nil)
The function generateAlert would look like this:
func generateAlert(titleIn: String, messageIn: String, actionTitle1:String, actionAct1: #escaping ((UIAlertAction) -> Void), actionTitle2:String?, actionAct2: ((UIAlertAction) -> Void)? = nil) -> UIAlertController {
let alert = UIAlertController(
title: titleIn,
message: messageIn,
preferredStyle: .alert)
// Add first action
let action1 = UIAlertAction(title: actionTitle1, style: UIAlertAction.Style.default, handler: actionAct1)

Make a network call when textfield has entered

I have an alertview with textfield, and would like to make call if a user enters something in the text field and confirm it. I am confused how to handle this workflow.
func btnTapped(cell: UICollectionViewCell) {
let indexPath = self.collectionView.indexPath(for: cell)
openAlert()
// I want to move the following part once user enters textfield in the alertview
fetchData(urlString: url, data: data[indexPath.row]) { [weak self] (response, error) in
if error == nil
{
DispatchQueue.main.async {
//do any ui update here!
self?.collectionView.reloadData()
}
}
}
}
func openAlert(){
let alertController = UIAlertController(title: "Alert", message: "", preferredStyle: .alert)
alertController.addTextField { (textField : UITextField!) -> Void in
textField.placeholder = "Enter reason"
}
let saveAction = UIAlertAction(title: "Confirm", style: .default, handler: { alert -> Void in
if let textField = alertController.textFields?[0] {
if textField.text!.count > 0 {
print("Text :: \(textField.text ?? "")")
}
}
})
let cancelAction = UIAlertAction(title: "Cancel", style: .default, handler: {
(action : UIAlertAction!) -> Void in })
alertController.addAction(cancelAction)
alertController.addAction(saveAction)
alertController.preferredAction = saveAction
self.present(alertController, animated: true, completion: nil)
}
You need to work with the text field delegates and use textFieldDidChange, then anytime the text is changed on the alert, call the API. Here is a quick example:
func textFieldDidChange(_ textField: UITextField) {
// Make API call here
}
Then in the completion block for the API call, set whatever data you would like depending on if the API confirmed or did not confirm.

I am trying to add specific handlers and alertStyles to my alerts, but I am using a global alert which doesn't have a handler

Is there any separate global function to add a different style and a different handler for alerts?
My function from AppDelegate looks like this:
static func showAlertView(vc : UIViewController, titleString : String , messageString: String) ->()
{
let alertView = UIAlertController(title: titleString, message: messageString, preferredStyle: .alert)
let alertAction = UIAlertAction(title: "ok", style: .cancel) { (alert) in
vc.dismiss(animated: true, completion: nil)
}
alertView.addAction(alertAction)
vc.present(alertView, animated: true, completion: nil)
}
You just need to add more parameters to your function. In the code below I've added the following: controllerStyle for UIAlertController, actionStyle for UIAlertAction and action for UIAlertAction handler.
static func showAlertView(vc : UIViewController, titleString : String , messageString: String, controllerStyle: UIAlertController.Style = .alert, actionStyle: UIAlertAction.Style = .cancel, action: #escaping () -> () = {}) {
let alertView = UIAlertController(title: titleString, message: messageString, preferredStyle: controllerStyle)
let alertAction = UIAlertAction(title: "ok", style: .cancel) { (alert) in
if action == {} {
vc.dismiss(animated: true, completion: nil)
} else {
action()
}
}
alertView.addAction(alertAction)
vc.present(alertView, animated: true, completion: nil)
}

How to create a reusable UIAlert ActionSheet as an UIViewController extension?

I would like to create an action sheet that can be used several time in my code. To do so, I need to be able to use functions according to the action sheet title. Is there a way to pass functions as a parameter array like the "title" parameter?
//MARK: - UIAlert action sheet title
enum ActionSheetLabel: String {
case camera = "Camera"
case photoLibrary = "Album"
case cancel = "Cancel"
}
class CameraHandler {
static let cameraHandler = CameraHandler()
func openCamera() { }
func openPhotoLibrary() { }
}
//MARK: - Alert that shows an action sheet with cancel
extension UIViewController {
func showActionSheetWithCancel(vc: UIViewController, title: [ActionSheetLabel] /*Make a function parameter here to match title*/) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
for value in title {
actionSheet.addAction(UIAlertAction(title: value.rawValue, style: .default, handler: {
(alert: UIAlertAction!) -> Void in
//Use the parameter function here to match title
}))
}
actionSheet.addAction(UIAlertAction(title: ActionSheetLabel.cancel.rawValue, style: .cancel, handler: nil))
vc.present(actionSheet, animated: true, completion: nil)
}
}
For UIAlert you just need to change preferredStyle .alert it and it's working for UIAlert And and below code just copy and paste it working for UIActionSheet.
extension UIViewController {
func popupAlert(title: String?, message: String?, actionTitles:[String?], actionStyle:[UIAlertAction.Style], actions:[((UIAlertAction) -> Void)?]) {
let alert = UIAlertController(title: title, message: message, preferredStyle: .actionSheet)
for (index, title) in actionTitles.enumerated() {
let action = UIAlertAction(title: title, style: actionStyle[index], handler: actions[index])
alert.addAction(action)
}
self.present(alert, animated: true, completion: nil)
}
}
Check below code For Usage
self.popupAlert(title: "Alert"), message: “Error in Loading”, actionTitles: ["Okey", "Email"], actionStyle: [.default, .default], actions: [nil,{ action in
// I have set nil for first button click
// do your code for second button click
}])
if you have any query then please comment me. Thank You
I have find out the best way to add an action sheet with cancel and as much action as needed.
Create an UIViewController extension with type alias:
//MARK: - Alert that shows an action sheet with cancel
extension UIViewController {
typealias AlertAction = () -> ()
typealias AlertButtonAction = (ActionSheetLabel, AlertAction)
func showActionSheetWithCancel(titleAndAction: [AlertButtonAction]) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
for value in titleAndAction {
actionSheet.addAction(UIAlertAction(title: value.0.rawValue, style: .default, handler: {
(alert: UIAlertAction!) -> Void in
value.1()
}))
}
actionSheet.addAction(UIAlertAction(title: ActionSheetLabel.cancel.rawValue, style: .cancel, handler: nil))
self.present(actionSheet, animated: true, completion: nil)
}
}
Then, in the class or other place where you want to use it, add the method this way:
//MARK: - UIAlert action sheet title
enum ActionSheetLabel: String {
case camera = "Camera"
case photoLibrary = "Album"
case cancel = "Cancel"
}
//MARK: - Class example where to use the action sheet action
class CameraHandler {
fileprivate let currentVC: UIViewController!
func openCamera() {
// Open user camera
}
func openPhotoLibrary() {
// Open user photo library
}
// Method example of this action sheet
func showActionSheetWithCameraAndLibrary(vc: UIViewController) {
//This is the way to use the extension
vc.showActionSheetWithCancel(titleAndAction: [
(ActionSheetLabel.camera, { [weak self] in self?.openCamera() }),
(ActionSheetLabel.photoLibrary, { [weak self] in self?.openPhotoLibrary() })
])
}
}
You can pass a closure and call it in the handler something like this should work.
Also not sure why you were passing the UIViewController , as you're already defining the function in a extension UIViewController therefore i allowed my self to remove it and used self.present instead .
extension UIViewController {
func showActionSheetWithCancel(title: [ActionSheetLabel], action: #escaping () -> ()?) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
for value in title {
actionSheet.addAction(UIAlertAction(title: value.rawValue, style: .default, handler: {
(alert: UIAlertAction!) -> Void in
// action
action()
}))
}
let alertAction = UIAlertAction(title: ActionSheetLabel.cancel.rawValue, style: .cancel) { (_) in
action() // or for cancel call it here
}
actionSheet.addAction(alertAction)
self.present(actionSheet, animated: true, completion: nil)
}
}
As you can see #escaping () -> ()? is optional so you can pass nil too .
from what I understood you need to call a specific functions when the title of the alert changes & also you want to be able to do so from different viewControllers,
I hope this will help
extension UIViewController {
func showActionSheetWithCancel(vc: UIViewController, title: [ActionSheetLabel] ) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let cameraHandler = CameraHandler()
for value in title {
switch value.rawValue {
case ActionSheetLabel.camera.rawValue:
actionSheet.addAction(UIAlertAction(title: ActionSheetLabel.camera.rawValue, style: .default, handler: { (alert) in
cameraHandler.openCamera()
}))
case ActionSheetLabel.photoLibrary.rawValue:
actionSheet.addAction(UIAlertAction(title: ActionSheetLabel.photoLibrary.rawValue, style: .default, handler: { (alert) in
cameraHandler.openPhotoLibrary()
}))
default:
actionSheet.addAction(UIAlertAction(title: ActionSheetLabel.cancel.rawValue, style: .cancel, handler: nil))
}
vc.present(actionSheet, animated: true, completion: nil)
}
}
}
and the call of the function will be like this:
showActionSheetWithCancel(vc: self, title: [UIViewController.ActionSheetLabel.camera])

How do we create and dismiss an UIAlertController without user input? (Swift)

I've been looking up a lot of tutorials on UIAlertController. Thus far, the way I found was to activate a UIAlertController by linking it to a button or label and then call a IBAction.
I tried to replicate the code to automatically pop an alert when user enters the app (I wanted to ask the user if they want to go through the tutorial). However, I keep getting the error:
Warning: Attempt to present UIAlertController on MainViewController whose view is not in the window hierarchy!
Then I tried to add the UIAlertController to the MainViewController via addChildViewController and addSubview. However, I get the error:
Application tried to present modally an active controller
I figured that I cannot use the presentViewController function and commented it out.
The UIAlertController is displayed BUT when I tried to click on the cancel or the never button, this error occurs.
Trying to dismiss UIAlertController with unknown presenter.
I am really stumped. Can someone share what I am doing wrong? Thank you so much. Here is the code.
func displayTutorial() {
alertController = UIAlertController(title: NSLocalizedString("tutorialAlert", comment: ""), message: NSLocalizedString("tutorialMsg", comment: ""), preferredStyle: .ActionSheet)
self.addChildViewController(alertController)
self.view.addSubview(alertController.view)
alertController.didMoveToParentViewController(self)
alertController.view.frame.origin.x = self.view.frame.midX
alertController.view.frame.origin.y = self.view.frame.midY
//alertController.popoverPresentationController?.sourceView = self.view*/
let OkAction = UIAlertAction(title: NSLocalizedString("yesh", comment: ""), style: .Destructive) { (action) in
}
alertController.addAction(OkAction)
let cancelAction = UIAlertAction(title: NSLocalizedString("notNow", comment: ""), style: .Destructive) { (action) in
//println(action)
self.tutorial = 1
self.presentedViewController?.dismissViewControllerAnimated(true, completion: nil)
}
alertController.addAction(cancelAction)
let neverAction = UIAlertAction(title: NSLocalizedString("never", comment: ""), style: .Cancel) { (action) in
self.tutorial = 1
}
alertController.addAction(neverAction)
//self.presentViewController(alertController, animated: false) {}
}
I found the solution. Apparently, I cannot call the UIAlertController from the func viewDidLoad. I must call the function from viewDidAppear. So my code now is
override func viewDidAppear(animated: Bool) {
if tutorial == 0 {
displayTutorial(self.view)
}
}
func displayTutorial(sender:AnyObject) {
let alertController = UIAlertController(title: NSLocalizedString("tutorialAlert", comment: ""), message: NSLocalizedString("tutorialMsg", comment: ""), preferredStyle: .ActionSheet)
let OkAction = UIAlertAction(title: NSLocalizedString("yesh", comment: ""), style: .Destructive) { (action) in
}
alertController.addAction(OkAction)
let cancelAction = UIAlertAction(title: NSLocalizedString("notNow", comment: ""), style: .Default) { (action) in
//println(action)
self.tutorial = 1
self.presentedViewController?.dismissViewControllerAnimated(true, completion: nil)
}
alertController.addAction(cancelAction)
let neverAction = UIAlertAction(title: NSLocalizedString("never", comment: ""), style: .Cancel) { (action) in
self.tutorial = 1
}
alertController.addAction(neverAction)
self.presentViewController(alertController, animated: true, completion: nil)
if let pop = alertController.popoverPresentationController {
let v = sender as UIView
pop.sourceView = view
pop.sourceRect = v.bounds
}
}
Thanks to this posting: Warning: Attempt to present * on * whose view is not in the window hierarchy - swift
Below UIAlertController with extension would help you show alert with dynamic number of buttons with completion handler for selected index
extension UIViewController {
func displayAlertWith(message:String) {
displayAlertWith(message: message, buttons: ["Dismiss"]) { (index) in
}
}
func displayAlertWith(message:String, buttons:[String], completion:((_ index:Int) -> Void)!) -> Void {
displayAlertWithTitleFromVC(vc: self, title: Bundle.main.infoDictionary!["CFBundleDisplayName"] as! String, andMessage: message, buttons: buttons, completion: completion)
}
func displayAlertWithTitleFromVC(vc:UIViewController, title:String, andMessage message:String, buttons:[String], completion:((_ index:Int) -> Void)!) -> Void {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
for index in 0..<buttons.count {
let action = UIAlertAction(title: buttons[index], style: .default, handler: {
(alert: UIAlertAction!) in
if(completion != nil){
completion(index)
}
})
alertController.addAction(action)
}
DispatchQueue.main.async {
vc.present(alertController, animated: true, completion: nil)
}
}
}
If you need to auto dismiss the alert you can call dismiss on presented view controller after some delay.
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
vc.dismiss(animated: true, completion: nil)
}
Hope this might help you.