繁体   English   中英

SwiftUI 枚举绑定不刷新视图

[英]SwiftUI enum binding not refreshing view

我试图根据枚举值显示不同的视图(具有相同的基数),但取决于如何“检查”枚举行为的变化。 这是代码(我使用“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()
    }
}

我不明白为什么使用开关(useSwitch == true)会刷新视图,但使用将枚举作为参数传递的构造函数(useSwitch = false)不会刷新视图......它无法检测到 currentType 有如果用作参数而不是使用开关检查它,则更改?

这都是关于身份的。 如果您需要更多信息,我建议您观看WWDC Demystify SwiftUI

如果您的@State var 在更改 Picker 时触发,则TestZStackView会自行重建。 当遇到 if/else 子句时,有两种可能性:

  • private var useSwitch = true 因此它检查currentType并构建适当的BaseView 它们在 id 上彼此不同,因此构建了一个新视图,您将得到您所期望的。

  • 第二种情况不太直观。 真心推荐看前面提到的WWDC session。 如果private var useSwitch = false则没有 switch 语句,并且 SwiftUI 会尝试找出您的BaseView是否已更改并需要重新渲染。 对于 SwiftUI,即使您提供了新的BaseVM ,您的BaseView也没有改变。 它只通知依赖属性或结构(或ObservableObject中的@Published )的更改。

在您的情况下@StateObject var vm: BaseVM是罪魁祸首。 但是删除@StateObject会创建新的视图,但您会失去ObservableObject功能。

这里的解决方案是重组你的代码。 仅使用一个BaseVm实例来保存您的 state 并将其传递到环境中。

例如:

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