如果需要,更新接收器中即将到来的值

Update upcoming values in sink if needed

我目前正在尝试从使用 Binding<Double> 的文本字段修改即将到来的值,但尚未找到任何可行的解决方案。它只是无限循环(如下例所示)和其他最终不起作用的解决方案。因此,例如,如果用户输入的金额太低,我想将即将到来的值更改为最小值,如果该值高于最大值,则反之亦然。

我还想为用户显示 modified 值(如果需要),所以我不能只将它存储在另一个变量中。

关于如何解决这个问题有什么想法吗?

例子

class ViewModel: ObservableObject {
    @Published var amount: Double

    private var subscriptions: Set<AnyCancellable> = []
    private let minimum: Double = 10_000
    private let maximum: Double = 100_000

    init() {
        $amount
            .sink {
                if [=10=] < self.minimum {
                    // Set minimum value
                    self.amount = self.minimum
                } else if [=10=] > self.maximum {
                    // Set maximum value
                    self.amount = self.maximum
                }

                // If `Else` is implemented it will just be an infinite loop...
                else {
                    self.amount = [=10=]
                }
            }
            .store(in: &subscriptions)
    }

    func prepareStuff() {
        // Start preparing
        let chosenAmount = amount
    }
}

一种方法是使用 属性 包装器来限定值。

这是一个非常基本的问题示例,我们有一个 amount,我们可以将其更改为任何值。 Stepper 只是让 input/testing:

变得容易
struct ContentView: View {
    @State private var amount = 0

    var body: some View {
        Form {
            Stepper("Amount", value: $amount)

            Text(String(amount))
        }
    }
}

这个例子的问题在于 amount 不限于一个范围。要解决此问题,请创建一个 Clamping 属性 包装器(部分来自 here):

@propertyWrapper
struct Clamping<Value: Comparable> {
    private var value: Value
    let range: ClosedRange<Value>

    var wrappedValue: Value {
        get { value }
        set { value = min(max(range.lowerBound, newValue), range.upperBound) }
    }
    var clampedValue: Value {
        get { wrappedValue }
        set { wrappedValue = newValue }
    }

    init(wrappedValue value: Value, _ range: ClosedRange<Value>) {
        precondition(range.contains(value))
        self.value = value
        self.range = range
    }
}

然后我们可以链接 属性 包装器,并得到一个工作示例,其中 amount 是有限的:

struct ContentView: View {
    @State @Clamping(-5 ... 5) private var amount = 0

    var body: some View {
        Form {
            Stepper("Amount", value: $amount.clampedValue)

            Text(String(amount))
        }
    }
}

我知道,这不是限制 Stepper 范围的正确方法。相反,您应该使用 Stepper(_:value:in:)。但是,这是为了演示限制值 - 而不是 如何限制 Stepper.

这意味着您需要做什么?

嗯,首先把你的 @Published 属性 改成这样:

@Published @Clamping(10_000 ... 100_000) var amount: Double

现在您可以像往常一样访问 amount 以获得 clamped 值。像我在解决方案中所做的那样使用 $amount.clampedValue 来获取 Binding<Double> 绑定。


如果有时在编译链式 属性 包装器时遇到麻烦(可能是错误),这里是我使用 Model 对象和 @Published:

重新创建的示例
struct ContentView: View {
    @StateObject private var model = Model(amount: 0)

    var body: some View {
        Form {
            Stepper("Amount", value: $model.amount.clampedValue)

            Text(String(model.amount.clampedValue))
        }
    }
}

class Model: ObservableObject {
    @Published var amount: Clamping<Int>

    init(amount: Int) {
        _amount = Published(wrappedValue: Clamping(wrappedValue: amount, -5 ... 5))
    }
}