SwiftUI ViewModel 不更新计算变量

SwiftUI ViewModel doesn't update computed variables

我有一个 SwiftUI 视图 class,它能够更新自己的文本视图,因为用户更新了具有可绑定值的文本字段。问题是所有变量都包含在视图 class 本身中。一旦我将变量提取到视图模型 class 但是,随着可绑定值的更新,计算字段不再更新。这是(非更新)代码:

struct KeView: View {
    var vm = KeViewModel()

    var body: some View {
        return VStack {
            Image("ke")
            InputFieldView(category: Localizable.weaponAp(), input: vm.$ap)
            InputFieldView(category: Localizable.targetArmor(), input: vm.$targetArmor)
            InputFieldView(category: Localizable.weaponRange(), input: vm.$weaponRange)
            InputFieldView(category: Localizable.targetRange(), input: vm.$targetRange)
            Text(vm.damageString)
            .foregroundColor(Color.white)
            .padding()
                .background(vm.damageColor)
            .frame(maxHeight: .infinity)
        }
    }
}


struct KeView_Previews: PreviewProvider {
    static var previews: some View {
        KeView()
    }
}

struct KeViewModel {
    @State var ap = ""
    @State var targetArmor = ""
    @State var targetRange = ""
    @State var weaponRange = ""

    var damageColor: Color {
        if damageString.contains(Localizable.outOfRange()) { return Color.red }
        if damageString.contains(Localizable.inefficient()) { return Color.black }
        let d = damageString.split(separator: " ").last ?? ""
        if (Double(d) ?? 0) < 10 { return Color.blue }
        return Color.red
    }

    var damageString : String {
            guard let ap = Double(ap),
                let weaponRange = Double(weaponRange),
                let targetRange = Double(targetRange),
                let targetArmor = Double(targetArmor) else {
                    return Localizable.damagePrefix() + " 0"
        }
            if (weaponRange < targetRange){
                return Localizable.outOfRange()
            } else {
                let difference = (weaponRange - targetRange) / 175
                //print("Difference is equal to",difference)
                let actualAp = ap + difference
                //print("actual AP is equal to",actualAp)
                if (actualAp < targetArmor){
                    return Localizable.inefficient()
                } else if (targetArmor == 0){
                    return Localizable.damagePrefix()
                        + "\(round(actualAp * 2))"
                } else {
                    return Localizable.damagePrefix()
                        + " \(round((actualAp - Double(targetArmor)) / 2 + 1.0))"
                }
            }
    }
}

下面是能够在用户输入值时更新的代码:

struct KeView: View {
    @State var ap = ""
    @State var targetArmor = ""
    @State var targetRange = ""
    @State var weaponRange = ""

    var damageColor: Color {
        if damageString.contains(Localizable.outOfRange()) { return Color.red }
        if damageString.contains(Localizable.inefficient()) { return Color.black }
        let d = damageString.split(separator: " ").last ?? ""
        if (Double(d) ?? 0) < 10 { return Color.blue }
        return Color.red
    }

    var damageString : String {
            guard let ap = Double(ap),
                let weaponRange = Double(weaponRange),
                let targetRange = Double(targetRange),
                let targetArmor = Double(targetArmor) else {
                    return Localizable.damagePrefix() + " 0"
        }
            if (weaponRange < targetRange){
                return Localizable.outOfRange()
            } else {
                let difference = (weaponRange - targetRange) / 175
                //print("Difference is equal to",difference)
                let actualAp = ap + difference
                //print("actual AP is equal to",actualAp)
                if (actualAp < targetArmor){
                    return Localizable.inefficient()
                } else if (targetArmor == 0){
                    return Localizable.damagePrefix()
                        + "\(round(actualAp * 2))"
                } else {
                    return Localizable.damagePrefix()
                        + " \(round((actualAp - Double(targetArmor)) / 2 + 1.0))"
                }
            }
    }

