xcodeswiftui

SwiftUI Modify TextField Format Dynamically, String Pattern, Mask


I am attempting to format the data in a SwiftUI TextField with a pattern or mask. (For clarity - NOT a UITextField). One example would be a US phone number. So the user can type 1115551212 and the result in the view is 111-555-1212. I'll use a numberPad in this case, but for future, with the full keyboard, if the user types a non-number, I'd like to be able to replace it with something, say 0. So entering 111abc1212 would result in 111-000-1212. Even though the code below converts the string to numbers, ideally, I want the mask to operate on a String not a number - giving the flexibility to format part numbers etc.

I have been able to do this with a func that operates from a button, but of course I want it to be automatic. I have been completely unsuccessful with SwiftUI modifiers to do the same. And using the built in .textContentType(.telephoneNumber) does absolutely nothing in my tests.

I would expect to have some modifier like .onExitCommand that can execute when the focus leaves the TextField but I don't see a solution.

This code works with the button (I will later add rules to filter for numbers when numbers are expected):

struct ContentView: View {

    @State private var phoneNumber = ""
    @State private var digitArray = [1]

    var body: some View {

        VStack {
            Group {//group one
                Text("Phone Number with Format").font(.title)
                    .padding(.top, 40)
            
                TextField("enter phone number", text: $phoneNumber)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding()
                    .keyboardType(.numberPad)

            }//Group one
        
            Group {//group two
                            
                Button(action: {self.populateTextFieldWithPhoneFormat(phoneString: self.phoneNumber)}) {
                    Text("Convert to Phone Format")
                }
            
            }//group two
            Spacer()
        }//VStack
    
    }

    func populateTextFieldWithPhoneFormat(phoneString: String) {

        var digitArray = [Int]()

        let padded = phoneNumber.padding(toLength: 10, withPad: "0", startingAt: 0)
        let paddedArray = padded.map {String($0)}

        for char in paddedArray {
            digitArray.append(Int(char) ?? 0)
        }

        phoneNumber = format(digits: digitArray)

    }//populate

    func format(digits: [Int]) -> String {
        var phone = digits.map(String.init)
            .joined()
        if digits.count > 3 {
            phone.insert("-", at: phone.index(
                phone.startIndex,
                offsetBy: 3)
            )
        }
        if digits.count > 7 {
            phone.insert("-", at: phone.index(
                phone.startIndex,
                offsetBy: 7)
            )
        }
        return phone
    }
}

I also attempted to make my own ViewModifier, but got nowhere.

Xcode Version 11.2.1 (11B500)


Solution

  • Isn't this constructor of what you are looking for?

    /// Creates an instance with a `Text` label generated from a title string.
    ///
    /// - Parameters:
    ///     - title: The title of `self`, describing its purpose.
    ///     - text: The text to be displayed and edited.
    ///     - onEditingChanged: An `Action` that will be called when the user
    ///     begins editing `text` and after the user finishes editing `text`,
    ///     passing a `Bool` indicating whether `self` is currently being edited
    ///     or not.
    ///     - onCommit: The action to perform when the user performs an action
    ///     (usually the return key) while the `TextField` has focus.
    public init<S>(_ title: S, text: Binding<String>, 
                onEditingChanged: @escaping (Bool) -> Void = { _ in }, 
                onCommit: @escaping () -> Void = {}) where S : StringProtocol