Search code examples
swiftswiftuibindingtextfieldlimit

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()
}
}

Solution

  • 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))