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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 11:52:59