我有一个非常简单的应用程序示例,该示例具有两个视图:MasterView和DetailView。 MasterView显示在ContentView内部,并带有NavigationView:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
MasterView(viewModel: MasterViewModel())
.navigationBarTitle(Text("Master"))
.navigationBarItems(
leading: EditButton()
)
}
}
}
struct MasterView: View {
@ObservedObject private var viewModel: MasterViewModel
init(viewModel: MasterViewModel) {
self.viewModel = viewModel
}
var body: some View {
print("Test")
return DataStatusView(dataSource: self.$viewModel.result) { texts -> AnyView in
print("Closure")
return AnyView(List {
ForEach(texts, id: \.self) { text in
NavigationLink(
destination: DetailView(viewModel: DetailViewModel(stringToDisplay: text))
) {
Text(text)
}
}
})
}.onAppear {
if case .waiting = self.viewModel.result {
self.viewModel.fetch()
}
}
}
}
struct DetailView: View {
@ObservedObject private var viewModel: DetailViewModel
init(viewModel: DetailViewModel) {
self.viewModel = viewModel
}
var body: some View {
self.showView().onAppear {
self.viewModel.fetch()
}
.navigationBarTitle(Text("Detail"))
}
func showView() -> some View {
switch self.viewModel.result {
case .found(let s):
return AnyView(Text(s))
default:
return AnyView(Color.red)
}
}
}
DataStatusView是管理某些状态的简单视图:
public enum ResultState<T, E: Error> {
case waiting
case loading
case found(T)
case failed(E)
}
struct DataStatusView<Content, T>: View where Content: View {
@Binding private(set) var dataSource: ResultState<T, Error>
private let content: (T) -> Content
private let waitingContent: AnyView?
@inlinable init(dataSource: Binding<ResultState<T, Error>>,
waitingContent: AnyView? = nil,
@ViewBuilder content: @escaping (T) -> Content) {
self._dataSource = dataSource
self.waitingContent = waitingContent
self.content = content
}
var body: some View {
self.buildMainView()
}
private func buildMainView() -> some View {
switch self.dataSource {
case .waiting:
return AnyView(Color.red)
case .loading:
return AnyView(Color.green)
case .found(let data):
return AnyView(self.content(data))
case .failed:
return AnyView(Color.yellow)
}
}
}
和视图模型是一个非常简单的“假装进行网络调用”的虚拟机:
final class MasterViewModel: ObservableObject {
@Published var result: ResultState<[String], Error> = .waiting
init() { }
func fetch() {
self.result = .loading
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
guard let self = self else { return }
self.result = .found(["This", "is", "a", "test"])
}
}
}
final class DetailViewModel: ObservableObject {
@Published var result: ResultState<String, Error> = .waiting
private let stringToDisplay: String
init(stringToDisplay: String) {
self.stringToDisplay = stringToDisplay
}
func fetch() {
self.result = .loading
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
guard let self = self else { return }
self.result = .found(self.stringToDisplay)
}
}
}
现在我遇到的问题是,每次我从Master-> Detail视图进入时,都会调用DataStatusView内部的块。这是一个问题,因为将不断重新创建“ DetailView”(因此也将重新创建它的vm,这将导致无法加载明细数据)。
之所以发生这种情况,是因为当我从master转到-> detail时,导航栏中的按钮会更改(或者至少是假设)。当我删除行时:
.navigationBarItems(
leading: EditButton()
)
这是“预期”。
处理此问题的“ SwiftUI”方法是什么?一个显示此问题的示例项目在这里:https://github.com/kerrmarin/swiftui-mvvm-master-detail