SwiftUI视图HTTP请求数据刷新求助:自动/手动刷新实现方案
我来帮你搞定这两个刷新问题,咱们一步步拆解解决:
场景1:提交工单新笔记后自动同步详情视图
你的核心问题是:提交笔记成功后没有重新拉取最新的工单数据,而且当前的FetchTick只是普通工具类,没法自动通知视图更新。咱们重构一下代码结构:
1. 重构工单详情ViewModel(用@Published自动通知视图)
把FetchTick改成带@Published属性的ObservableObject,这样数据更新时视图会自动刷新:
class TicketDetailsViewModel: ObservableObject { @Published var ticket: TicketDetails? func fetchTicket(id: Int, userApi: String) { guard let url = URL(string: "URL FOR TICKET DATA") else { return } URLSession.shared.dataTask(with: url) { [weak self] data, _, error in guard let data = data, error == nil else { print("拉取工单失败:\(error?.localizedDescription ?? "未知错误")") return } do { let decodedTicket = try JSONDecoder().decode(TicketDetails.self, from: data) DispatchQueue.main.async { self?.ticket = decodedTicket } } catch { print("解析工单数据失败:\(error.localizedDescription)") } }.resume() } }
2. 给创建笔记的方法添加成功回调
修改CreateTicketNote,让它在提交成功后通知调用者,这样我们就能触发刷新:
class CreateTicketNote: ObservableObject { func createNote(ticketId: Int, userApi: String, techNote: String, completion: @escaping (Bool) -> Void) { let ticketUrl = URL(string: "URLFORTICKET")! var request = URLRequest(url: ticketUrl) request.httpMethod = "POST" request.setValue("application/json", forHTTPHeaderField: "Content-Type") // 必须加这个头,后端才能正确解析JSON let json: [String: Any] = [ "noteText": techNote, "jobticket": [ "id": ticketId, "type": "Ticket" ] ] do { let data = try JSONSerialization.data(withJSONObject: json, options: []) URLSession.shared.uploadTask(with: request, from: data) { _, response, error in if let error = error { print("提交笔记失败:\(error.localizedDescription)") completion(false) return } guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { print("响应异常:\((response as? HTTPURLResponse)?.statusCode ?? -1)") completion(false) return } completion(true) }.resume() } catch { print("JSON序列化失败:\(error.localizedDescription)") completion(false) } } }
3. 更新详情视图,提交成功后自动刷新
把原来的@State var ticket换成ViewModel实例,提交笔记成功后调用刷新方法:
struct DetailsView: View { @ObservedObject var ticketVM = TicketDetailsViewModel() @ObservedObject var createTicketNote = CreateTicketNote() @ObservedObject var ticketStatusAction = TicketStatusAction() @State private var showingNoteAlert = false @State private var note: String = "" let id: Int let displayClient: String @Binding var userApi: String var body: some View { ScrollView(.vertical){ VStack(alignment: .leading){ // 用ViewModel里的ticket代替原来的@State数组 if let ticket = ticketVM.ticket { Text(ticket.location.locationName) .fontWeight(.bold) .padding() Text("\(displayClient) - \(id)") .fontWeight(.bold) .font(.system(size:20)) .padding() Divider() Text("Status") .fontWeight(.bold) .padding() Text(ticket.statustype.statusTypeName) .padding() Text("Details") .fontWeight(.bold) .padding() Text(clearMarkdown(on: ticket.detail)) .padding() .fixedSize(horizontal: false, vertical: true) Divider() Text("Most Recent Note") .fontWeight(.bold) .padding() if let latestNote = ticket.notes.first { Text(clearMarkdown(on: latestNote.prettyUpdatedString ?? "")) .padding() Text(clearMarkdown(on: latestNote.mobileNoteText ?? "")) .padding() .fixedSize(horizontal: false, vertical: true) } } } .onAppear { ticketVM.fetchTicket(id: id, userApi: userApi) } // 创建笔记区域:修改提交动作,成功后刷新 Section(header: Text("Create New Note") .fontWeight(.bold) .padding() .padding(10) .frame(maxWidth: .infinity)) { TextField("Enter your note", text: $note) .textFieldStyle(RoundedBorderTextFieldStyle()) .frame(width: 350) .padding(15) Button(action: { showingNoteAlert = true }) { Text("Submit Note") .frame(width: 300) .padding(15) .foregroundColor(Color.white) .background(Color.orange) .cornerRadius(5) } .actionSheet(isPresented: $showingNoteAlert) { ActionSheet( title: Text("确认要添加这条笔记到\(displayClient)的工单吗?"), message: Text(note), buttons: [ .default(Text("提交")) { createTicketNote.createNote(ticketId: id, userApi: userApi, techNote: note) { success in if success { // 提交成功后立即刷新工单详情 self.ticketVM.fetchTicket(id: self.id, userApi: self.userApi) self.note = "" } } }, .cancel(){ note = "" } ]) } } // 状态修改区域保持不变 Divider() Section(header: Text("Change Ticket Status") .fontWeight(.bold) .padding() .padding(10) .frame(maxWidth: .infinity)) { // ... 原来的状态按钮代码不变 } } } func clearMarkdown(on text: String) -> String { // 你的原有实现 return text } }
这样提交笔记成功后,视图会自动刷新显示最新的笔记内容,不用重新打开视图了。
场景2:工单列表添加下拉刷新功能
对于iOS 15+,SwiftUI的List自带.refreshable修饰符,实现起来非常简单:
1. 重构工单列表ViewModel
把FetchTicksByLocation改成带@Published的ViewModel,方便管理列表数据:
class TicketsByLocationViewModel: ObservableObject { @Published var tickets: [TicksByLocation] = [] func fetchTickets(location: String, userApi: String) { guard let url = URL(string: "URLFORTICKETS") else { return } URLSession.shared.dataTask(with: url) { [weak self] data, _, error in guard let data = data, error == nil else { print("拉取工单列表失败:\(error?.localizedDescription ?? "未知错误")") return } do { let decodedTickets = try JSONDecoder().decode([TicksByLocation].self, from: data) DispatchQueue.main.async { self?.tickets = decodedTickets } } catch { print("解析工单列表失败:\(error.localizedDescription)") } }.resume() } }
2. 更新列表视图,添加下拉刷新
给List加上.refreshable修饰符,下拉时触发数据刷新:
struct ShowLocationView: View { @ObservedObject var ticketsVM = TicketsByLocationViewModel() let ticketLocation: String @Binding var userApi: String @State var green = Color.green @State var yellow = Color.yellow @State var blue = Color.blue @State var purple = Color.purple var body: some View { NavigationView{ List(ticketsVM.tickets) { tick in VStack(alignment: .leading, spacing: 10) { Text("\(tick.id)") .font(.system(size: 11)) .cornerRadius(5) Text(tick.displayClient) Text(tick.shortDetail) .font(.system(size: 11)) .foregroundColor(Color.gray) NavigationLink(destination: DetailsView( id: tick.id, displayClient: tick.displayClient, userApi: $userApi )) { Text("See Details") .foregroundColor(Color.blue) } Divider() } } .navigationBarTitle( Text("\(ticketLocation) - All (\(ticketsVM.tickets.count))"), displayMode: .inline ) .navigationBarItems( trailing: Text("Filter") .contextMenu { // 原来的Filter菜单保持不变 NavigationLink( destination: ShowLocationAndStatusView( ticketLocation: .constant(ticketLocation), userApi: $userApi, status: "Open", color: green ) ) { Text("Open") } // ... 其他状态选项不变 } ) // 添加下拉刷新 .refreshable { ticketsVM.fetchTickets(location: ticketLocation, userApi: userApi) } .onAppear { ticketsVM.fetchTickets(location: ticketLocation, userApi: userApi) } } } }
如果需要支持iOS 14及以下,可以用UITableView的下拉刷新桥接,但目前iOS 15+已经是主流,用.refreshable最简洁。
额外小提示
- 把所有
try!换成do-catch,避免JSON解析失败导致App崩溃 - 给HTTP请求添加必要的请求头,比如
Content-Type: application/json - 闭包里用
[weak self]避免内存泄漏
内容的提问来源于stack exchange,提问作者Seth Duncan
相关产品推荐
相关产品推荐

