Skip to content

Real-time NumberFormatter Validation of TextField Input

Notifications You must be signed in to change notification settings

crenelle/swiftui-numberfield

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

15 Commits
 
 
 
 

Repository files navigation

NumberField: Real-time NumberFormatter Validation of TextField Input

One of the most frustrating parts of building SwiftUI-based apps at the moment is dealing with input validation of numeric values. As of this writing, the native support for numberFormatters in TextFields appears to be broken.

Various folks[1] have suggested that the way around this is to create a Binding<String> that manages conversion between string and numeric values. This is indeed possible -- and is part of what this code does. But there is currently an additional problem with TextField, and IIRC, it's also a maddening problem with UITextField in UIKit: Input validation (and the associated conversion of string to numeric values) happens either too often or not often enough.

In a naive version of the use-a-binding approach, string to numeric and back to string conversion occurs with each keystroke, which is a nightmare if you are using a currency format. The string "1" gets converted to 1.0 which gets converted to "$1.00" with the insertion point not between the dollar sign and the numeral one. Comma and decimal point handling is similarly frustrating for the user.

If you try to deal with this problem by implementing string validation and conversion only when focus is lost, you are now in a position where your numeric state object does not reflect the most recent value entered by the user. This makes it impossible to type "12.34" and then tap a button that then consumes the converted decimal. More subtley, you will not be able to guard the button's enabled state using something like Button("Add"){ ... }.disabled(myNumber==nil).

One way of partially mitigating some of these issues is using onCommit: but its behavaior if not broken is at least awkward. Most importantly, your onCommit: closure (only) fires when the user taps return on a text field, which means 1) more stale value problems and 2) using the decimal pad keyboard means your onCommit: handler will never be fired.

I finally figured out an approach that works well for my needs, and it is based on the insight that things get much simpler if an additional numeric value is introduced. One Decimal holds the most recent numeric value as of the last loss of focus, which reflects the formatted appearance of the field when the user is not interacting with it. The other Decimal holds the most recent converted value as of the last keystroke. The insight is that you want to convert on a per-keystroke basis but you do not want to use that conversion to update the text field string contents until after the user is no longer typing in the field.

Please enjoy. Pull requests et c. welcome.

[1]: See this Stack Overflow post and this Twitter thread.

About

Real-time NumberFormatter Validation of TextField Input

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Swift 100.0%