I pass Int in to TextField together with NumberFormatter
private var numberFormatter: NumberFormatter {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.locale = Locale(identifier: "pl")
return numberFormatter
}
TextField(placeholder, value: self.$amount, formatter: self.numberFormatter)
And I expect, that TextField will include group separators, and it is, but only in initial state, later, when I change value: add and remove new digits, it works as with simple string just ignores places where separators should be.
If I use proxy
var amountProxy: Binding<String> {
Binding<String>(
get: { self.numberFormatter.string(from: NSNumber(value: self.amount)) ?? "" },
set: {
let cleanValue = $0.replacingOccurrences(of: self.numberFormatter.groupingSeparator, with: "")
if let value = self.numberFormatter.number(from: cleanValue) {
self.amount = value.intValue
}
}
)
}
TextField(placeholder, text: amountProxy)
It formats it right, but when adds a new separator, displays cursor from last position to lastPosition + numberOfSeparators
Did you try using onEditingChanged completion for TextField? I would suggest that when User starts editing, you can remove the formatting and let user edit the phone number. When user finishes the editing, you can show the formatted number again.
Something like this:
TextField(placeholder, text: $amount, onEditingChanged: { (editing) in
if editing {
amountBinding = amount
} else {
amountBinding = amount.formatted()
}
})
I haven't tested the code but it is something you can modify to fit your needs. Also, you can probably have a separate binding and model variable
Related
What is the approach to shuffle an array of strings one time a day?
And not every time the app is relaunches.
struct View: View {
#ObservedObject var quotes = Quotes()
var body: some View {
List {
ForEach(quotes.shuffled()) { quote in
Text(quote.quotes)
}
}
}
}
When I try the shuffled() method every time the view is updated the quotes are shuffled again, also when relaunching the app, I want to shuffle the array only one time a day.
You need to store current date in memory like user defaults and check every time for new date like I have in code below. isNewDay() function checks if date is new and saves current date in user defaults. Condition isNewDay() ? quotes.shuffled() : quotes shuffles quotes only if date is new
struct View :View{
#ObservedObject var quotes = Quotes()
var body :some View{
List{
ForEach(isNewDay() ? quotes.shuffled() : quotes){ quote in
Text(quote.quotes)
}
}
}
func isNewDay()-> Bool{
let currentDate = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yyyy"
let currentDateString = dateFormatter.string(from: currentDate)
if let lastSaved = UserDefaults.standard.string(forKey: "lastDate"){// last saved date
if lastSaved == currentDateString{
return true
}else{
UserDefaults.standard.setValue(currentDateString, forKey: "lastDate")
return false
}
}else{
UserDefaults.standard.setValue(currentDateString, forKey: "lastDate")
return false
}
}
}
This question already has answers here:
How to check if a text field is empty or not in swift
(16 answers)
Checking if textfields are empty Swift
(6 answers)
Closed 4 years ago.
I'm currently working on a project where I use lots of UITextFields. For validation I need to check if the UITextFields are empty. I got a working solution, but it's not that elegant. Maybe someone knows a better way of doing it.
Here is my solution:
// Check if text field is empty
if let text = textField.text, !text.isEmpty {
// Text field is not empty
} else {
// Text field is empty
}
Is there a faster way without unwrapping the text attribute of the text field to find out if it's empty?
Thanks!
How about extending UITextField…
extension UITextField {
var isEmpty: Bool {
if let text = textField.text, !text.isEmpty {
return false
}
return true
}
}
so then…
if myTextField.isEmpty {
}
You can use UIKeyInput property hasText. It works for both UITextField and UITextView:
if textField.hasText {
// Text field is not empty
} else {
// Text field is empty
}
If you would like to check if the text has not only spaces on it:
extension UITextField {
var isEmpty: Bool {
return text?.trimmingCharacters(in: .whitespacesAndNewlines) == ""
}
}
let tf = UITextField()
tf.text = " \n \n "
tf.isEmpty // true
If you have several textfields that you want to check, you could put them all in a guard statement
guard let text1 = textField1.text, let text2 = textField2.text, let text3 = textField3.text, !text1.isEmpty, !text2.isEmpty, !text3.isEmpty else {
//error handling
return
}
//Do stuff
I like to validate each text field depending on the content that should be provided by the user, i.e. emailTextField should contain a valid email address etc. While Ashley Mills answer is convenient, if you regard whitespace " " as text this will return false.
In your case, since you need to validate multiple text fields in the same way, why not extend UITextField as Ashley did with a static class method that can validate each text field passed as an array, in addition to this have other validation methods for each type of text field. Instead of returning a Boolean value I've learned to use guard instead. In this way guard let can be used to check if the validation fails (is nil) and execute the proper code, such as displaying a prompt to the user, or otherwise continue execution.
UITextFieldExtension.swift
import Foundation
import UIKit
extension UITextField {
/// Validates all text field are non-nil and non-empty, Returns true if all fields pass.
/// - Returns: Bool
static func validateAll(textFields:[UITextField]) -> Bool {
// Check each field for nil and not empty.
for field in textFields {
// Remove space and new lines while unwrapping.
guard let fieldText = field.text?.trimmingCharacters(in: .whitespacesAndNewlines) else {
return false
}
// Are there no other charaters?
if (fieldText.isEmpty) {
return false
}
}
// All fields passed.
return true
}
//A function that validates the email address...
func validateEmail(field: UITextField) -> String? {
guard let trimmedText = field.text?.trimmingCharacters(in: .whitespacesAndNewlines) else {
return nil
}
//email addresses are automatically detected as links in i0S...
guard let dataDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue) else {
return nil
}
let range = NSMakeRange(0, NSString(string: trimmedText).length)
let allMatches = dataDetector.matches(in: trimmedText,
options: [],
range: range)
if allMatches.count == 1,
allMatches.first?.url?.absoluteString.contains("mailto:") == true
{
return trimmedText
}
return nil
}
func validateUserName(field: UITextField) -> String? {
guard let text:String = field.text else {
return nil
}
/* 3 to 12 characters, no numbers or special characters */
let RegEx = "^[^\\d!##£$%^&*<>()/\\\\~\\[\\]\\{\\}\\?\\_\\.\\`\\'\\,\\:\\;|\"+=-]+$"
let Test = NSPredicate(format:"SELF MATCHES %#", RegEx)
let isValid = Test.evaluate(with: text)
if (isValid) {
return text
}
return nil
}
/*6 to 16 Characters */
func validatePassword(field: UITextField) -> String?{
guard let text:String = field.text else {
return nil
}
/*6-16 charaters, and at least one number*/
let RegEx = "^(?=.*\\d)(.+){6,16}$"
let Test = NSPredicate(format:"SELF MATCHES%#", RegEx)
let isValid = Test.evaluate(with: text)
if (isValid) {
return text
}
return nil
}
}
Meanwhile, elsewhere...
if (UITextField.validateAll(textFields: [emailTextField, nameTextField])) {
// Do something
}
In an iOS Project, i have to implement a view controller that allows to pick a float value using a slider and a text field at the same time. it should have the following behaviour:
whenever a number is typed in the text field, the slider should update it's value to the typed float number
whenever the slider is dragged it should update the text field with the slider value
Current ideas is as follows:
I have two propertys in the view model, the actual value and the value text.
Whenever the value is updated i update the text within the view model.
Both the text field and the slider change only the value.
But then i have a kind of 'cyclic' dependency: text field updates the view models value, which updates the view models text, which is bound to the textfield where we just type the value. This leads to buggy behaviour of the text field. I tried to work around with the isFirstResponder property, that works better, but does not seem best practise, plus it's not as intended.
Is there a way bind the enabledProperty of the slider to isFirstResponder of the text field? That would also work
Bindings in VC:
layout.valueTextField.reactive.text <~ viewModel.valueText.map( { text in
if !self.layout.valueTextField.isFirstResponder {
return text
}
return self.layout.valueTextField.text ?? ""
}
)
viewModel.value <~ layout.valueSlider.reactive.values
layout.valueSlider.reactive.value <~ viewModel.value
viewModel.value <~ layout.valueTextField.reactive.continuousTextValues.map({ textValue in
if let t = textValue {
if let parsed = Float(t) {
return parsed
}
}
return viewModel.value.value
})
My view Model:
import ReactiveSwift
class ValuePickerViewModel {
var valueText: MutableProperty<String>
var value: MutableProperty<Float>
let minValue: Float
let maxValue: Float
let unit: String
init(initialValue: Float, formatter: FloatFormatter, minValue: Float, maxValue: Float, unit: String) {
self.value = MutableProperty(initialValue)
self.valueText = MutableProperty(formatter.format(value: initialValue))
self.valueText <~ self.value.map({value in
formatter.format(value: value)
}
)
self.minValue = minValue
self.maxValue = maxValue
self.unit = unit
}
}
The solution is simple:
The textFields value binds to the view models float value.
The slides value binds to the view models text value.
So each component updates the other, and there is no cycle!
Working code:
Bindings:
viewModel.value <~ layout.valueTextField.reactive.continuousTextValues.map({ textValue in
if let t = textValue {
if let parsed = Float(t) {
return parsed
}
}
return viewModel.value.value
})
viewModel.valueText <~ layout.valueSlider.reactive.values.map({ value in
return viewModel.formatter.format(value: value)
})
layout.valueSlider.reactive.value <~ viewModel.value
layout.valueTextField.reactive.text <~ viewModel.valueText
ViewModel:
import ReactiveSwift
class ValuePickerViewModel {
var valueText: MutableProperty<String>
var value: MutableProperty<Float>
let minValue: Float
let maxValue: Float
let unit: String
let formatter: FloatFormatter
init(initialValue: Float, formatter: FloatFormatter, minValue: Float, maxValue: Float, unit: String) {
self.formatter = formatter
self.value = MutableProperty(initialValue)
self.valueText = MutableProperty(formatter.format(value: initialValue))
self.minValue = minValue
self.maxValue = maxValue
self.unit = unit
}
}
in my View Controller I have a uitextfield where user can input decimals for some expense by pressing add button.
The issue: if I input Int like "100" everything works properly, but if I input "12,3" data is not accepted even if I'm converting the text in a Double
note : if I input a decimal, "print 1" values and stop, just like no data was set
note2: in my textfield I set to no correction and spellchecking due to xcodebug about textfield keyboard, but this change does not seem to influence the present issue
I declare:
var amount : Double!
my button:
#IBAction func addBillButton(_ sender: UIButton) {
print("button pressed 1")
guard self.dataInput.text != nil else {return}
amount = Double(self.dataInput.text!)
guard amount != nil else {return}
print("amount : \(amount!)")
dateShowed = myDatePicker.date as NSDate!
let dateShowedString = convertDateToString(dateToConvert: dateShowed as NSDate)
print("date : \(dateShowedString)")
if presentPickName != nil {
print("name is : \(presentPickName!)")
} else {
print("presentPickName is nil")
}
// guard presentPickData != nil else {return}
if presentPickTag != nil {
print("tag is : \(presentPickTag!)")
} else {
print("presentPickData is nil")
}
//func for saving in core data
// saveExpense(amountToSave: amount, amountDate: dateShowed as NSDate, owner: presentPickName, tag: presentPickTag)
resetViewForNextInput()
self.dataInput.resignFirstResponder()
print("button pressed 2")
}
my textfield in storyboard
EDIT SOLUTION:
in my button
guard let stringFromTxtField = self.dataInput.text else {return}
print(stringFromTxtField)
let myFormatter = NumberFormatter().number(from: stringFromTxtField) as! Double
print(myFormatter)
amount = myFormatter
It depends on locale, passing directly from text to number and viceversa should always be done by using a NumberFormatter. The number formatter knows the locale set in the settings and convert correctly your number.
For instance in italy decimal places are displayed with a ,,italian users find natural to input number like these 1,5 or 54,2 swift Double prints decimal like this 1.5 54.2 because the programming language requires numbers with a US format.
Thus a Double is not able to parse the string because it expect a number in this format.
Using a NumberFormatteryou can pass from the text to a correct number number because it know from the locale that the decimal separator in italy is ,.
Of course number formatters has a plenty of options, forcing the locale, forcing the decimal separator etc.
let double = NumberFormatter().number(from: "1,3") as! Double
This line of code with a locale set to Italy will get correctly a number of 1.3
textField.text.isEmpty
textField.text != ""
These two functions regard spaces as characters. However, I would like my to recognise when the text field has no spaces or is not empty.
Just check if the trimmed string is empty
let isEmpty = str.stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet()).isEmpty
You can even put it in an String extension:
extension String {
var isReallyEmpty: Bool {
return self.stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet()).isEmpty
}
}
It even works for a string like var str = " " (that has spaces, tabs and zero-width spaces).
Then simply check textField.text?.isReallyEmpty ?? true.
If you wanna go even further (I wouldn't) add it to an UITextField extension:
extension UITextField {
var isReallyEmpty: Bool {
return text?.stringByTrimmingCharactersInSet(.whitespaceAndNewlineCharacterSet()).isEmpty ?? true
}
}
Your code becomes textField.isReallyEmpty.
In Swift 4,
extension String {
var isReallyEmpty: Bool {
return self.trimmingCharacters(in: .whitespaces).isEmpty
}
}
I used this from #Pieter21 's link.
It works for as many blank spaces a user could enter.
let myString = TextField.text
let trimmedString = myString!.stringByTrimmingCharactersInSet(
NSCharacterSet.whitespaceAndNewlineCharacterSet()
)
if trimmedString != "" {
// Code if not empty
} else {
// Code if empty
}