    var body: some View {
        return VStack {
            Image("ke")
            InputFieldView(category: Localizable.weaponAp(), input: $ap)
            InputFieldView(category: Localizable.targetArmor(), input: $targetArmor)
            InputFieldView(category: Localizable.weaponRange(), input: $weaponRange)
            InputFieldView(category: Localizable.targetRange(), input: $targetRange)
            Text(String(self.damageString))
            .foregroundColor(Color.white)
            .padding()
            .background(damageColor)
            .frame(maxHeight: .infinity)
        }
    }
}


struct KeView_Previews: PreviewProvider {
    static var previews: some View {
        KeView()
    }
}

这似乎很愚蠢,因为我无法将这些变量提取到外部结构并且更愿意在我的数据和我的视图之间进行清晰的分离。任何帮助表示赞赏。最后,如果您想自己构建和 运行 项目,可以在 https://github.com/jamesjmtaylor/wrd-ios

获得完整的项目

将您的 KeViewModel 结构更改为满足 ObservableObject 协议的 class。此外,将 @State 属性 包装器更改为 @Published 属性 包装器,如下所示:

class KeViewModel: ObservableObject {
    @Published var ap = ""
    @Published var targetArmor = ""
    @Published var targetRange = ""
    @Published var weaponRange = ""

同时使用 @ObservedOjbect 属性 包装器标记您的 ViewModel 实例:

@ObservedObject var vm: KeViewModel

现在您正在通过 TabView 中的构造函数将此视图模型注入特定视图:

TabView {
                KeView(vm: KeViewModel()).tabItem {
                    Text("KE")
                    Image("first")
...

在内容预览中:

struct KeView_Previews: PreviewProvider {
    static var previews: some View {
        KeView(vm: KeViewModel())
    }
}

现在您的 View 可以观察 ViewModel 对象以发布 ViewModel 对象属性的新值,而无需将其作为环境对象向下提供到视图层次结构,但仍会自动在所有必要的位置获取更新。

这是您可能感兴趣的完整代码:

  window.rootViewController = UIHostingController(rootView: KeView().environmentObject(KeViewModel())

class KeViewModel : ObservableObject {
@Published var ap = ""
@Published var targetArmor = ""
@Published var targetRange = ""
@Published var weaponRange = ""

var damageColor: Color {
    if damageString.contains(Localizable.outOfRange()) { return Color.red }
    if damageString.contains(Localizable.inefficient()) { return Color.black }
    let d = damageString.split(separator: " ").last ?? ""
    if (Double(d) ?? 0) < 10 { return Color.blue }
    return Color.red
}

var damageString : String {
        guard let ap = Double(ap),
            let weaponRange = Double(weaponRange),
            let targetRange = Double(targetRange),
            let targetArmor = Double(targetArmor) else {
                return Localizable.damagePrefix() + " 0"
    }
        if (weaponRange < targetRange){
            return Localizable.outOfRange()
        } else {
            let difference = (weaponRange - targetRange) / 175
            //print("Difference is equal to",difference)
            let actualAp = ap + difference
            //print("actual AP is equal to",actualAp)
            if (actualAp < targetArmor){
                return Localizable.inefficient()
            } else if (targetArmor == 0){
                return Localizable.damagePrefix()
                    + "\(round(actualAp * 2))"
            } else {
                return Localizable.damagePrefix()
                    + " \(round((actualAp - Double(targetArmor)) / 2 + 1.0))"
            }
        }
}
}


struct KeView: View {
@EnvironmentObject var model: KeViewModel
var body: some View {
    return VStack {
        Image("ke")
        InputFieldView(category: Localizable.weaponAp(), input: $model.ap)
        InputFieldView(category: Localizable.targetArmor(), input: $model.targetArmor)
        InputFieldView(category: Localizable.weaponRange(), input: $model.weaponRange)
        InputFieldView(category: Localizable.targetRange(), input: $model.targetRange)
        Text(String(model.damageString))
        .foregroundColor(Color.white)
        .padding()
        .background(model.damageColor)
        .frame(maxHeight: .infinity)
    }
}

}

模型必须是 class,因为它需要符合 observable。所有变量都需要@published,这使事情变得更容易。