How to restrict input to TextField to take only numbers between 0-10 in SwiftUI?

Viewed 915

I have looked at different answers to this question but I am confused. I am relatively new to Swift. I am currently working on my senior project for school. Would love someone to answer the question but also go into detail. The current behavior only does 0-9. I can't get it to do the number 10.

struct ContentView: View {
    
    // Private vars for tracking
    @State private var hoursSlept = 0;
    
    // *** What happens if the user enters something other than an integer/double?
    @State private var depressedMood = ""
    @State private var elevatedMood = ""
    @State private var anxietyMood = ""
    @State private var irritabilityMood = ""
    
    @State private var maxLength = 1;
    
    
    // Var for button
    @State private var enterButtonPress = false
    
    var body: some View {
        VStack {
            NavigationView {
                Form {
                    
                    
                    
                    // Hours slept last night (0-24)
                    Stepper(value: $hoursSlept, in: 0...24, label: {
                        Text("Hours slept last night: \(self.hoursSlept)")
                        
                    })
                    
                    TextField("Depression 0-10", text: $depressedMood)
                        .keyboardType(.numberPad)
                        .onReceive(Just(depressedMood)) {
                            guard !$0.isEmpty else {
                                self.depressedMood = ""
                                return
                            }
                            if let value = Int($0.filter(\.isWholeNumber).prefix(maxLength)) {
                                self.depressedMood = String(value)
                            }
                            
                        }
                    
                    
                    
                    TextField("Elevation 0-10", text: $elevatedMood)
                        .keyboardType(.numberPad)
                        .onReceive(Just(elevatedMood)) {
                            guard !$0.isEmpty else {
                                self.elevatedMood = ""
                                return
                            }
                            
                            if let value = Int($0.filter(\.isWholeNumber).prefix(maxLength)) {
                                self.elevatedMood = String(value)
                            }
                        }
                    
                    
                    
                    TextField("Anxiety 0-10", text: $anxietyMood)
                        .keyboardType(.numberPad)
                        .onReceive(Just(anxietyMood)) {
                            guard !$0.isEmpty else {
                                self.anxietyMood = ""
                                return
                            }
                            if let value = Int($0.filter(\.isWholeNumber).prefix(maxLength)) {
                                self.anxietyMood = String(value)
                            }
                        }
                    
                    
                    TextField("Irritability 0-10", text: $irritabilityMood)
                        .keyboardType(.numberPad)
                        .onReceive(Just(irritabilityMood)) {
                            guard !$0.isEmpty else {
                                self.irritabilityMood = ""
                                return
                            }
                            if let value = Int($0.filter(\.isWholeNumber).prefix(maxLength)) {
                                self.irritabilityMood = String(value)
                            }
                        }
3 Answers

Here is a possible solution - use ~= and check if the value is in range:

struct ContentView: View {
    @State private var depressedMood = ""

    var body: some View {
        TextField("Depression 0-10", text: $depressedMood)
            .keyboardType(.numberPad)
            .onReceive(Just(depressedMood)) {
                guard let value = Int($0), 0...10 ~= value else {
                    self.depressedMood = ""
                    return
                }
                self.depressedMood = String(value)
            }
    }
}

You should try a custom TextField so that you can manage the logic. Then you can bind it to whatever text field you want.

class TextBindingManager: ObservableObject {
    @Published var text = "" {
        didSet {
            guard let newValue = Int(text) else {
                text = oldValue
                return
            }
            
            if( !(newValue >= range.lowerBound) || !(newValue <= range.upperBound)) {
                text = oldValue
            }
        }
    }
    
    let range: Range<Int>

    init(limit: Range<Int> = 0..<10){
        range = limit
    }
}

You can then use it like so wherever you like by binding it

struct MyView: View {
    
    @ObservedObject var textBindingManager = TextBindingManager()
    
    var body: some View {
            
       TextField("", text: $textBindingManager.text, onEditingChanged: { _ in }, onCommit: {})
    }
}

you could use something simple like this:

struct ContentView: View {
    @State var depressedMood = ""
    var body: some View {
        VStack {
            TextField("0-10", text: $depressedMood)
                .keyboardType(.numberPad)
                .onChange(of: depressedMood) {
                    let txt = $0.filter("0123456789".contains)
                    if let num = Int(txt), num <= 10, txt.count <= 2 {
                        depressedMood = txt
                    } else {
                        depressedMood = String(txt.dropLast())
                    }
                }
        }
    }
}
Related