SwiftUI异步操作完成后视图未自动更新问题求助
问题解决思路及代码修改
核心问题分析
- SwiftData的
@Model类仅追踪持久化属性的变化,@Transient标记的临时属性不会触发SwiftData的视图更新机制。 - URLSession的异步回调运行在后台线程,直接更新UI相关属性会导致线程安全问题,同时无法触发SwiftUI的视图刷新。
NeighborView仅接收普通的Request实例,未建立属性变化的订阅关系,因此无法感知response的更新。
具体修复步骤
1. 让Request同时遵循ObservableObject协议
通过结合@Model和ObservableObject,让临时属性response的变化能触发视图更新:
@Model class Request: ObservableObject { var id: String = UUID().uuidString @Transient @Published var response: ResponseData? = nil init() { self.id = UUID().uuidString self.response = nil } }
2. 在主线程更新response属性
URLSession的回调在后台线程执行,必须切换到主线程更新@Published属性:
// OperatorView中的按钮点击逻辑修改 Button { Task{ let url: URL = URL(string: "https://www.google.com/")! var request = URLRequest(url: url) request.httpMethod = "GET" let session = URLSession(configuration: .ephemeral) session.dataTask(with: request) { data, response, error in guard error == nil else { print(error?.localizedDescription ?? "No data") return } // 切换到主线程更新属性 DispatchQueue.main.async { self.activeRequest.response = ResponseData(body: data, response: response, error: error) } }.resume() } } label: { Text("Do Task") }
3. 在NeighborView中订阅Request的变化
使用@ObservedObject让视图感知Request的属性变化:
struct NeighborView: View { @ObservedObject var activeRequest: Request var body: some View { VStack { Text("Active request is \(activeRequest.id)") if activeRequest.response != nil { Text("The request completed!") } else { Text("The request hasn't been run yet (or the view hasn't updated)") } } } }
修改后的完整代码
import SwiftUI import SwiftData // MARK: Backing data - @Model class Request: ObservableObject { var id: String = UUID().uuidString @Transient @Published var response: ResponseData? = nil init() { self.id = UUID().uuidString self.response = nil } } struct ResponseData { var body: Data? = nil var response: URLResponse? = nil var error: Error? = nil } // MARK: App main & views - @main struct sampleApp: App { var sharedModelContainer: ModelContainer = { let schema = Schema([ Request.self, ]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) do { return try ModelContainer(for: schema, configurations: [modelConfiguration]) } catch { fatalError("Could not create ModelContainer: \(error)") } }() var body: some Scene { WindowGroup { ContentView() } .modelContainer(sharedModelContainer) } } struct ContentView: View { @Environment(\.modelContext) var modelContext @Query var allRequests: [Request] @State var activeRequest: Request? = nil var body: some View { NavigationSplitView{ VStack { List(allRequests, id: \.self, selection: $activeRequest) { request in Text(request.id) } Button { modelContext.insert(Request()) } label: { Image(systemName: "plus") } } } content: { if activeRequest != nil { OperatorView(activeRequest: activeRequest!) } else { Text("Select a request") } } detail: { if activeRequest != nil { NeighborView(activeRequest: activeRequest!) } else { Text("No request has been selected") } } } } struct OperatorView: View { @Bindable var activeRequest: Request var body: some View { Button { Task{ let url: URL = URL(string: "https://www.google.com/")! var request = URLRequest(url: url) request.httpMethod = "GET" let session = URLSession(configuration: .ephemeral) session.dataTask(with: request) { data, response, error in guard error == nil else { print(error?.localizedDescription ?? "No data") return } DispatchQueue.main.async { activeRequest.response = ResponseData(body: data, response: response, error: error) } }.resume() } } label: { Text("Do Task") } } } struct NeighborView: View { @ObservedObject var activeRequest: Request var body: some View { VStack { Text("Active request is \(activeRequest.id)") if activeRequest.response != nil { Text("The request completed!") } else { Text("The request hasn't been run yet (or the view hasn't updated)") } } } }
内容的提问来源于stack exchange,提问作者Lucas Burns
相关产品推荐
相关产品推荐

