简体   繁体   English

SwiftUI 枚举绑定不刷新视图

[英]SwiftUI enum binding not refreshing view

I'm trying to show different views (with the same base) depending on an enum value but depending on how to "inspect" the enum the behavior changes.我试图根据枚举值显示不同的视图(具有相同的基数),但取决于如何“检查”枚举行为的变化。 This is the code (I'm using a "useSwitch" variable to be able to alternate between both behaviors)这是代码(我使用“useSwitch”变量能够在两种行为之间交替)

import SwiftUI

enum ViewType: CaseIterable {
    case type1
    case type2
    
    var text: String {
        switch self {
        case .type1:
            return "Type 1"
        case .type2:
            return "Type 2"
        }
    }
}

final class BaseVM: ObservableObject {
    let type: ViewType
    
    @Published var requestingData = false
    
    init(type: ViewType) {
        self.type = type
    }
    
    @MainActor func getData() async {
        requestingData = true
        
        try! await Task.sleep(nanoseconds: 1_000_000_000)
        
        requestingData = false
    }
}

struct BaseView: View {
    @StateObject var  vm: BaseVM
    
    var body: some View {
        Group {
            if vm.requestingData {
                ProgressView("Getting data for \(vm.type.text)")
            } else {
                Text("\(vm.type.text)")
            }
        }
        .onAppear {
            Task {
                await vm.getData()
            }
        }
    }
}

struct TestZStackView: View {
    private let types = ViewType.allCases
    @State var currentType: ViewType = .type1
    
    private var useSwitch = true
    
    var body: some View {
        VStack {
            if useSwitch {
                Group {
                    switch currentType {
                    case .type1:
                        BaseView(vm: BaseVM(type: currentType))
                    case .type2:
                        BaseView(vm: BaseVM(type: currentType))
                    }
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            } else {
                BaseView(vm: BaseVM(type: currentType))
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            }
            Spacer()
            Picker("", selection: $currentType) {
                ForEach(types, id: \.self) {
                    Text($0.text)
                }
            }
            .pickerStyle(.segmented)
            .padding(.top, 20)
        }
        .padding()
    }
}

struct TestZStackView_Previews: PreviewProvider {
    static var previews: some View {
        TestZStackView()
    }
}

I don't understand why using a switch (useSwitch == true) refreshes the view but using the constructor passing the enum as parameter (useSwitch = false) doesn't refresh the view... It can't detect that the currentType has changed if used as parameter instead of checking it using a switch?我不明白为什么使用开关(useSwitch == true)会刷新视图,但使用将枚举作为参数传递的构造函数(useSwitch = false)不会刷新视图......它无法检测到 currentType 有如果用作参数而不是使用开关检查它,则更改?

This is all about identity.这都是关于身份的。 If you need more information I would recommend watching WWDC Demystify SwiftUI .如果您需要更多信息,我建议您观看WWDC Demystify SwiftUI

If your @State var triggers when changing the Picker the TestZStackView rebuilds itself.如果您的@State var 在更改 Picker 时触发,则TestZStackView会自行重建。 When hitting the if/else clause there are two possibilities:当遇到 if/else 子句时,有两种可能性:

  • private var useSwitch = true . private var useSwitch = true So it checks the currentType and builds the appropriate BaseView .因此它检查currentType并构建适当的BaseView These differ from each other in their id, so a new View gets build and you get what you expect.它们在 id 上彼此不同,因此构建了一个新视图,您将得到您所期望的。

  • the second case is less intuitive.第二种情况不太直观。 I really recommend watching that WWDC session mentioned earlier.真心推荐看前面提到的WWDC session。 If private var useSwitch = false there is no switch statement and SwiftUI tries to find out if your BaseView has changed and needs to rerender.如果private var useSwitch = false则没有 switch 语句,并且 SwiftUI 会尝试找出您的BaseView是否已更改并需要重新渲染。 For SwiftUI your BaseView hasn´t changed even if you provided a new BaseVM .对于 SwiftUI,即使您提供了新的BaseVM ,您的BaseView也没有改变。 It does notify only changes on depending properties or structs (or @Published in ObservableObject ).它只通知依赖属性或结构(或ObservableObject中的@Published )的更改。

In your case @StateObject var vm: BaseVM is the culprit.在您的情况下@StateObject var vm: BaseVM是罪魁祸首。 But removing @StateObject will create the new View but you loose the ObservableObject functionality.但是删除@StateObject会创建新的视图,但您会失去ObservableObject功能。

Solution here would be to restructure your code.这里的解决方案是重组你的代码。 Use only one BaseVm instance that holds your state and pass that on into the environment.仅使用一个BaseVm实例来保存您的 state 并将其传递到环境中。

Eg:例如:

final class BaseVM: ObservableObject {
    // create a published var here
    @Published var type: ViewType = .type1
    @Published var requestingData = false

    @MainActor func getData() async {
        requestingData = true
        
        try! await Task.sleep(nanoseconds: 1_000_000_000)
        
        requestingData = false
    }
}

struct BaseView: View {
    // receive the viewmodel from the environment
    @EnvironmentObject private var vm: BaseVM
    
    var body: some View {
        Group {
            if vm.requestingData {
                ProgressView("Getting data for \(vm.type.text)")
            } else {
                Text("\(vm.type.text)")
            }
        }
        // change this also because the view will not apear multiple times it
        // will just change depending on the type value
        .onChange(of: vm.type) { newValue in
            Task{
                await vm.getData()
            }
        }.onAppear{
            Task{
                await vm.getData()
            }
        }
    }
}

struct TestZStackView: View {
    private let types = ViewType.allCases
    @StateObject private var viewmodel = BaseVM()
    
    private var useSwitch = false
    
    var body: some View {
        VStack {
            if useSwitch {
                //this group doesn´t really make sense but just for demonstration
                Group {
                    switch viewmodel.type {
                    case .type1:
                        BaseView()
                            .environmentObject(viewmodel)
                    case .type2:
                        BaseView()
                            .environmentObject(viewmodel)
                    }
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity)
            } else {
                BaseView()
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
                    .environmentObject(viewmodel)
            }
            Spacer()
            Picker("", selection: $viewmodel.type) {
                ForEach(types, id: \.self) {
                    Text($0.text)
                }
            }
            .pickerStyle(.segmented)
            .padding(.top, 20)
        }
        .padding()
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM