swiftswiftuibindingios16ios17

Custom Binding<String> doesn't work in iOS 16


I made 2 custom Binding<String> from the priceRange property. Everything works correctly in my opinion, but when entering a value in iOS 16 nothing happens, TextField does not accept a value from the keyboard:

struct AnotherQuestion: View {
    @State
    private var priceRange: ClosedRange<Float> = 500 ... 1000

    private var lowerPriceBinding: Binding<String> {
        Binding {
            let lowerValue = Int(priceRange.lowerBound)
            guard lowerValue != 500 else { return "" }
            return "\(lowerValue)"
        } set: { newValue in
            guard let lowerBound = Float(newValue),
                  500 ... 1000 ~= lowerBound,
                  lowerBound <= priceRange.upperBound else { return }

            priceRange = lowerBound ... priceRange.upperBound
        }
    }

    private var upperPriceBinding: Binding<String> {
        Binding {
            let maxValue = Int(priceRange.upperBound)
            guard maxValue != 1000 else { return "" }
            return "\(maxValue)"
        } set: { newValue in
            guard let upperBound = Float(newValue),
                  upperBound <= 1000,
                  priceRange.lowerBound <= upperBound else { return }

            priceRange = priceRange.lowerBound ... upperBound
        }
    }

    var body: some View {
        VStack {
            HStack {
                Text("Lower: \(priceRange.lowerBound, specifier: "%0.f")")
                Spacer()
                Text("Upper: \(priceRange.upperBound, specifier: "%0.f")")
                Spacer()
            }

            HStack {
                TextField("Min price", text: lowerPriceBinding)
                TextField("Max price", text: upperPriceBinding)
            }
            .keyboardType(.numberPad)
            .textFieldStyle(.roundedBorder)
        }
        .padding(.horizontal)
    }
}

#Preview {
    AnotherQuestion()
}

The same code works fine in iOS 17, i.e. the value can be typed from the keyboard. Main question is why this binding doesn't work in iOS 16 ?


Solution

  • The binding is working fine: but the get function returns an empty string, since no single digit (or character) is in your range.

    TextField in iOS16.4 is overwriting the input with the binding value after every input. It seems that this behavior was changed in iOS17 to rewrite the value only after submitting.

    As to why this behavior changed, I can't find any official documentation. It might have to do with UIKit text changes, or one of many other changes.

    In any case, this is not really a bug but rather a change in implementation. (The behaviour in iOS16.4 is in fact more aligned with my expectations of this code.) Reading the comments, I agree that this code is hacky, and would recommend you rewrite it to work -with- SwiftUI rather than against it, e.g using the Formatter initializer for TextField, which per documentation implements the sort of behaviour you expect. (And if there is a different issue affecting the slider in your use case, please open a question concerning that).