Kighian
Kighian

Reputation: 81

swiftui Numeric TextField maximum and minimum values

I'm trying to implement some TextFields that accept any number in a desired range. This is, if the user is entering an value, I'd like it to be from min to max dynamically , for example. However, I don't know how to control this in a TextField.

struct Container {
var textInput: Double
}

struct ContentView: View {
@State private var container = Container
var body: some View {
TextField("", value: $container.textInput, format: .number)
                        .keyboardType(.decimalPad)
                        .frame(width: 200, height: 20)
                        .padding()
}
}

Upvotes: 1

Views: 2096

Answers (2)

gustav
gustav

Reputation: 406

Had the exact same problem and came up with this:
Using a custom formatter – it’s not perfect but it works the way I want it to.

class BoundFormatter: Formatter {
    
    var max: Int = 0
    var min: Int = 0
    
    func clamp(with value: Int, min: Int, max: Int) -> Int{
        guard value <= max else {
            return max
        }
        
        guard value >= min else {
            return min
        }
        
        return value
    }

    func setMax(_ max: Int) {
        self.max = max
    }
    func setMin(_ min: Int) {
        self.min = min
    }
    
    override func string(for obj: Any?) -> String? {
        guard let number = obj as? Int else {
            return nil
        }
        return String(number)
        
    }

    override func getObjectValue(_ obj: AutoreleasingUnsafeMutablePointer<AnyObject?>?, for string: String, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {

        guard let number = Int(string) else {
            return false
        }
        
        obj?.pointee = clamp(with: number, min: self.min, max: self.max) as AnyObject
        
        return true
    }
    
}

Then I use it like this:

let max: Int = 100
let min: Int = 0
var formatter: BoundFormatter {
   let formatter = BoundFormatter()
   formatter.setMax(self.max)
   formatter.setMin(self.min)
   return formatter
}

@Binding var value: Int = 0

//// VIEW BODY \\\\

TextField("Number here:", value: $value, formatter: boundFormatter)

You can even improve this version by setting min max in the formatter as bindings, so you have dynamic bounds.

class BoundFormatter: Formatter {
    
    @Binding var max: Int
    @Binding var min: Int

    // you have to add initializers
    init(min: Binding<Int>, max: Binding<Int>) {
       self._min = min
       self._max = max

       super.init()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented"
    }
    ...
}


/// usage

TextField("Number here:", value: $value, formatter: BoundFormatter(min: .constant(0), max: $max))

Upvotes: 1

mohammad mohajer
mohammad mohajer

Reputation: 1

 TextField(
        .onChange(of: text, perform: {
          text = String($0.prefix(1))
        })

Upvotes: 0

Related Questions