How to create an alert with DatePicker in SwiftUI - ios

I want to display DatePicker in alert view or action sheet view , but I could not find any resources to do it.
I want the following view.
Thanks for the help

What you want is actually discouraged by Apple (according to this answer). This is probably why you can't find any examples yourself.
Here is a possible solution:
struct ContentView: View {
#State var selectedDate = Date()
static let formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.setLocalizedDateFormatFromTemplate("yyMMddhhmm")
return formatter
}()
var body: some View {
VStack {
Text("Selected date: \(selectedDate, formatter: Self.formatter)")
Button("Show action sheet") {
self.showDatePickerAlert()
}
}
}
func showDatePickerAlert() {
let alertVC = UIAlertController(title: "\n\n\n\n\n\n\n\n\n", message: nil, preferredStyle: .actionSheet)
let datePicker: UIDatePicker = UIDatePicker()
alertVC.view.addSubview(datePicker)
let okAction = UIAlertAction(title: "OK", style: .default) { _ in
self.selectedDate = datePicker.date
}
alertVC.addAction(okAction)
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel)
alertVC.addAction(cancelAction)
if let viewController = UIApplication.shared.windows.first?.rootViewController {
viewController.present(alertVC, animated: true, completion: nil)
}
}
}
This uses the "\n\n\n\n\n\n\n\n\n" hack from this answer.

Related

In Swift, how to get a UIAlertAction button to stay in the enclosure when pressed

I have a UIAlertController that has a series of buttons. When I press the button, I want a variable to be set with a certain value which will in turn populate one of the textfields and the CoreData upon hitting the save button within the alert.
I am close but not quite there - when I press the button it will set the variable to what I want, but it immediately closes the alert. What I want to do is use keyboard to enter the information in the 2 textfields, press the button for 'vaccination' for example to set the entryType = "vaccination" and then the user should hit save to save the record.
#IBAction func buttonAddEntry(_ sender: Any) {
var typeField = ""
let newEntry = Vaccination(context: context)
print ("Create New VAX ENTRY")
let alert = UIAlertController(title: "New Entry", message: "Enter Entry Details", preferredStyle: .alert)
alert.addTextField()
alert.addTextField()
let dateField = alert.textFields![0]
dateFieldMain = dateField
//let typeField = alert.textFields![1]
//let commentField = alert.textFields![2]
let datePicker = UIDatePicker()
datePicker.datePickerMode = UIDatePicker.Mode.date
datePicker.addTarget(self, action: #selector(VaxViewController.datePickerValueChanged(sender:)), for: UIControl.Event.valueChanged)
dateFieldMain!.inputView = datePicker
print ("textfield set to datepicker")
let bt1 = UIAlertAction(title: "Vaccination", style: UIAlertAction.Style.default) {
(action) in
print ("Vaccination")
typeField = "Vaccination"
}
alert.addAction(bt1)
let bt2 = UIAlertAction(title: "Wormer", style: UIAlertAction.Style.default){
(action) in
print ("Wormer")
typeField = "Wormer"
}
alert.addAction(bt2)
let bt3 = UIAlertAction(title: "Medication", style: UIAlertAction.Style.default){
(action) in
print ("Medication")
typeField = "Medication"
}
alert.addAction(bt3)
let cancelButton = UIAlertAction(title: "Cancel", style: UIAlertAction.Style.destructive) {
(action) in return }
alert.addAction(cancelButton)
let alertButton = UIAlertAction(title: "Save", style: .default) { (action) in
let dateField = alert.textFields![0]
//let typeField = alert.textFields![1]
let commentField = alert.textFields![2]
//convert date
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM, dd, yyyy"
let dateConverted = dateFormatter.date(from: dateField.text!)
//set the properties of the new entry
newEntry.vaxDate1 = dateConverted
newEntry.vaxType = typeField
newEntry.comment = commentField.text
newEntry.animal = self.animal
try! self.context.save()
self.fetchVaccinations()
//self.tableView.reloadData()
}
alert.addAction(alertButton)
self.present(alert, animated: true, completion: nil)
}

Display a value selected from a datePicker to a textField inside an alertController

Good Day,
My workout app allows the user to build their custom workouts using a bar button '+' that opens an alert controller. I have two text fields and a date picker as part of the alert controller. Currently, a selection from the datePicker does not display the selection in the textField. How can this be done?
Here's the code for the alert controller:
#IBAction func addButtonPressed(_ sender: UIBarButtonItem) {
let alertController = UIAlertController(title: "Add New Workout", message: "", preferredStyle: .alert)
let addAction = UIAlertAction(title: "Add", style: .default, handler: { _ in
let newWorkout = Workout()
newWorkout.name = self.nameTextField.text!
newWorkout.dateCreated = self.datePicker.date
newWorkout.time = self.timeTextField.text!
print("Date \(self.datePicker.date)")
self.saveWorkout(workout: newWorkout)
} )
let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action) in
return
}
alertController.addTextField { (alertTextField) in
alertTextField.placeholder = "Workout Name"
self.nameTextField = alertTextField
}
alertController.addTextField { (alertTextField) in
self.doDatePicker()
alertTextField.inputView = self.datePicker
alertTextField.placeholder = "DD MMM YYYY HH:MM:SS"
self.dateTextField = alertTextField
}
alertController.addTextField { (alertTextField) in
alertTextField.placeholder = "Average Time"
self.timeTextField = alertTextField
}
alertController.addAction(addAction)
alertController.addAction(cancelAction)
present(alertController, animated: true, completion: nil)
}
func doDatePicker(){
datePicker = UIDatePicker(frame:CGRect(x: 0, y: self.view.frame.size.height - 220, width:self.view.frame.size.width, height: 216))
datePicker.timeZone = .current
datePicker.backgroundColor = UIColor.white
datePicker.datePickerMode = .dateAndTime
}
I appreciate any guidance provided.

Show UIAlertController every time view controller launches

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 */
}
}

How to show Actionsheet in iPad

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)
}

Check on UIAlertController TextField for enabling the button

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.

Resources