我试图在这里遵循这个指南:https://elliotekj.com/2019/12/11/sqlite-ios-getting-started-with-grdb,虽然它很有帮助,但并不完全是一个教程。
到目前为止,我有这样的代码:
AppDatabase
import GRDB
var dbQueue: DatabaseQueue!
class AppDatabase {
static func setup(for application: UIApplication) throws {
let databaseURL = try FileManager.default
.url(for: .applicationDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("db.sqlite")
dbQueue = try DatabaseQueue(path: databaseURL.path)
}
}在我的AppDelegate中,这段代码:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
try! AppDatabase.setup(for: application)
return true
}它认为以上所述是正确的。目前,我正在通过Navicat操纵我的数据库,所以我知道我的桌子很好。但是现在我需要做什么才能简单地阅读我的桌子呢?
这是我的SwiftUI ContentView
import SwiftUI
import GRDB
struct ContentView: View {
@State private var firstName: String = "Saul"
@State private var dateOfBirth: String = "1992-05-12"
var body: some View {
ZStack {
VStack{
HStack {
Text("Name")
Spacer()
TextField(" Enter text ", text: $firstName)
.frame(width: 160, height: 44)
.padding(4)
.border(Color.blue)
}.frame(width:300)
HStack {
Text("Date of Birth")
Spacer()
TextField(" Enter text ", text: $dateOfBirth)
.frame(width: 160, height: 44)
.padding(4)
.border(Color.blue)
}.frame(width:300)
}.foregroundColor(.gray)
.font(.headline)
VStack {
Spacer()
Button(action: {
}) {
Text("Add").font(.headline)
}
.frame(width: 270, height: 64)
.background(Color.secondary).foregroundColor(.white)
.cornerRadius(12)
}
}
}
}
private func readPerson() {
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct Person {
var personID: Int64?
var firstName: String
var lastName: String?
var dateOfBirth: String
}
extension Person: Codable, FetchableRecord, MutablePersistableRecord {
// Define database columns from CodingKeys
private enum Columns {
static let personID = Column(CodingKeys.personID)
static let firstName = Column(CodingKeys.firstName)
static let lastName = Column(CodingKeys.lastName)
static let dateOfBirth = Column(CodingKeys.dateOfBirth)
}
// Update a person id after it has been inserted in the database.
mutating func didInsert(with rowID: Int64, for column: String?) {
personID = rowID
}
}我真的不明白用readPerson()写什么,或者把它放在什么地方。现在,我很乐意从表中填充我的textFields,但是接下来,我想使用按钮添加人员。
发布于 2020-05-09 09:51:03
好的,现在我已经有时间深入研究这个问题了,下面是我找到的解决方案。
假设数据库已经附加,我创建了一个envSetting.swift文件来保存ObservableObject。下面是该文件,我认为它是相当不言自明的(这是一个基本的ObservableObject设置--参见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")
}
}在这段代码中,getAthletes函数返回一个Athlete对象数组。它驻留在一个Athlete.swift文件中,其中大部分来自GRDB演示应用程序,其中包含特定的编辑和功能:
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
}这是我的ContentView.swift文件:
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())
}
}不要忘记将环境添加到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()
}
}对我来说,到目前为止,经过有限的测试,这是可行的。我不确定这是最好的方法。
本质上,我们正在设置ObservableObject,以便在对DB文件进行相关更改时查询它。这就是为什么我在.onDelete中调用.onDelete函数,并在“AddAthlete()”的“完成”按钮操作中调用该函数的原因。
否则,我不确定如何让SwiftUI知道DB已经改变了。GRDB确实有一些观察代码,但对于我来说,它真的非常不清楚如何使用它,或者即使这是正确的解决方案。
我希望这对人们有帮助。
发布于 2020-04-18 13:20:03
我目前正在开发一个包,使从SwiftUI访问GRDB变得容易。例如,请参阅演示项目的用法。
在你的模型中:
extension YourModelType
{
// You could define multiple different request types as needed
struct Request: GRDBFetchRequest
{
var maxCount = 100 //An example of how you can make this request configurable.
func onRead(db: Database) throws -> [YourModelType] {
let models = try YourModelType
.limit(maxCount)
.fetchAll(db)
return models
}
}在你看来:
struct YourView: View
{
@GRDBFetch(request: YourModelType.Request(maxCount: 20))
var result
var body: some View {
//Use the result as needed here
}发布于 2020-08-01 13:55:08
SwiftUI应用程序生命周期
下面是一个更新,展示了在"SWiftUI应用生命周期“中使用GRDB的一种方法
首先,创建一个类似于此的environmentObject:
import UIKit
import GRDB
class EnvSettings: ObservableObject {
@Published var players: [Player] = getData(withQuery: "SELECT * FROM Player")
}其次,在应用程序结构中使用@StateObject,如下所示:
import SwiftUI
@main
struct myNewApp: App {
@StateObject private var env = EnvSettings()
//I initialize here with a modified setupDataBase function,
//removing the AppDelegate paramters
init() {
try! setupDatabase()
}
var body: some Scene {
WindowGroup {
StartView()
.environmentObject(env) // Don't forget to add the environmentObject here
}
}
}如上所述,我修改了setupDatabase(),如下所示,删除了_ application: UIApplication参数:
import Foundation
import GRDB
func setupDatabase() throws {
let path = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true)[0] as String
let url = NSURL(fileURLWithPath: path)
if let pathComponent = url.appendingPathComponent("db.sqlite") {
let filePath = pathComponent.path
let fileManager = FileManager.default
if fileManager.fileExists(atPath: filePath) {
print("FILE AVAILABLE")
try openGSdb()
} else {
print("FILE NOT AVAILABLE")
try fileManager.copyfileToApplicationSupportDirectory(forResource: "db", ofType: "sqlite")
try openDatabase()
}
} else {
print("FILE PATH NOT AVAILABLE")
}
}
// MARK: Open the databaseQueue
func openDatabase() throws {
let databaseURL = try FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("db.sqlite")
dbQueue = try AppDatabase.openDatabase(atPath: databaseURL.path)
}将db文件输入应用程序需要与我在上面的回答中一样的FileManager扩展名:
extension FileManager {
//lets us copy in the database, creating the Application Support Directory if needed.
func copyfileToApplicationSupportDirectory(forResource name: String,
ofType ext: String) throws
{
let fileManager = FileManager.default
let urls = fileManager.urls(for: .applicationSupportDirectory, in: .userDomainMask)
if let applicationSupportURL = urls.last {
do{
try fileManager.createDirectory(at: applicationSupportURL, withIntermediateDirectories: true, attributes: nil)
print("WE ARE CREATING THE APPLICATION SUPPORT FOLDER ...")
print("APP SUPPORT FOLDER PATH:\n\(applicationSupportURL.path)")
}
catch{
print(error)
}
}
if let bundlePath = Bundle.main.path(forResource: name, ofType: ext),
let destPath = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory,
.userDomainMask,
true).first {
let fileName = "\(name).\(ext)"
let fullDestPath = URL(fileURLWithPath: destPath)
.appendingPathComponent(fileName)
let fullDestPathString = fullDestPath.path
if !self.fileExists(atPath: fullDestPathString) {
try self.copyItem(atPath: bundlePath, toPath: fullDestPathString)
print("WE COPIED THE FILE OVER")
print("BUNDLE:\n\(bundlePath)")
print("DEST:\n\(fullDestPath.path)")
}
}
}
}我还有一个AppDatabase.swift文件:
import GRDB
// The shared database queue
var dbQueue: DatabaseQueue!
/// A type responsible for initializing the application database.
///
/// See AppDelegate.setupDatabase()
struct AppDatabase {
/// Creates a fully initialized database at path
static func openDatabase(atPath path: String) throws -> DatabaseQueue {
// Connect to the database
// See https://github.com/groue/GRDB.swift/blob/master/README.md#database-connections
let dbQueue = try DatabaseQueue(path: path)
print("OPEN DATABASE")
print("DATAEBASE PATH:\n\(path)")
// Define the database schema
try migrator.migrate(dbQueue)
return dbQueue
}
/// The DatabaseMigrator that defines the database schema.
///
/// See https://github.com/groue/GRDB.swift/blob/master/README.md#migrations
static var migrator: DatabaseMigrator {
var migrator = DatabaseMigrator()
print("TRYING MIGRATOR")
// removed database specific code, see the Demo in GRDB ...
return migrator
}
}然后,在实际视图结构中,您可以这样做:
struct ContentView: View {
@EnvironmentObject var env: EnvSettings
var body: some View {
VStack {
ForEach(players, id: \.id) { player in
Text("\(player.name)")
}
}
}
}我希望这一点或多或少是明确的。任何能够加入的人都是绝对受欢迎的。它还没有经过广泛的测试,只是在一个应用程序到目前为止。但是,它在那里运行得很好。
https://stackoverflow.com/questions/61077936
复制相似问题