织梦CMS - 轻松建站从此开始!

欧博ABG-会员注册-官网网址

How do I read data using GRDB in a SwiftUI applic

时间:2025-07-31 04:10来源: 作者:admin 点击: 7 次
Ok, so now that I've had time to delve into this, here is the solution I found. Assuming a database is already attached, I created an envSetting.swif

Ok, so now that I've had time to delve into this, here is the solution I found.

Assuming a database is already attached, I created an envSetting.swift file to hold an ObservableObject. Here is that file, which I feel is fairly self-explanatory (It's a basic ObservableObject set-up see https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-observedobject-to-manage-state-from-external-objects):

import UIKit import GRDB class EnvSettings: ObservableObject { @Published var team: [Athlete] = getAthletes(withQuery: "SELECT * FROM Athlete ORDER BY lastName") func updateAthletes() { team = getAthletes(withQuery: "SELECT * FROM Athlete ORDER BY lastName") } }

In this code, the getAthletes function returns an array of Athlete objects. It resides in an Athlete.swift file, the bulk of which comes from the GRDB demo app with specific edits and functions for my case:

import SwiftUI import GRDB // A plain Athlete struct struct Athlete { // Prefer Int64 for auto-incremented database ids var athleteID: Int64? var firstName: String var lastName: String var dateOfBirth: String } // Hashable conformance supports tableView diffing extension Athlete: Hashable { } // MARK: - Persistence // Turn Player into a Codable Record. // See https://github.com/groue/GRDB.swift/blob/master/README.md#records extension Athlete: Codable, FetchableRecord, MutablePersistableRecord { // Define database columns from CodingKeys private enum Columns { static let id = Column(CodingKeys.athleteID) static let firstName = Column(CodingKeys.firstName) static let lastName = Column(CodingKeys.lastName) static let dateOfBirth = Column(CodingKeys.dateOfBirth) } // Update a player id after it has been inserted in the database. mutating func didInsert(with rowID: Int64, for column: String?) { athleteID = rowID } } // MARK: - Database access // Define some useful player requests. // See https://github.com/groue/GRDB.swift/blob/master/README.md#requests extension Athlete { static func orderedByName() -> QueryInterfaceRequest<Athlete> { return Athlete.order(Columns.lastName) } } // This is the main function I am using to keep state in sync with the database. func getAthletes(withQuery: String) -> [Athlete] { var squad = [Athlete]() do { let athletes = try dbQueue.read { db in try Athlete.fetchAll(db, sql: withQuery) } for athlete in athletes { squad.append(athlete) print("getATHLETES: \(athlete)")// use athlete } } catch { print("\(error)") } return squad } func addAthlete(fName: String, lName: String, dob: String) { do { try dbQueue.write { db in var athlete = Athlete( firstName: "\(fName)", lastName: "\(lName)", dateOfBirth: "\(dob)") try! athlete.insert(db) print(athlete) } } catch { print("\(error)") } } func deleteAthlete(athleteID: Int64) { do { try dbQueue.write { db in try db.execute( literal: "DELETE FROM Athlete WHERE athleteID = \(athleteID)") } } catch { print("\(error)") } } //This code is not found in GRDB demo, but so far has been helpful, though not //needed in this StackOverflow answer. It allows me to send any normal query to //my database and get back the fields I need, even - as far as I can tell - from //`inner joins` and so on. func fetchRow(withQuery: String) -> [Row] { var rs = [Row]() do { let rows = try dbQueue.read { db in try Row.fetchAll(db, sql: withQuery) } for row in rows { rs.append(row) } } catch { print("\(error)") } return rs }

And this is my ContentView.swift file:

import SwiftUI struct ContentView: View { @EnvironmentObject var env: EnvSettings @State var showingDetail = false var body: some View { NavigationView { VStack { List { ForEach(env.team, id: \.self) { athlete in NavigationLink(destination: DetailView(athlete: athlete)) { HStack { Text("\(athlete.firstName)") Text("\(athlete.lastName)") } } }.onDelete(perform: delete) } Button(action: { self.showingDetail.toggle() }) { Text("Add Athlete").padding() }.sheet(isPresented: $showingDetail) { //The environmentObject(self.env) here is needed to avoid the //Xcode error "No ObservableObject of type EnvSettings found. //A View.environmentObject(_:) for EnvSettings may be missing as //an ancestor of this view which will show when you try to //dimiss the AddAthlete view, if this object is missing here. AddAthlete().environmentObject(self.env) } }.navigationBarTitle("Athletes") } } func delete(at offsets: IndexSet) { deleteAthlete(athleteID: env.team[(offsets.first!)].athleteID!) env.updateAthletes() } } struct AddAthlete: View { @EnvironmentObject var env: EnvSettings @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode> @State private var firstName: String = "" @State private var lastName: String = "" @State private var dob: String = "" var body: some View { VStack { HStack{ Button(action: { self.presentationMode.wrappedValue.dismiss() }) { Text("Cancel") } Spacer() Button(action: { addAthlete(fName: self.firstName, lName: self.lastName, dob: self.dob) self.env.updateAthletes() self.presentationMode.wrappedValue.dismiss() }) { Text("Done") } } .padding() VStack (alignment: .leading, spacing: 8) { Text("First Name:") TextField("Enter first name ...", text: $firstName).textFieldStyle(RoundedBorderTextFieldStyle()) Text("Last Name:") TextField("Enter last name ...", text: $lastName).textFieldStyle(RoundedBorderTextFieldStyle()) Text("Date Of Birth:") TextField("Enter date of birth ...", text: $dob).textFieldStyle(RoundedBorderTextFieldStyle()) }.padding() Spacer() } } } struct DetailView: View { let athlete: Athlete var body: some View { HStack{ Text("\(athlete.firstName)") Text("\(athlete.lastName)") } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView().environmentObject(EnvSettings()) } }

And don't forget to add the environemnt to the SceneDelegate:

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). // HERE var env = EnvSettings() // Create the SwiftUI view that provides the window contents. let contentView = ContentView() // Use a UIHostingController as window root view controller. if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) // AND HERE ATTACHED TO THE contentView window.rootViewController = UIHostingController(rootView: contentView.environmentObject(env)) self.window = window window.makeKeyAndVisible() } }

For me this works, so far, after limited testing. I'm not sure it is the best way to go.

Essentially, we are setting up the ObservableObject to query the DB file anytime we make a pertinent change to it. That's why you see me calling the env.updateAthletes() funtion in .onDelete and in the "Done" button action for 'AddAthlete()'.

I'm not sure otherwise how to let SwiftUI know the DB has changed. GRDB does have some kind of observation code going on, but it's really, really opaque to me how to use that, or even if that is the correct solution here.

I hope this is helpful to people.

(责任编辑:)
------分隔线----------------------------
发表评论
请自觉遵守互联网相关的政策法规,严禁发布色情、暴力、反动的言论。
评价:
表情:
用户名: 验证码:
发布者资料
查看详细资料 发送留言 加为好友 用户等级: 注册时间:2025-08-01 03:08 最后登录:2025-08-01 03:08
栏目列表
推荐内容