Currently, the UIAlertController appears when the user taps on the HeaderButton. I am trying to make the UIAlertController automatically appear every time the view controller initially launches. Any suggestions?
// MARK: - RestaurantListTableViewHeaderDelegate
extension RestaurantListViewController: RestaurantListTableViewHeaderDelegate {
func didTapHeaderButton(_ headerView: RestaurantListTableViewHeader) {
let locationPicker = UIAlertController(title: "Select location", message: nil, preferredStyle: .actionSheet)
for location in RestaurantListViewController.locations {
locationPicker.addAction(UIAlertAction(title: location, style: .default) { [weak self] action in
guard let `self` = self else { return }
self.currentLocation = action.title
self.tableView.reloadData()
})
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
locationPicker.addAction(cancelAction)
present(locationPicker, animated: true)
}
}
I kept the extension for when the Header Button gets tapped and I added the following to viewDidLoad:
// Code for showing alert
let locationPicker = UIAlertController(title: "Select location", message: nil, preferredStyle: .actionSheet)
for location in RestaurantListViewController.locations {
locationPicker.addAction(UIAlertAction(title: location, style: .default) { [weak self] action in
guard let `self` = self else { return }
self.currentLocation = action.title
self.tableView.reloadData()
})
}
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
locationPicker.addAction(cancelAction)
present(locationPicker, animated: true)
It's not an elegant solution but it will work:
var alertAlreadyShown = false
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if !alertAlreadyShown {
alertAlreadyShown = true
/* Code for showing alert */
}
}
Related
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.
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)
}
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
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)
}
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)
}
I add UICollectionView in my app and each cell has "Delete" trash button.
When I click on this button, it will show alert message to ask. After clicks on OK button, the item in Collection View will delete. However, the UI does not refresh , the item was not deleted.
My Code is ...
override func viewDidLoad() {
super.viewDidLoad()
myCollectionView.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
debugPrint("viewDidAppear")
callWatchLater()
}
override func viewWillAppear(_ animated: Bool) {
DispatchQueue.main.async(execute: {
debugPrint("Appear")
self.myCollectionView.reloadData()
})
}
override func viewDidLayoutSubviews() {
DispatchQueue.main.async(execute: {
debugPrint("SubViews")
self.myCollectionView.reloadData()
})
}
Code for Alert Controller is
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "watchCell", for: indexPath) as! WatchLaterTableCell
let watchTableList = self.watchLaterTable[indexPath.row]
cell.deleteMovie.addTarget(self, action:#selector(showAlert(sender:)), for: .touchUpInside)
}
return cell
}
func showAlert(sender:UIButton!)
{
debugPrint("Press Button")
let alert = UIAlertController(title: "Are you really want to delete this movie?", message: "", preferredStyle: UIAlertControllerStyle.alert)
// Create the actions
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) {
UIAlertAction in
self.callRest()
debugPrint("Press OK")
}
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel) {
UIAlertAction in
_ = self.navigationController?.popViewController(animated: true)
}
// Add the actions
alert.addAction(okAction)
alert.addAction(cancelAction)
// Present the controller
DispatchQueue.main.async(execute: {
self.present(alert, animated: true, completion: nil)
})
}
Code For callRest() is
func callRest(){
SwiftLoading().showLoading()
if Reachability().isInternetAvailable() == true {
rest.auth(auth: access_token)
rest.delete(StringResource().mainURL + "user/" + user_id + "/watch/later/" + String(vedioId) , parma: [:], finished: {(result: NSDictionary, status : Int) -> Void in
debugPrint(result)
if(status == 200){
let data = result["data"] as! NSString
self.messages = String(describing: data)
DispatchQueue.main.sync{[unowned self] in
let alertController = UIAlertController(title: "", message: self.messages , preferredStyle: .alert)
// Create the actions
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) {
UIAlertAction in
debugPrint("call rest Press OK")
self.callWatchLaterGetAPI()
}
alertController.addAction(okAction)
// Present the controller
DispatchQueue.main.async(execute: {
self.present(alertController, animated: true, completion: nil)
})
}
self.myCollectionView.reloadData()
SwiftLoading().hideLoading()
}else{
let error = result["error"] as! NSArray
for item in 0...(error.count) - 1 {
let device : AnyObject = error[item] as AnyObject
self.messages = device["message"] as! String
DispatchQueue.main.sync{[unowned self] in
let alertController = UIAlertController(title: "", message: self.messages , preferredStyle: .alert)
// Create the actions
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default) {
UIAlertAction in
debugPrint("Press OK")
}
alertController.addAction(okAction)
// Present the controller
DispatchQueue.main.async(execute: {
self.present(alertController, animated: true, completion: nil)
})
}
SwiftLoading().hideLoading()
}
}
})
}else{
noInternetConnection()
}
}
However, the CollectionView does not refresh . Can anyone help me this, please?
I'm new to RxSwift, trying to wrap my head around it. I was having trouble getting a UIButton in a cell to show a UIAlertController when it's pressed.
private func setupCellConfiguration() {
bookListViewModel.data
.bindTo(collectionView.rx.items(cellIdentifier: BookListCell.Identifier, cellType: BookListCell.self)) { [unowned self] (row, element, cell) in
cell.configureForBook(book: element)
cell.moreButton.rx.tap.subscribe { [weak self] in
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) {(action) in
self?.dismiss(animated: true, completion: nil)
}
alertController.addAction(cancelAction)
let destroyAction = UIAlertAction(title: "Delete", style: .destructive) { (action) in
}
alertController.addAction(destroyAction)
self?.present(alertController, animated: true)
}
.addDisposableTo(self.disposeBag)
}
.addDisposableTo(disposeBag)
}
Nothing happens when it's pressed. What am I doing wrong here?
I actually prefer to assign cell button action on its subclass. The problem is I think every cell should have it's own disposeBag and it should reinitialize every time it is reused.
Example: Haven't tested on code, if there's any problem let me know
private func setupCellConfiguration() {
bookListViewModel.data
.bindTo(collectionView.rx.items(cellIdentifier: BookListCell.Identifier, cellType: BookListCell.self)) { [unowned self] (row, element, cell) in
cell.delegate = self
cell.configureForBook(book: element)
}
.addDisposableTo(disposeBag)
}
// Your Cell Class
var disposeBag = DisposeBag()
var delegate: UIViewController?
func configureForBook(book: Book) {
self.moreButton.rx.tap.subscribe { [unowned self] in
let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) {(action) in
self?.dismiss(animated: true, completion: nil)
}
alertController.addAction(cancelAction)
let destroyAction = UIAlertAction(title: "Delete", style: .destructive) { (action) in
}
alertController.addAction(destroyAction)
self.delegate?.present(alertController, animated: true)
}
.addDisposableTo(self.disposeBag)
}
override func prepareForReuse() {
disposeBag = DisposeBag()
}
How can I show a UIActionsheet in iPad when I'm using my current code its giving me this error:
Your application has presented a UIAlertController (<UIAlertController: 0x7f9ec624af70>) of style UIAlertControllerStyleActionSheet. The modalPresentationStyle of a UIAlertController with this style is UIModalPresentationPopover. You must provide location information for this popover through the alert controller's popoverPresentationController. You must provide either a sourceView and sourceRect or a barButtonItem. If this information is not known when you present the alert controller, you may provide it in the UIPopoverPresentationControllerDelegate method -prepareForPopoverPresentation.
which is working totally fine in an iPhone :
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet)
let reminderAction = UIAlertAction(title: "Reminder", style: .Default, handler: {
(alert: UIAlertAction!) -> Void in }
optionMenu.addAction(reminderAction)
self.presentViewController(optionMenu, animated: true, completion: nil)
I came across some similar problems, the solution was this:
let optionMenu = UIAlertController(title: nil, message: nil, preferredStyle: .ActionSheet)
optionMenu.popoverPresentationController?.sourceView = self.view
optionMenu.popoverPresentationController?.sourceRect = self.view.bounds
but it didnt worked for me maybe because my ActionSheet's Sender is on a UItableviewCell.
I tired to set AlertController's Sourceview to tableView's Cell but its not correctly placed and sometime its partially visible this is what I tried:
optionMenu.popoverPresentationController?.sourceView = currentCell.contentView
optionMenu.popoverPresentationController?.sourceRect = currentCell.contentView.bounds
Any clue how can I fix this problem?
The sample code given below works both on iPhone and iPad.
guard let viewRect = sender as? UIView else {
return
}
let cameraSettingsAlert = UIAlertController(title: NSLocalizedString("Please choose a course", comment: ""), message: NSLocalizedString("", comment: ""), preferredStyle: .ActionSheet)
cameraSettingsAlert.modalPresentationStyle = .Popover
let photoResolutionAction = UIAlertAction(title: NSLocalizedString("Photo Resolution", comment: ""), style: .Default) { action in
}
let cameraOrientationAction = UIAlertAction(title: NSLocalizedString("Camera Orientation", comment: ""), style: .Default) { action in
}
let flashModeAction = UIAlertAction(title: NSLocalizedString("Flash Mode", comment: ""), style: .Default) { action in
}
let timeStampOnPhotoAction = UIAlertAction(title: NSLocalizedString("Time Stamp on Photo", comment: ""), style: .Default) { action in
}
let cancel = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .Cancel) { action in
}
cameraSettingsAlert.addAction(cancel)
cameraSettingsAlert.addAction(cameraOrientationAction)
cameraSettingsAlert.addAction(flashModeAction)
cameraSettingsAlert.addAction(timeStampOnPhotoAction)
cameraSettingsAlert.addAction(photoResolutionAction)
if let presenter = cameraSettingsAlert.popoverPresentationController {
presenter.sourceView = viewRect;
presenter.sourceRect = viewRect.bounds;
}
presentViewController(cameraSettingsAlert, animated: true, completion: nil)
If you want to show any ActionSheet on iPad so their use popoverPresentationController to show and at iPad don't show the cancel style button of action sheet.
Use this code in Swift 3:
#IBAction func ActionSheetShow(_ sender: UIButton) {
let actionSheet = UIAlertController(title: "Choose any option", message: "choose as you like here!", preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: "Click1", style: .cancel, handler: {
action in
print("first button")
}))
actionSheet.addAction(UIAlertAction(title: "Click2", style: .default, handler: {
action in
print("second button")
}))
actionSheet.addAction(UIAlertAction(title: "Click3", style: .destructive, handler: {
action in
print("third button")
}))
actionSheet.popoverPresentationController?.sourceView = self.view
actionSheet.popoverPresentationController?.sourceRect = sender.frame
present(actionSheet, animated: true, completion: nil)
}
Good Luck!
Swift 4.1 Solution:-
MAK Eextension FILE UIDEviceExtension.swift and with below code :-
import Foundation
import UIKit
public extension UIDevice {
public class var isPhone: Bool {
return UIDevice.current.userInterfaceIdiom == .phone
}
public class var isPad: Bool {
return UIDevice.current.userInterfaceIdiom == .pad
}
public class var isTV: Bool {
return UIDevice.current.userInterfaceIdiom == .tv
}
public class var isCarPlay: Bool {
return UIDevice.current.userInterfaceIdiom == .carPlay
}
}
Call your action Sheet on UIViewcontroller By this Separate common method :-
import Foundation
import UIKit
class Common: NSObject{
public class func showActionSheet(vc: UIViewController,sender:UIButton? = nil) {
let actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: "Camera", style: .default, handler: { (alert:UIAlertAction!) -> Void in
//self.camera()
}))
actionSheet.addAction(UIAlertAction(title: "Gallery", style: .default, handler: { (alert:UIAlertAction!) -> Void in
//self.photoLibrary()
}))
actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
//if iPhone
if UIDevice.isPhone {
vc.present(actionSheet, animated: true, completion: nil)
}
else {
//In iPad Change Rect to position Popover
if let btn = sender{
actionSheet.popoverPresentationController?.sourceRect = btn.frame
actionSheet.popoverPresentationController?.sourceView = vc.view
}
vc.present(actionSheet, animated: true, completion: nil)
}
}
}
Use it from your UIButton Click for iPhone/iPad both :-
#objc func btnPicImageTaped(btn:UIButton){
print("it will work for both iPhone /ipad")
Common.showActionSheet(vc: self,sender: btn)
}
Add Following two lines before presentViewController. (Swift 3.2)
optionMenu.popoverPresentationController?.sourceView = self.view
optionMenu.popoverPresentationController?.sourceRect = (sender as AnyObject).frame
present(optionMenu, animated: true, completion: nil)
Updated for swift 5
extension UIDevice {
class var isPhone: Bool {
return UIDevice.current.userInterfaceIdiom == .phone
}
class var isPad: Bool {
return UIDevice.current.userInterfaceIdiom == .pad
}
class var isTV: Bool {
return UIDevice.current.userInterfaceIdiom == .tv
}
class var isCarPlay: Bool {
return UIDevice.current.userInterfaceIdiom == .carPlay
}
}
You can take sourceView & sourceRect from sender:
#IBAction func btMenuPressed(_ sender: Any) {
let Sender = sender as? UIButton
let actSheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
actSheet.addAction(UIAlertAction(title: "settings", style: .default) { _ in self.doSettings() })
...
...
actSheet.popoverPresentationController?.sourceView = Sender!.superview!
actSheet.popoverPresentationController?.sourceRect = Sender!.frame
resent(actSheet, animated: true)
}
Swift 5+ solution Very smooth and easy just call this function and you will easy solve your problem
let IPAD = UIDevice.current.userInterfaceIdiom == .pad
//Mark:- Choose Action Sheet Methods
func actionSheet() {
var actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: UIAlertController.Style.actionSheet)
actionSheet.view.tintColor = UIColor.black
let button1 = UIAlertAction(title: "Button 1".localizableString(language: Defaults.selectedLanguageCode), style: .default, handler: {
(alert: UIAlertAction!) -> Void in
})
let button2 = UIAlertAction(title: "Button 2".localizableString(language: Defaults.selectedLanguageCode), style: .default, handler: {
(alert: UIAlertAction!) -> Void in
})
let cancelAction = UIAlertAction(title: "Cancel".localizableString(language: Defaults.selectedLanguageCode), style: .cancel, handler: {
(alert: UIAlertAction!) -> Void in
})
if IPAD {
//In iPad Change Rect to position Popover
actionSheet = UIAlertController(title: nil, message: nil, preferredStyle: UIAlertController.Style.alert)
}
actionSheet.addAction(button1)
actionSheet.addAction(button2)
actionSheet.addAction(cancelAction)
print("Action Sheet Open")
self.present(actionSheet, animated: true, completion: nil)
}
I have an AlertController with a text field and two button: CANCEL and SAVE. This is the code:
#IBAction func addTherapy(sender: AnyObject)
{
let addAlertView = UIAlertController(title: "New Prescription", message: "Insert a name for this prescription", preferredStyle: UIAlertControllerStyle.Alert)
addAlertView.addAction(UIAlertAction(title: "Cancel",
style: UIAlertActionStyle.Default,
handler: nil))
addAlertView.addAction(UIAlertAction(title: "Save",
style: UIAlertActionStyle.Default,
handler: nil))
addAlertView.addTextFieldWithConfigurationHandler({textField in textField.placeholder = "Title"})
self.presentViewController(addAlertView, animated: true, completion: nil)
}
What I want to do is implement a check on the textfield for disabling the SAVE button when the textfield is empty just like Pictures Application of iOS when you want create a NewAlbum. Please someone can explain me what to do?
There is a much simpler way without using notification center, in swift:
weak var actionToEnable : UIAlertAction?
func showAlert()
{
let titleStr = "title"
let messageStr = "message"
let alert = UIAlertController(title: titleStr, message: messageStr, preferredStyle: UIAlertControllerStyle.alert)
let placeholderStr = "placeholder"
alert.addTextField(configurationHandler: {(textField: UITextField) in
textField.placeholder = placeholderStr
textField.addTarget(self, action: #selector(self.textChanged(_:)), for: .editingChanged)
})
let cancel = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: { (_) -> Void in
})
let action = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: { (_) -> Void in
let textfield = alert.textFields!.first!
//Do what you want with the textfield!
})
alert.addAction(cancel)
alert.addAction(action)
self.actionToEnable = action
action.isEnabled = false
self.present(alert, animated: true, completion: nil)
}
func textChanged(_ sender:UITextField) {
self.actionToEnable?.isEnabled = (sender.text! == "Validation")
}
I would first create the alertcontroller with the save action initially disabled. Then when adding the textfield inculde a Notification to observe its change in the handler and in that selector just toggle the save actions enabled property.
Here is what I am saying:
//hold this reference in your class
weak var AddAlertSaveAction: UIAlertAction?
#IBAction func addTherapy(sender : AnyObject) {
//set up the alertcontroller
let title = NSLocalizedString("New Prescription", comment: "")
let message = NSLocalizedString("Insert a name for this prescription.", comment: "")
let cancelButtonTitle = NSLocalizedString("Cancel", comment: "")
let otherButtonTitle = NSLocalizedString("Save", comment: "")
let alertController = UIAlertController(title: title, message: message, preferredStyle: .Alert)
// Add the text field with handler
alertController.addTextFieldWithConfigurationHandler { textField in
//listen for changes
NSNotificationCenter.defaultCenter().addObserver(self, selector: "handleTextFieldTextDidChangeNotification:", name: UITextFieldTextDidChangeNotification, object: textField)
}
func removeTextFieldObserver() {
NSNotificationCenter.defaultCenter().removeObserver(self, name: UITextFieldTextDidChangeNotification, object: alertController.textFields[0])
}
// Create the actions.
let cancelAction = UIAlertAction(title: cancelButtonTitle, style: .Cancel) { action in
NSLog("Cancel Button Pressed")
removeTextFieldObserver()
}
let otherAction = UIAlertAction(title: otherButtonTitle, style: .Default) { action in
NSLog("Save Button Pressed")
removeTextFieldObserver()
}
// disable the 'save' button (otherAction) initially
otherAction.enabled = false
// save the other action to toggle the enabled/disabled state when the text changed.
AddAlertSaveAction = otherAction
// Add the actions.
alertController.addAction(cancelAction)
alertController.addAction(otherAction)
presentViewController(alertController, animated: true, completion: nil)
}
//handler
func handleTextFieldTextDidChangeNotification(notification: NSNotification) {
let textField = notification.object as UITextField
// Enforce a minimum length of >= 1 for secure text alerts.
AddAlertSaveAction!.enabled = textField.text.utf16count >= 1
}
I am doing this in another project - I got this pattern directly from apple examples. They have a very good example project outlining a few of these patterns in the UICatalog examples: https://developer.apple.com/library/content/samplecode/UICatalog/Introduction/Intro.html
Swift 3.0 Updated Solution given By #spoek
func showAlert()
{
let titleStr = "title"
let messageStr = "message"
let alert = UIAlertController(title: titleStr, message: messageStr, preferredStyle: UIAlertControllerStyle.alert)
let placeholderStr = "placeholder"
alert.addTextField(configurationHandler: {(textField: UITextField) in
textField.placeholder = placeholderStr
textField.addTarget(self, action: #selector(self.textChanged(_:)), for: .editingChanged)
})
let cancel = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.cancel, handler: { (_) -> Void in
})
let action = UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: { (_) -> Void in
let textfield = alert.textFields!.first!
//Do what you want with the textfield!
})
alert.addAction(cancel)
alert.addAction(action)
self.actionToEnable = action
action.isEnabled = false
self.present(alert, animated: true, completion: nil)
}
func textChanged(_ sender:UITextField) {
self.actionToEnable?.isEnabled = (sender.text! == "Validation")
}
I implemented a subclass of UIAlertController for conveniently adding text fields and associated enabling and disabling of buttons. The basic logic is similar to that Sourabh Sharma but everything is encapsulated in this subclass for tidiness. This should be helpful if your project involves a lot of such alert functionalities.
public class TextEnabledAlertController: UIAlertController {
private var textFieldActions = [UITextField: ((UITextField)->Void)]()
func addTextField(configurationHandler: ((UITextField) -> Void)? = nil, textChangeAction:((UITextField)->Void)?) {
super.addTextField(configurationHandler: { (textField) in
configurationHandler?(textField)
if let textChangeAction = textChangeAction {
self.textFieldActions[textField] = textChangeAction
textField.addTarget(self, action: #selector(self.textFieldChanged), for: .editingChanged)
}
})
}
#objc private func textFieldChanged(sender: UITextField) {
if let textChangeAction = textFieldActions[sender] {
textChangeAction(sender)
}
}
}
To use it, just provide a textChangeAction block when adding the text fields:
alert.addTextField(configurationHandler: { (textField) in
textField.placeholder = "Your name"
textField.autocapitalizationType = .words
}) { (textField) in
saveAction.isEnabled = (textField.text?.characters.count ?? 0) > 0
}
For the full example, see the git page.