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.
Related
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)
}
My Swift view controller is called from my mainly Objective C app. Calling function 'showAlert()' displays, but freezes the emulator. I get no error, but none of the buttons work and the textfield doesn't raise the keyboard (the placeholder shows up). (My actual code has more alertActions, which work fine if I comment out the TextField code). I even copied verbatim code from StackOverflow to display a basic alert controller with a TextField, and it has the same issue as written below.
#objc class MyVC :UIViewController {
#objc func showAlert() {
let alertController = UIAlertController(title: "Title", message: "", preferredStyle: .alert)
alertController.addTextField { (textField : UITextField!) -> Void in
textField.placeholder = "Enter name"
}
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: .cancel, handler: {
(action : UIAlertAction!) -> Void in })
alertController.addAction(cancelAction)
alertController.addAction(saveAction)
alertController.preferredAction = saveAction
self.present(alertController, animated: true, completion: nil)
}
}
A breakpoint at 'present(...' show the alert controller allocated, but stepping in from here ends with the frozen display of the alertcontroller. Any debug suggestions greatly appreciated.
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)
}
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])
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.