iOS SwiftUI中插入SQLite数据库后UI无法实时更新求助
iOS SwiftUI SQLite数据插入后UI无法实时更新问题排查
问题现象
向SQLite数据库插入Job数据后,JobsView始终显示为空,仅重启应用后新插入的数据才会加载显示,无法实现UI实时刷新。
相关代码
JobsView.swift
import SwiftUI import Combine struct JobsView: View { @EnvironmentObject var appState: AppState @StateObject private var webSocketManager = WebSocketManager.shared @EnvironmentObject private var databaseManager: DatabaseManager @State private var selectedJobs: Set<Job> = [] //Tracks selected Jobs @State private var navigateToPODView: Bool = false //Controls navigation to POD view var body: some View { NavigationStack { VStack { // List with selection from local db List(databaseManager.jobs, id: \.id, selection: $selectedJobs) { job in HStack { if selectedJobs.contains(job) { Image(systemName: "checkmark.circle.fill") .foregroundColor(Color.green) //Shows a checkmark for selected jobs } VStack(alignment: .leading) { Text("\(job.CustomerName)") .font(.headline) Text("\(job.Address1), \(job.Suburb), \(job.State)") .font(.subheadline) Text("Invoice: \(job.InvoiceNo)") .font(.footnote) } .contentShape(Rectangle()) //Makes the entire row tappable .onTapGesture { toggleSelection(for: job) } } } .listStyle(.inset) .navigationTitle("Jobs") // Action button to proceed to POD view Button(action: { navigateToPODView = true }) { Text("Sign POD (\(selectedJobs.count) selected)") .bold() .frame(maxWidth: .infinity, minHeight: 44) .background(selectedJobs.isEmpty ? Color.gray : Color.blue) .foregroundColor(.white) .cornerRadius(8) .padding() } .disabled(selectedJobs.isEmpty) // Disable button if no jobs are selected } //.onAppear { // databaseManager.fetchJobs() //} .navigationDestination(isPresented: $navigateToPODView) { PODView(selectedJobs: Array(selectedJobs)) } } } private func toggleSelection(for job: Job) { if selectedJobs.contains(job) { selectedJobs.remove(job) } else { selectedJobs.insert(job) } } }
DatabaseManager.swift
import GRDB import Foundation import Combine class DatabaseManager : ObservableObject { // The database file URL private var dbQueue: DatabaseQueue! //Publisher for jobs updates @Published var jobs: [Job] = [] private var cancellables = Set<AnyCancellable>() init() { do { // Specify the SQLite database file location (it will create the file if it does not exist) let fileManager = FileManager.default let documentDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! let dbPath = documentDirectory.appendingPathComponent("job.sqlite") dbQueue = try DatabaseQueue(path: dbPath.path) // Create the jobs table if it doesn't already exist try createJobsTable() //Observe database changes observeJobs() } catch { print("Error initializing database: \(error)") } } // Create jobs table private func createJobsTable() throws { try dbQueue.write { db in try db.create(table: "job", ifNotExists: true) { t in t.autoIncrementedPrimaryKey("id") t.column("JobId", .integer) t.column("PickTicketId", .integer) t.column("DriverId", .integer) t.column("InvoiceNo", .text) t.column("CustomerName", .text) t.column("Address1", .text) t.column("Address2", .text) t.column("Address3", .text) t.column("Suburb", .text) t.column("State", .text) t.column("Pcode", .text) t.column("Cartons", .integer) t.column("Created", .text) } } } //Observe jobs table for changes and update the @Published property private func observeJobs() { let observation = ValueObservation.tracking() { db in try Job.fetchAll(db) //Fetch all jobs whenever there is a change } observation .publisher(in: dbQueue) .catch { error -> Just<[Job]> in print("Database observation error: \(error)") return Just([]) // Return an empty array if there's an error } .receive(on: DispatchQueue.main) .sink { [weak self] jobs in self?.jobs = jobs } .store(in: &cancellables) } func insertJob(job: Job) { do { try dbQueue.write { db in try job.insert(db) // Use GRDB's insert method } } catch { print("Error inserting job: \(error)") } } }
问题原因及修复方案
1. Job模型未实现Equatable协议
Set<Job>的contains方法、SwiftUI列表的选择逻辑都依赖Equatable协议判断对象是否相等。若Job未实现该协议,会导致选择状态异常,同时GRDB的数据观察可能无法正确识别数据变化。
修复:让Job模型遵循Equatable协议,基于唯一标识id实现相等判断:
struct Job: Codable, FetchableRecord, MutablePersistableRecord, Equatable { // 你的模型属性定义 static func == (lhs: Job, rhs: Job) -> Bool { lhs.id == rhs.id } }
2. GRDB观察器未触发初始数据加载
当前的ValueObservation配置未明确设置初始触发,可能导致应用启动时未加载已有数据,且插入新数据后观察器未正确响应。
修复:修改observeJobs方法,添加scheduling: .immediate确保首次启动就触发数据获取:
private func observeJobs() { let observation = ValueObservation.tracking { db in try Job.fetchAll(db) } observation .publisher(in: dbQueue, scheduling: .immediate) .catch { error -> Just<[Job]> in print("Database observation error: \(error)") return Just([]) } .receive(on: DispatchQueue.main) .sink(receiveValue: { [weak self] jobs in self?.jobs = jobs }) .store(in: &cancellables) }
同时,在DatabaseManager的init方法末尾手动调用一次数据拉取,确保初始数据加载:
// 在init的observeJobs()之后添加 fetchJobs()
新增fetchJobs方法:
func fetchJobs() { do { let jobs = try dbQueue.read { db in try Job.fetchAll(db) } DispatchQueue.main.async { self.jobs = jobs } } catch { print("Error fetching jobs: \(error)") } }
3. 环境对象注入验证
确保DatabaseManager在App入口正确作为EnvironmentObject注入,避免视图拿到的是不同实例导致数据不同步:
@main struct YourApp: App { @StateObject private var databaseManager = DatabaseManager() var body: some Scene { WindowGroup { JobsView() .environmentObject(databaseManager) // 确保其他需要的视图也注入该环境对象 } } }
4. 启用JobsView的onAppear数据拉取(可选)
如果上述修复仍有问题,可以取消JobsView中onAppear的注释,确保视图出现时主动拉取数据:
.onAppear { databaseManager.fetchJobs() }
内容的提问来源于stack exchange,提问作者kkaplan
相关产品推荐
相关产品推荐

