SwiftUI ViewModel不会更新计算变量

时间:2019-10-29 12:48:45

标签: swift mvvm viewmodel swiftui xcode11

我有一个SwiftUI视图类,该类能够更新其自己的Text视图,因为具有可绑定值的TextFields已由用户更新。问题在于所有变量都包含在View类本身中。一旦将变量提取到视图模型类中,但是由于可绑定值已更新,因此计算字段不再更新。这是(非更新)代码:

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

2 个答案:

答案 0 :(得分:1)

将您的KeViewModel结构更改为满足ObservableObject协议的类。另外,将@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对象属性的新值,而无需将其作为Environment Object向下提供给视图层次结构,但是仍然可以在所有必要的地方自动获取更新。

答案 1 :(得分:1)

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

  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,这使事情变得更容易。