Search code examples
swiftswiftuiformattextfield

SwiftUI TextField problem with textInputAutocapitalization modifier


I have an issue with applying my formatting when using textInputAutocapitalization. When we uncomment textInputAutocapitalization it works correctly on iOS 17 but for e.g. iOS 16 when user enter more than this 9 letters and submit nothing happened. Do you know where can be issue?

This is my code

import SwiftUI

struct ContentView: View {
    @FocusState var isFocused: Bool
    let lengh = 9
    @State var text = ""
    var body: some View {
        VStack {
            HStack {
                TextField("write text here...",
                          value: $text,
                          format: .defaultUppercased)
                .focused($isFocused)
                //.textInputAutocapitalization(.characters)
            }
            .border(.black)
        }
        .padding()
        .onChange(of: text) {
            if text.count > lengh {
                text = String($0.prefix(lengh))
            }
        }
    }
}

#Preview {
    ContentView()
}

public struct DefaultUppercasedFormatStyle: ParseableFormatStyle {
    
    public init() {}
    
}

extension DefaultUppercasedFormatStyle {
    
    public var parseStrategy: Self {
        self
    }
    
    public func format(_ value: String) -> String {
        value
    }
    
}

extension DefaultUppercasedFormatStyle: ParseStrategy {
    
    public func parse(_ value: String) -> String {
        value.uppercased()
    }
    
}

extension FormatStyle where Self == DefaultUppercasedFormatStyle {
    
    public static var defaultUppercased: Self {
        Self()
    }
    
}

Solution

  • I've recently answered a similar question here on Stack Overflow: Different behavior of FocusState on iOS 17. The culprit is likely the FocusState which is buggy on iOS before 17. Also, it's not even possible to use a solution initilising a Binding in the TextField constructor because it has its fair share of problems on iOS 17. Something like this I mean:

    TextField("write text here...",
                          value: .init(get: {
                    text
                }, set: { newVal in
                    text = String(newVal.prefix(lengh))
                }),
                          format: .defaultUppercased)
    

    This appears to be broken on iOS 17, and would have been a great way to overcome your issue. The only thing that come to my mind is an ugly workaround. You need to change the .onChange modifier like so:

    .onChange(of: text) {
            text = String($0.prefix(lengh))
            if #available(iOS 17, *) {
                return
            }
            guard text.count >= lengh else { return }
            isFocused = false
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
                isFocused = true
            }
        }
    

    It will work well on iOS 17. The side effect is that on iOS pre 17 it will bounce the keyboard up and down, but at least it will preserve the wanted behavior.

    Let me know if that worked well for you!