我有一个子视图,它是一个包含来自 ObservedObject 的已发布数组变量上的 ForEach 的列表。子视图显示在主 ContentView 上的 TabView 和 NavigationView 中。
但是,当使用 onAppear 函数从网络请求更新列表时,列表不会呈现更新后的数组。
将网络调用放在另一个回调中,例如,另一个视图元素上的点击手势会导致视图正确更新。
我尝试使用 willSet 并手动调用 objectWillChange,但没有成功。
服务器列表视图.swift:
struct ServerListView: View {
@ObservedObject var viewModel: ViewModel
@State private var searchText = ""
var body: some View {
List {
TextField("Search", text: self.$searchText)
ForEach(self.viewModel.servers) { server in
Text(server.name)
}
}
.navigationBarTitle(Text("Your Servers"))
.onAppear(perform: fetchServers)
}
func fetchServers() {
self.viewModel.getServers()
}
}
ViewModel.swift:
class ViewModel: ObservableObject {
private let service: ApiService
@Published var servers: [Server] = []
init(service: ApiService) {
self.service = service
}
func getServers() {
self.service.getServers { [weak self] result in
switch result {
case .failure(let error):
print(error)
case .success(let serverListModel):
DispatchQueue.main.async {
self?.servers = serverListModel.servers
}
}
}
}
}
如果此视图未放置在父视图中,则该功能按预期工作。
编辑以获取更多信息:ApiService 类仅处理 URLSession dataTask,并且模型符合 Decodable 以从 JSON 解码。
父视图在示例中创建 ServerListView,如下所示,其中 service.authenticated 是一个 Bool,根据客户端是否通过 API 进行身份验证设置为 true 或 false。我在没有任何其他代码的情况下进行了测试,只需在正文中创建 ServerListView 即可。看到了同样的结果。
struct ContentView: View {
var service: ApiService
@ViewBuilder
var body: some View {
if service.authenticated {
TabView {
NavigationView {
ServerListView(ServerListViewModel(service: service))
}
.tabItem {
Image(systemName: "desktopcomputer")
Text("Servers")
}
}
}
else {
LoginView(service: service)
}
}
}
经过进一步测试,将 API 调用放在 ServerViewList.swift 的结构初始化程序中意味着代码按预期工作。但是,这确实意味着 API 调用被放置了两次。该结构似乎被构造了两次,但 onAppear 只被调用一次。