如何在SwiftUI中对@State属性包装器进行子类化

时间:2019-10-10 11:22:16

标签: swift state swiftui

我有一个SELECT MAX(StudentID) FROM students GROUP BY CountryID, CityID 变量,我想添加一个约束,例如下面的简化示例:

@State

但是,这看起来不太好(尽管似乎可行),但我真正想做的是以某种方式继承或扩展属性包装器@State private var positiveInt = 0 { didSet { if positiveInt < 0 { positiveInt = 0 } } } ,以便可以在此设置器中添加此约束。但是我不知道该怎么做。可能吗

2 个答案:

答案 0 :(得分:2)

由于@State@State,因此您不能继承Struct的子类。您正在尝试操纵模型,因此不应在您的视图中考虑此逻辑。您至少应该以这种方式依赖于您的视图模型:

class ContentViewModel: ObservableObject {
    @Published var positiveInt = 0 {
        didSet {
            if positiveInt < 0 {
                positiveInt = 0
            }
        }
    }
}

struct ContentView: View {
    @ObservedObject var contentViewModel = ContentViewModel()

    var body: some View {
        VStack {
            Text("\(contentViewModel.positiveInt)")
            Button(action: {
                self.contentViewModel.positiveInt = -98
            }, label: {
                Text("TAP ME!")
            })
        }
    }
}

但是,由于SwiftuUI并不是事件驱动的框架(它全都涉及数据,模型,绑定等),我们应该习惯于不对事件做出反应,而应将视图设计为“始终与模型保持一致”。 。在您的示例和上面我的回答中,我们对更改为整数的值做出反应,以覆盖其值并强制重新创建视图。更好的解决方案可能是这样的:

class ContentViewModel: ObservableObject {
    @Published var number = 0
}

struct ContentView: View {
    @ObservedObject var contentViewModel = ContentViewModel()

    private var positiveInt: Int {
        contentViewModel.number < 0 ? 0 : contentViewModel.number
    }

    var body: some View {
        VStack {
            Text("\(positiveInt)")
            Button(action: {
                self.contentViewModel.number = -98
            }, label: {
                Text("TAP ME!")
            })
        }
    }
}

或更简单(因为基本上没有更多的逻辑了):

struct ContentView: View {
    @State private var number = 0

    private var positiveInt: Int {
        number < 0 ? 0 : number
    }

    var body: some View {
        VStack {
            Text("\(positiveInt)")
            Button(action: {
                self.number = -98
            }, label: {
                Text("TAP ME!")
            })
        }
    }
}

答案 1 :(得分:0)

您不能应用多个propertyWrapper,但是可以使用2个独立的包装值。首先创建一个将值钳位到Range的值:

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

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

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

接下来,创建一个ObservableObject作为您的后备商店:

class Model: ObservableObject {

    @Published
    var positiveValue: Int = 0

    @Clamping(0...(.max))
    var clampedValue: Int = 0 {
        didSet { positiveValue = clampedValue }
    }
}

现在您可以在内容视图中使用它:

    @ObservedObject var model: Model = .init()

    var body: some View {
        Text("\(self.model.positiveValue)")
            .padding()
            .onTapGesture {
                 self.model.clampedValue += 1
            }
    }