Home > other >  @propertywrapper in SwiftUI
@propertywrapper in SwiftUI

Time:12-08

I created a @propertyWrapper to limit the number a variable can reach. I tried it in a SwiftUI view with a button that increases the value of the variable and it works, the variable stops at the maximum number set in the initializer. However if I try it with a Textflied it doesn't work, if I insert a higher number than the one set nothing happens, it makes me do it. How can I solve this problem, I know the problem has to do with Binding but I don't know exactly what it is, here is the code:

import SwiftUI

struct ContentView: View {
    
    @Maximum(maximum: 12) var quantity: Int
    
    var body: some View {
        NavigationView{
            Form{
                TextField("", value: $quantity, format: .number, prompt: Text("Pizza").foregroundColor(.red))
                
                Button {
                    quantity  = 1
                } label: {
                    Text("\(quantity)")
                }
            }
        }
    }
}

@propertyWrapper
struct Maximum<T: Comparable> where T: Numeric {
    @State private var number: T = 0

    var max: T

    var wrappedValue: T {
        get { number }
        nonmutating set { number = min(newValue, max) }
    }
    
    var projectedValue: Binding<T> {
        Binding(
            get: { wrappedValue },
            set: { wrappedValue = $0 }
        )
    }
    
    init(maximum: T){
        max = maximum
    }
}

extension Maximum: DynamicProperty {
    
}

CodePudding user response:

@Asperi is exactly right as to how to create the property wrapper. However, that does not solve the problem of the TextField(). The issue seems to be that You are not actually using $quantity on your TextField, but rather are using a string from the formatter that is derived from $quantity. That just doesn't seem to allow the update mechanism to work properly.

However, you can fix this simply by feeding a @State string into the TextField, and then updating everything in an .onChange(of:). This allows you to set quantity to the Int value of the TextField, and the maximum prevents the quantity from going too high. You then turn around and set your string to the quantity.description to keep everything in sync.

One last thing, I changed the keyboardType to .decimalPad to make inputting easier.

struct ContentView: View {

    @Maximum(maximum: 12) var quantity: Int
    @State private var qtyString = "0"
    
    var body: some View {
        NavigationView{
            Form{
                TextField("", text: $qtyString, prompt: Text("Pizza").foregroundColor(.red))
                    .onChange(of: qtyString) { newValue in
                        if let newInt = Int(newValue) {
                            quantity = newInt
                            qtyString = quantity.description
                        }
                    }
                    .keyboardType(.decimalPad)
                Button {
                    quantity  = 1
                } label: {
                    Text("\(quantity)")
                }
            }
        }
    }
}

@propertyWrapper
struct Maximum<T: Comparable>: DynamicProperty where T: Numeric {
    let number: State<T> = State(initialValue: 0)
    
    var max: T
    
    var wrappedValue: T {
        get { number.wrappedValue }
        nonmutating set { number.wrappedValue = min(newValue, max) }
    }
    
    var projectedValue: Binding<T> {
        Binding(
            get: { wrappedValue },
            set: { wrappedValue = $0 }
        )
    }
    
    init(maximum: T){
        max = maximum
    }
}
  • Related