Frakcool
Frakcool

Reputation: 11153

SwiftUI custom number formatter as phone

I'm trying to create a custom phone formatter. I found PhoneNumberKit that could work for my particular case, the problem with this is that when I tried it there are multiple issues like:

So, what I'm trying to do is to create my own PhoneFormatter, something like: (###) ###-####, currently this might suffice as we're only targeting a single country and this is the most common format used here.

I'm following the code from this site to get an idea of how to do it, but here they are working with dates:

struct ContentView: View {
    static let taskDateFormat: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateStyle = .long
        return formatter
    }()

    var dueDate = Date()

    var body: some View {
        Text("Task due date: \(dueDate, formatter: Self.taskDateFormat)")
    }
}

Someone asked something similar 2 years ago on Apple's Developer Forums but I believe it was for UIKit rather than SwiftUI and there's no code shown there.

I'm trying to attach my formatter directly to my own TextField, I've tried looking for something similar with SwiftUI and I haven't found anything, and still can't find myself how to create my own custom formatter using something like formatter.numberStyle = "(###) ###-####"

Could anyone point me in the right direction?

Upvotes: 1

Views: 544

Answers (1)

Tim
Tim

Reputation: 11

Try editing the dictionary to your desire...

 let testCountries: [ PhoneNumberFormat ]  = [
        PhoneNumberFormat ( id: "United States" ,flagEmoji: "🇺🇸"      , leadingDigits: "+1"       , format: "+X (XXX) XXX-XXXX"       ),
        PhoneNumberFormat ( id: "Argentina"     ,flagEmoji: "🇦🇷"      , leadingDigits: "+54"      , format: "+XX (X) XXXX-XXXX"       ),
        PhoneNumberFormat ( id: "Austria"       ,flagEmoji: "🇦🇹"      , leadingDigits: "+43"      , format: "+XX (X) XXXXX-XXXX"      ),
        PhoneNumberFormat ( id: "Brazil"        ,flagEmoji: "🇧🇷"      , leadingDigits: "+55"      , format: "+XX (XX) XXXX-XXXX"      ),
        PhoneNumberFormat ( id: "Canada"        ,flagEmoji: "🇨🇦"      , leadingDigits: "+1"       , format: "+X (XXX) XXX-XXXX"       ),
        PhoneNumberFormat ( id: "China"         ,flagEmoji: "🇨🇳"      , leadingDigits: "+86"      , format: "+XX (XXX) XXXX-XXXX"     ),
        PhoneNumberFormat ( id: "France"        ,flagEmoji: "🇫🇷"      , leadingDigits: "+33"      , format: "+XX (X) XX XX XX XX"     ),
        PhoneNumberFormat ( id: "Germany"       ,flagEmoji: "🇩🇪"      , leadingDigits: "+49"      , format: "+XX (XXX) XXXXXXX"       ),
        PhoneNumberFormat ( id: "India"         ,flagEmoji: "🇮🇳"      , leadingDigits: "+91"      , format: "+XX XXXXX-XXXXX"         ),
        PhoneNumberFormat ( id: "Italy"         ,flagEmoji: "🇮🇹"      , leadingDigits: "+39"      , format: "+XX (XXX) XXXXXXX"       ),
    ]
        

    
@main
struct PhoneNumberApp: App {
    @State var PN: String = ""
    var body: some Scene {
        WindowGroup {
            PhoneNumberField()
        }
    }
}
        
struct PhoneNumberFormat: Identifiable {
    let id: String
    let flagEmoji: String
    let leadingDigits: String
    let format: String

}
        
struct PhoneNumberField: View {
    @State private var country: String = "United States"
    @State var phoneNumber: String = ""
    private var data: PhoneNumberFormat { testCountries.first ( where: { $0.id == self.country } )! }

    var body: some View {
        HStack {
            Picker("Hide Label", selection: $country, content: {
                ForEach ( testCountries , id: \.id ) { PN_Data in
                    Text ( PN_Data.id )
                }
            }) .frame ( maxWidth: 200 ) .labelsHidden()

            Text ( self.data.flagEmoji )

            TextField ( "" , text: self.$phoneNumber.phoneNumberFormat ( self.data ) )
                .onAppear { self.phoneNumber = data.leadingDigits }
                
                .onChange ( of: self.country ) { nv in self.phoneNumber = data.leadingDigits }
        }
    }
}
    
extension Binding where Value == String {
    func phoneNumberFormat ( _ data: PhoneNumberFormat ) -> Binding<String> {
        return Binding (
            get: {
                self.wrappedValue
            },
            set: { newValue in
                DispatchQueue.main.async {
                    let filteredString = newValue.filter { $0.isNumber }
                    var formattedString = ""
                    var currentNumberIndex = filteredString.startIndex
                    var patternIndex = data.format.startIndex
                    
                    while patternIndex < data.format.endIndex && currentNumberIndex < filteredString.endIndex {
                        if data.format [ patternIndex ] == "X" {
                            formattedString.append ( filteredString[currentNumberIndex ] )
                            currentNumberIndex = filteredString.index(after: currentNumberIndex)
                        } else {
                            formattedString.append ( data.format [ patternIndex ] )
                        }
                        patternIndex = data.format.index ( after: patternIndex )
                    }
                    self.wrappedValue = formattedString
                    guard newValue.hasPrefix ( data.leadingDigits ) else { self.wrappedValue = data.leadingDigits ; return  }
                }
            }
        )
    }
}

Upvotes: 0

Related Questions