SwiftUI 中的@propertywrapper

@propertywrapper in SwiftUI

我创建了一个@propertyWrapper 来限制变量可以达到的数量。我在 SwiftUI 视图中尝试了它,它带有一个增加变量值的按钮并且它起作用了,变量停止在初始化程序中设置的最大数量处。但是,如果我用 Textflied 尝试它,它不起作用,如果我插入一个比一组更大的数字,什么也没有发生,它让我这样做。我该如何解决这个问题,我知道这个问题与绑定有关,但我不知道它到底是什么,这是代码:

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 = [=10=] }
        )
    }
    
    init(maximum: T){
        max = maximum
    }
}

extension Maximum: DynamicProperty {
    
}

@Asperi 关于如何创建 属性 包装器的说法完全正确。但是,这并没有解决 TextField() 的问题。问题似乎是您实际上并没有在 TextField 上使用 $quantity,而是使用来自 $quantity. 派生的格式化程序的字符串这似乎不允许更新机制正常工作。

但是,您可以简单地通过将 @State 字符串输入 TextField,然后更新 .onChange(of:) 中的所有内容来解决此问题。这允许您将 quantity 设置为 TextFieldInt 值,最大值可防止 quantity 过高。然后你转身将你的字符串设置为 quantity.description 以保持一切同步。

最后一件事,我将 keyboardType 更改为 .decimalPad 以使输入更容易。

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 = [=10=] }
        )
    }
    
    init(maximum: T){
        max = maximum
    }
}