You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI异步操作完成后视图未自动更新问题求助

问题解决思路及代码修改

核心问题分析

  1. SwiftData的@Model类仅追踪持久化属性的变化,@Transient标记的临时属性不会触发SwiftData的视图更新机制。
  2. URLSession的异步回调运行在后台线程,直接更新UI相关属性会导致线程安全问题,同时无法触发SwiftUI的视图刷新。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 02:13:22