Magnas
Magnas

Reputation: 4054

SwiftUI: TextField conditional validation

The user can supply some numerical input using TextFields. If a picker is set to "plot" then any valid number is okay but if it's set to "map" then the validation must limit the number between (in this case) -180 and +180. Good numbers display a green outline and bad numbers show red.

I can handle the validation fine until the picker is changed. e.g. an input of 1234 is fine for "plot" but switching to "map" still displays green for okay when it isn't. The same is true when an invalid "map" number (showing red) remains invalid when the picker is changed to "plot". This is true both when the focus is on that particular TextField or not.

I've cut it down to a small amount of code below that demonstrates the problem. I'm not sure if my logic is wrong or I'm simply not implementing it correctly. Any help appreciated.

enter image description here

struct ContentView: View {

   @StateObject var addPatternVM = AddPatternVM()

   var body: some View {
       VStack {
           Picker(selection: $addPatternVM.type, label: Text("")) {
               Text("Plot").tag("plot")
               Text("Map").tag("map")
           }.pickerStyle(SegmentedPickerStyle())
           TextField(addPatternVM.type == "plot" ? "Minimum x" : "Minimum longitude", text: $addPatternVM.minimumXString)
               .textFieldStyle(BorderedTextView(isGood: addPatternVM.minimumXIsGood))
               .onChange(of: addPatternVM.minimumXString) { newValue in
                   addPatternVM.isMinimumXGood(newValue: newValue)
               }
       }.padding()
   }
}

My simplified view model:

class AddPatternVM: ObservableObject {

   @Published var type: String = "plot"
   @Published var minimumXIsGood = false
   @Published var minimumXString = ""

   func isMinimumXGood(newValue: String) {
       if type == "plot" {
           if numberIsGood(newValue: newValue) {
               minimumXIsGood = true
           } else {
               minimumXIsGood = false
           }
       } else if type == "map" {
           if longitudeIsGood(newValue: newValue) {
               minimumXIsGood = true
           } else {
               minimumXIsGood = false
           }
       }
   }

   func numberIsGood(newValue: String) -> Bool {
       return Double(newValue) != nil ? true : false
   }

   func longitudeIsGood(newValue: String) -> Bool {
       guard let longitude = Double(newValue) else { return false }
    
       if longitude < -180.0 || longitude > 180.0 {
           return false
       } else {
           return true
       }
   }
}

This is just the TextFieldStyle I use to show valid/invalid input.

struct BorderedTextView: TextFieldStyle {

   var isGood: Bool

   public func _body(
       configuration: TextField<Self._Label>) -> some View {
       return configuration
           .padding(EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16))
           .background(Color.white)
           .overlay(RoundedRectangle(cornerRadius: 8)
                       .stroke(lineWidth: 1)
                       .foregroundColor(isGood ? .green : .red))
   }
}

Upvotes: 1

Views: 2128

Answers (1)

the problem as I see it, is that you do not recalculate the "minimumXIsGood" value when you change the picker from plot to map and vice versa. Try this:

    Picker(selection: $addPatternVM.type, label: Text("")) {
        Text("Plot").tag("plot")
        Text("Map").tag("map")
    }.pickerStyle(SegmentedPickerStyle())
    .onChange(of: addPatternVM.type) { _ in
        addPatternVM.isMinimumXGood(newValue: addPatternVM.minimumXString)
    }

Upvotes: 1

Related Questions