我目前正试图修改即将到来的值从一个使用Binding<Double>
的textField,但还没有找到任何工作的解决方案。它只是无限循环(就像下面的例子)和其他最终不起作用的解决方案。因此,例如,如果用户输入的金额过低,我希望将即将到来的值更改为最小值,反之亦然,如果该值高于最大值。
我还想为用户显示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 $0 < self.minimum {
// Set minimum value
self.amount = self.minimum
} else if $0 > self.maximum {
// Set maximum value
self.amount = self.maximum
}
// If `Else` is implemented it will just be an infinite loop...
else {
self.amount = $0
}
}
.store(in: &subscriptions)
}
func prepareStuff() {
// Start preparing
let chosenAmount = amount
}
}
一种方法是使用属性包装器来夹紧这些值。
这是一个非常基本的问题的例子,我们有一个amount
,我们可以改变为任何值。Stepper
使输入/测试变得简单:
struct ContentView: View {
@State private var amount = 0
var body: some View {
Form {
Stepper("Amount", value: $amount)
Text(String(amount))
}
}
}
这个例子的问题是amount
不局限于一个范围。为了解决这个问题,创建一个Clamping
属性包装器(部分来自这里):
@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
以获得夹住的值。使用$amount.clampedValue
,就像我在我的解决方案中得到你的Binding<Double>
绑定。
如果在编译链式属性包装器时遇到麻烦(可能是bug),这里是我使用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))
}
}