7

I am trying to get a text field that only accepts numbers but will also accept an empty value (ie. it is an optional field). This is what the textfield looks like:

TextField("Phone Number", value: $phoneNumber, formatter: numberFormatter)
    .disableAutocorrection(true)
    .autocapitalization(.none)

and my number formatter looks like this:

@State private var numberFormatter: NumberFormatter = {
    var nf = NumberFormatter()
    nf.numberStyle = .none
    nf.zeroSymbol = ""    
    return nf
}()

I have tried two different solutions yet both have problems. The first solution is to define phoneNumber as an optional integer Int? so that the TextField will accept a blank input:

@State var phoneNumber: Int? = nil

However, this messes with the TextField so that when I change its value via the app the changes don't update the actual variable phoneNumber. So, when I go to send my data in, phoneNumber still stays at nil. I think this has something to do with the numberFormatter and how it wont't accept nil variables.

So, my second solution is to initialize phoneNumber as an Int like so:

@State var phoneNumber: Int = 0

This solution does update phoneNumber when I change the text in the TextField. However, it will only show a blank space in the TextField box when I type in 0 (because of my .zeroSymbol definition in the numberFormatter). When I try to just put a blank space (ie. delete all the text) and then click out of the TextField, it just reverts back to the number that it was before. This same thing happens when I put a non-numeric entry into the field, which I am okay with because it should only accept numbers, but I want to still allow the user to include a blank entry.

I am using XCode and creating an iOS app. Thank you for any help.

1
  • phone numbers are incredibly complex, there is PhoneNumberKit to help with that and here is a SwiftUI wrapper gist.github.com/jbnunn/… Commented May 12, 2022 at 20:11

3 Answers 3

2

if you want to accept empty value you can create optional Int variable with value nil and use default formatter i.e. NumberFormatter()

@State var phoneNumber:Int? = nil
TextField("Phone Number", value: $phoneNumber,formatter:NumberFormatter())
    .disableAutocorrection(true)
    .autocapitalization(.none)

Other approaches are create extension and use it like you did

extension Formatter {
    static let valueFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        formatter.minusSign   = " - "  // Just for fun!
        formatter.zeroSymbol  = ""     // Show empty string instead of zero
        return formatter
    }()
}

TextField code like

  TextField("Phone Number", value: $phoneNumber,formatter:Formatter.valueFormatter)
                    .keyboardType(.decimalPad)

Sign up to request clarification or add additional context in comments.

Comments

1

A possible solution is to intercept input/output via proxy binding and and perform needed additional validation/processing.

demo

Tested with Xcode 13.3 / iOS 15.4

Here is main part:

TextField("Phone Number", value: Binding(
    get: { phoneNumber ?? 0},
    set: { phoneNumber = phoneNumber == $0 ? nil : $0 }
    ), formatter: numberFormatter)

Complete test module in project

4 Comments

thank you this solution worked. I am just trying to understand why it works. In the set field, it seems like if phoneNumber is equal to the value in the TextField (denoted by $0), then phoneNumber is set to nil again. Can you help me understand why this produces the result it does? Also, when I type a string in, the Entered text alternates between 0 and nil. Why does this happen? I am fairly new to swift and thank you for all your help.
Also, when I remove the .textFieldStyle(RoundedBorderTextFieldStyle()), the numbers stop updating in the Entered field. Do you know why this could be?
Hmm.. that's weird, I added style just for better visibility of edit field, but it appears that style affects behavior ... that's all about Apple :). Ok with default style they duplicate every input, not just empty, so needed different logic in setter.
@Asperi This doesn't seem to be working anymore. It seems to always force the value to 0 and if I just hit backspace to clear the 0, the app crashes.
0

Another solution that I found is just to make phoneNumber a string with default "". Then, validate it by trying to convert it to an Int via Int(phoneNumber). If Int(phoneNumber) == nil, then raise an error. Otherwise, you now have a new optional integer that can be nil or the integer provided:

var intPhoneNumber: Int?
        if phoneNumber == "" {
            intPhoneNumber = nil
        } else {
            intPhoneNumber = Int(phoneNumber)
            if intPhoneNumber == nil {
                completion(.failure(.custom(errorMessage: "Please enter a valid phone number. Try again.")))
                return
            } else if intPhoneNumber! < 0 {
                completion(.failure(.custom(errorMessage: "Invalid Phone Number. Please enter a valid 10 digit phone number.")))
                return
            } else if numberOfDigits(in: intPhoneNumber!) != 10 {
                completion(.failure(.custom(errorMessage: "Invalid Phone Number. Please enter a valid 10 digit phone number.")))
                return
            }
        }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.