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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 12:15:58