[英]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.