I am working on a checklist app. If the user wants to add a new point on his checklist, he can push the "Add" button and an alert will appear with a UITextField to put his new task in.
alert.addAction(UIAlertAction(title: "Add", style: .default, handler: { (action) in
let textf = alert.textFields![0] as UITextField
if(textf.text != "")
{
checklist.append(textf.text!.capitalized)
}
self.myTableView.reloadData()
}))
self.present(alert, animated: true, completion: nil)
}
Doing it like this will capitalize the words after hitting "Add". But I want that only the first word to be capitalized. Also, I want it to be on the keyboard when it appears so that the first word is capitalized right away. How can I do that?
let alert = UIAlertController(title: "Add new task", message: nil, preferredStyle: .alert)
alert.addTextField { (alertInputTextField) in
}
Use the autocapitalizationType property of the textfield and set it to sentences when you add it...
alert.addTextField { textField in
textField.text = "Some default text"
textField.autocapitalizationType = .sentences
}
Always refer to the documentation, it explains this quite well.
If you need to do this to a String directly just uppercase the first letter of the String.
You can use any of the below function:-
public mutating func capitalizeFirst() {
guard self.count > 0 else { return }
self.replaceSubrange(startIndex...startIndex, with: String(self[startIndex]).capitalized)
}
or
public func capitalizedFirst() -> String {
guard self.count > 0 else { return self }
var result = self
result.replaceSubrange(startIndex...startIndex, with: String(self[startIndex]).capitalized)
return result
}
Related
I have an game app where players count cows on road trip. I have a button that comes up with an alert that allow players to enter however many cows they see. I had an error where if they pressed "add" without entering any cows, it would come up nil and crash. So I set the alert text field to equal "0." However, now if they add, say, 25 cows, the text field would read 025 (it still enters it as only 25 and doesn't cause problems with the math). I would like to clear the text field when players start adding numbers, but I'm not sure how to do that with a text field within an alert. I successfully did it in other areas of the app, so I'm familiar with how it works. Just not this specific instance (if it is in fact even different).
Here's the creation of the alert (I cut out all the math inside as I don't think that's relevant, but let me know if you think it's needed.):
#IBAction func playerOneSawCows(_ sender: UIButton) {
var textField = UITextField()
let alert = UIAlertController(title: "Cows Spotted!", message: "How many cows did you see?", preferredStyle: .alert)
let actionCancel = UIAlertAction(title: "False Alarm", style: .cancel)
let action = UIAlertAction(title: "Add Cows", style: .default) {(action) in
//MARK: - first cows
var totalCows = self.totalCowsOne
var additionalCows = 0
let newCows = textField.text!
let cowsNumber = Int(newCows)
var cowArrayOne: [String] = []
if self.numberOfCowsPlayerOne.text == "0 cows"{
...
}
}
alert.addTextField { (alertTextField) in
alertTextField.placeholder = ""
alertTextField.text = "0"
textField = alertTextField
textField.keyboardType = UIKeyboardType.numberPad
}
alert.addAction(action)
alert.addAction(actionCancel)
present (alert, animated: true, completion: nil)
}
And here's my previous instance of clearing a text field that works correctly:
func textFieldDidBeginEditing(_ textField: UITextField) {
if (textField == playerOneNameText) {
playerOneNameText.text = ""
}
else if (textField == playerTwoNameText) {
playerTwoNameText.text = ""
}else {
print("No players")
}
}
I tried adding another if statement there, but it didn't seem to trigger it. I feel like it's an easy solution, but I could be wrong. Any help would be great.
So my goal is to have the label text always revert back to the original price if the alert view controller is dismissed.
Here is a clip of the issue so you guys can understand it more, it would be too long to explain over text...
So as you can see, when the user cancels the alert prompt, I want everything to revert back as if they never pressed it in the first place. The stepper value reverts, the number label text
reverts, but the event cost does not and I can't figure out why.
Here is the #IBAction function I use with the stepper:
#IBAction func guestsCount(_ sender: UIStepper) {
guestNumberCount.text = String(Int(sender.value))
let totalCost = Decimal(sender.value) * cost
let formatted = totalCost.formattedAmount
var textfield = UITextField()
var textfield2 = UITextField()
actualCostOfEvent.text = "$\(formatted ?? "")"
if(Int(sender.value) > sampleStepperValueForIncrement){
print("increasing")
sampleStepperValueForIncrement += 1
let alert = UIAlertController(title: "Add A Guest", message: "Type full name the of guest you want to add below", preferredStyle: .alert)
let addGuest = UIAlertAction(title: "Add", style: .default) { (add) in
guard let user = Auth.auth().currentUser else { return }
db.document("student_users/\(user.uid)/guestTickets/guestDetails").setData(["guests": FieldValue.arrayUnion([textfield.text ?? "Nil"])], merge: true) { (error) in
if let error = error {
print("There was an error adding the name: \(error)")
} else {
print("name added successfully")
}
}
}
let dismiss = UIAlertAction(title: "Dismiss", style: .cancel) { (cancel) in
self.dismiss(animated: true, completion: nil)
self.guestNumberCount.text = String(Int(sender.value) - 1)
self.stepperValue.value -= 1
}
alert.addTextField { (alerttextfield) in
textfield = alerttextfield
alerttextfield.placeholder = "Guest Name"
alerttextfield.clearButtonMode = .whileEditing
}
alert.addAction(dismiss)
alert.addAction(addGuest)
present(alert, animated: true, completion: nil)
}
else {
print("decreasing")
sampleStepperValueForIncrement = sampleStepperValueForIncrement - 1
let alert = UIAlertController(title: "Remove A Guest", message: "Type the full name of the guest you want to remove. The guest name is case-sensitive and must equal the name you added.", preferredStyle: .alert)
let dismiss = UIAlertAction(title: "Dismiss", style: .cancel) { (cancel) in
self.dismiss(animated: true, completion: nil)
self.guestNumberCount.text = String(Int(sender.value) + 1)
self.stepperValue.value += 1
}
let removeGuest = UIAlertAction(title: "Remove", style: .destructive) { (remove) in
guard let user = Auth.auth().currentUser else { return }
db.document("student_users/\(user.uid)/guestTickets/guestDetails").updateData(["guests": FieldValue.arrayRemove([textfield2.text ?? "Nil"])]) { (error) in
if let error = error {
print("There was an error removing the name: \(error)")
} else {
print("name removed successfully")
}
}
}
alert.addTextField { (alerttextfield2) in
textfield2 = alerttextfield2
alerttextfield2.placeholder = "Guest Name"
alerttextfield2.clearButtonMode = .whileEditing
}
alert.addAction(dismiss)
alert.addAction(removeGuest)
present(alert, animated: true, completion: nil)
}
}
The problems both stem from the dismiss actions in both the if statement and the else statement. I tried possibly adding Decimal(sender.value) - 1 in one dismiss action and then Decimal(sender.value) + 1 in the other, but those didn't make a difference. The exact thing happens when you already have a guest added and you decide to remove, but end up dismissing the alert vc, the price also doesn't revert as well. The odd thing is that if I add all the way to the max value, which is 6 and then remove guests all the way back down to 1 again, the price will revert back to the original price.
Any help would be appreciated, thanks.
You only set actualCostOfEvent.text before the alert controller is displayed.
In the event that it is cancelled, you'll need to set it again, with the recalculated value once you've decremented the stepper again.
I'd suggest moving that code into a helper function so that you aren't repeating it:
func setLabelToFormattedTotalCost(multiplier: Int) {
let totalCost = Decimal(multiplier) * cost
let formatted = totalCost.formattedAmount
actualCostOfEvent.text = "$\(formatted ?? "")"
}
Then, call that with the stepper value (that would be the multiplier parameter) at the beginning of guestsCount and then again once you've decremented the value if the alert is dismissed.
Its because in dismiss you reset stepper value and guest number count but dont update the cost value, so in both dismiss action you can have
let dismiss = UIAlertAction(title: "Dismiss", style: .cancel) { (cancel) in
//all other code of yours and finally
let totalCost = Decimal(self.stepperValue.value) * cost
let formatted = totalCost.formattedAmount
actualCostOfEvent.text = "$\(formatted ?? "")"
}
You can obviously move this to a function and call the function from both dismiss action as
func updateCostValue() {
let totalCost = Decimal(self.stepperValue.value) * cost
let formatted = totalCost.formattedAmount
actualCostOfEvent.text = "$\(formatted ?? "")"
}
let dismiss = UIAlertAction(title: "Dismiss", style: .cancel) { (cancel) in
self.dismiss(animated: true, completion: nil)
self.guestNumberCount.text = String(Int(sender.value) - 1)
self.stepperValue.value -= 1
self.updateCostValue()
}
let dismiss = UIAlertAction(title: "Dismiss", style: .cancel) { (cancel) in
self.dismiss(animated: true, completion: nil)
self.guestNumberCount.text = String(Int(sender.value) + 1)
self.stepperValue.value += 1
self.updateCostValue()
}
I have a screen with a collection view, and a plus sign bar button item. When the plus sign is pressed, an alert window pops up, where the user can add information to the list. Upon hitting OK, I am trying to refresh the collection view, but I'm doing something wrong.
The print statement "passed guard" is achieved, and I can get the information they entered. Just can't refresh the view to reflect this without leaving and coming back. Any guidance? I've run into this a few times actually, so I'm clearly missing something. Thanks very much in advance.
#objc func newButtonPressed() {
let alert = UIAlertController(title: "Add", message: "", preferredStyle: .alert)
alert.addTextField { (textField) in
textField.placeholder = "Name"
}
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: { (action) in
var name = ""
guard let textFields = alert.textFields else { return }
guard let navController = self.parent as? UINavigationController else { return }
guard let settingsVC = navController.topViewController as? SettingsVC else { return }
print("passed guard") // success
DispatchQueue.main.async {
settingsVC.collectionView.reloadData()
settingsVC.view.backgroundColor = .red
// For testing purposes, explicitly using main thread and setting to red
}
}))
alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
present(alert, animated: true, completion: nil)
}
May be you need to alter the collection dataSource
guard let textFields = alert.textFields else { return }
settingsVC.arr.append(textFields.first!.text!) // arr is collection dataSource
settingsVC.collectionView.reloadData()
I am trying to do validation on the entry in a text field in textFieldShouldEndEditing, I check to see if the value is non numeric or out of bounds, then call a function that displays an alert. After the alert is displayed, I set the value to a default and then call other functions to perform calculations.
Regardless of what action I choose to dismiss the alert, editing of the text field is triggered. This is what I want for the first option ("Try Again"), but for the option "Set to Default" I just want to alert to go away and not begin editing of the text field, as the default value was already assigned. I do not understand how alerts interact with first responder status or why the text field is given first responder status again. Relevant code:
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
var InvalidFlagText: String = ""
let WindIntBool = isStringAnInt(string: textField.text!)
if WindIntBool { //if entered wt is numeric, check to see if out of bounds of data loaded in table
if WindInt < LowestWind || WindInt > HighestWind { //value is out of bounds of data, set to default
txtWind.text = "0"
// display alert
InvalidFlagText = "OutofBounds"
DisplayAlert(InvalidFlag: InvalidFlagText)
}
} else { // if not numeric, set to default value
txtWind.text = "0"
// display alert
InvalidFlagText = "Nonnumeric"
DisplayAlert(InvalidFlag: InvalidFlagText)
}
CalculateResults()
return true
}
func DisplayAlert (InvalidFlag: String) {
var messageText: String = ""
if InvalidFlag == "Nonnumeric" {
messageText = "Please enter a numeric value."
} else if InvalidFlag == "OutofBounds" {
messageText = "Entered value is outside of the valid numeric range. Please enter a valid numeric value"
}
let alert = UIAlertController(title: "That is an invalid entry.", message: "\(messageText)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Try Again", style: .cancel, handler: nil))
alert.addAction(UIAlertAction(title: "Set to Default", style: .default, handler: { action in
}))
self.present(alert, animated: true)
}
try resign all responders on your alert
alert.addAction(UIAlertAction(title: "Set to Default", style: .default, handler: { action in
//// set your text value before ////
self.view.endEditing(true)
}))
Right or better way:
func DisplayAlert (InvalidFlag: String) {
self.view.endEditing(true)
var messageText: String = ""
if InvalidFlag == "Nonnumeric" {
messageText = "Please enter a numeric value."
} else if InvalidFlag == "OutofBounds" {
messageText = "Entered value is outside of the valid numeric range. Please enter a valid numeric value"
}
let alert = UIAlertController(title: "That is an invalid entry.", message: "\(messageText)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Try Again", style: .cancel, handler: { action in
self.txtWind.becomeFirstResponder()
}))
alert.addAction(UIAlertAction(title: "Set to Default", style: .default, handler: { action in
/// set default value ////
}))
self.present(alert, animated: true)
}
Not sure what CalculateResults() method does and I assumed the isStringAnInt method as well. Below is the code for the functionality you are expecting.
import UIKit
let kDEFAULT_WIND = "0"
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var txtWind: UITextField!
let HighestWind = 200
let LowestWind = 100
var WindInt = -1
override func viewDidLoad() {
super.viewDidLoad()
txtWind.delegate = self
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
self.view.endEditing(true)
return true
}
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool {
if textField.text == kDEFAULT_WIND{
return true
}
var InvalidFlagText: String = ""
let WindIntBool = isStringAnInt(s: textField.text!)
if WindIntBool { //if entered wt is numeric, check to see if out of bounds of data loaded in table
if WindInt < LowestWind || WindInt > HighestWind { //value is out of bounds of data, set to default
InvalidFlagText = "OutofBounds"
DisplayAlert(InvalidFlag: InvalidFlagText)
}
} else { // if not numeric, set to default value
InvalidFlagText = "Nonnumeric"
DisplayAlert(InvalidFlag: InvalidFlagText)
}
// CalculateResults()
return true
}
func isStringAnInt(s : String) -> Bool{
if let val = Int(s){
WindInt = val
return true
}
return false
}
func DisplayAlert (InvalidFlag: String) {
var messageText: String = ""
if InvalidFlag == "Nonnumeric" {
messageText = "Please enter a numeric value."
} else if InvalidFlag == "OutofBounds" {
messageText = "Entered value is outside of the valid numeric range. Please enter a valid numeric value"
}
let alert = UIAlertController(title: "That is an invalid entry.", message: "\(messageText)", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Try Again", style: .cancel, handler: { action in
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now(), execute: {
self.txtWind.text = kDEFAULT_WIND
self.txtWind.becomeFirstResponder()
})
}))
alert.addAction(UIAlertAction(title: "Set to Default", style: .default, handler: { action in
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now(), execute: {
self.txtWind.text = kDEFAULT_WIND
self.txtWind.resignFirstResponder()
})
}))
self.present(alert, animated: true)
}
}
I am building a todo-list app and I am having a lot of trouble returning the text input from an Alert.
This is in a separate file ex: 'AddItem.swift'
func showAddItemDialog(view: UIViewController) -> String {
var textOfTask = UITextField()
var textValue = ""
let diag = UIAlertController(title: "Add Task", message: "Enter a task name", preferredStyle: .Alert)
diag.addTextFieldWithConfigurationHandler({ (textField) -> Void in })
diag.addAction(UIAlertAction(title: "Add", style: .Default, handler: { (action) -> Void in
textOfTask = diag.textFields![0] as UITextField
textValue = textOfTask.text!
addListItem(textValue)
}))
diag.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: { (action: UIAlertAction!) in }))
view.presentViewController(diag, animated: true, completion: nil)
print("returning " + textValue)
return textValue
}
And I am trying to have the text value ('textValue') of the Alert's text box be returned to the caller.
I have tried a bunch of ways but have not come up with anything and what I have above returns nothing because the function does not stop and wait for the alert to show before returning. I want to avoid putting this code into the ViewController file as I have read that it's bad practice, but I really can't figure this out.
If anyone has any ideas, please let me know! Thanks!
Edit:
I am calling this function with:
#IBAction func didPressAdd(sender: AnyObject) {
showAddItemDialog(self)
}
You are correct that the code won't wait for the person to enter data and click the OK button. The answer is to use a completion handler.
// This is a slightly modified version of your code
func showAddItemDialog(view: UIViewController, completion: (text: String?) -> Void ) {
var textOfTask = UITextField()
var textValue = ""
let diag = UIAlertController(title: "Add Task", message: "Enter a task name", preferredStyle: .Alert)
diag.addTextFieldWithConfigurationHandler({ (textField) -> Void in })
diag.addAction(UIAlertAction(title: "Add", style: .Default, handler: { (action) -> Void in
textOfTask = diag.textFields![0] as UITextField
textValue = textOfTask.text!
addListItem(textValue)
completion(text: textValue)
}))
diag.addAction(UIAlertAction(title: "Cancel", style: .Cancel, handler: { (action: UIAlertAction!) in
completion(text: nil)
}))
view.presentViewController(diag, animated: true, completion: nil)
}
// In the view controller
#IBAction func didPressAdd(sender: AnyObject) {
// Call it like this:
showAddItemDialog(self) {
(text) in
// handle the result value here
if let textUserEntered = text {
// User entered some text and pressed OK
}
else {
// User pressed cancel
}
}
}
As #nhgrif says in his comment, you can't return a value as the result of your function. The function returns as soon as it hands the alert to the system for display, and before the alert is even drawn to the screen.
This is a very common beginner mistake when dealing with async methods.
You need to refactor your showAddItemDialog function to take a completion closure with a string parameter. In the closure for your add action, fetch the text from the field of the alert and then invoke the closure that's passed to you, giving it the string.
Then when you call your showAddItemDialog function, pass it a closure that does whatever you need to do with the text you collect from the user.