How to format TextField input string by function?

Viewed 787

I have a simple TextField for telephone input, and want to format it each time it being changed.

I'm using PhoneNumberKit and it works fine, but i do not understand how to call formatting func after the value in textField have changed.

Telephone Formatting function.

import SwiftUI
import PhoneNumberKit
import Combine

func formatTelephone(telephone : String) -> String
    {

        do {
            let phoneNumber =  PartialFormatter().formatPartial(telephone)

            print(phoneNumber)
            return phoneNumber
        }
        catch {
            print("Generic parser error")

        }
    }

It does something like this:

formatTelephone("79152140700") -> "7 (915) 214 08-00" 
formatTelephone("791521") -> "7 (915) 21" 

and i have a TextField like that

TextField("915 214 07 00" , text: $telephoneManager.telephone)

After each input of a digit the whole textfield label needs to be formatted by a func and show user's input in a better way.

1 Answers

In order to get what you want you need to use Combine (the implementation of the two methods in the view model is up to you because they strictly depend on your needs):

import SwiftUI
import Combine

class ContentViewModel: ObservableObject {
    @Published var phoneNumber = ""
    private var toCancel: AnyCancellable!

    init() {
        toCancel = $phoneNumber
            .filter { !self.isFormatValid($0) }
            .map { self.convert($0) }
            .receive(on: RunLoop.main)
            .assign(to: \.phoneNumber, on: self)
    }

    private func isFormatValid(_ str: String) -> Bool {
        //here you must check if _str_ is already in the right format
        true //just to make this example compile, but clearly this must be replaced with your code
    }

    private func convert(_ str: String) -> String {
        //convert your string to be in the right format
        "string in the correct format" //just to make this example compile, but clearly this must be replaced with your code
    }

    deinit {
        toCancel.cancel()
    }
}

struct ContentView: View {
    @ObservedObject var contentViewModel = ContentViewModel()

    var body: some View {
        TextField("Your phone number...", text: $contentViewModel.phoneNumber)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Steps are (look at the view model init):

  1. phoneNumber is a @Published property, so "it's backed" by a publisher you can subscribe to. So, let's subscribe to that publisher.
  2. You must check if the string in your textfield is already in the right format. This is paramount: if you miss this step you'll end up with an infinite loop. This is done through the .filter operator. The event is forwarded down only if the .filter operator returns true (in this case it returns true if the string format is invalid).
  3. With the .map operator you can change the string (in the wrong format) and return the string in the right format.
  4. .receive lets you forward the event on the thread you are interested in. In this case the main thread (since you want to update a textfield).
  5. .assign just assigns the new value to the textfield.
Related