首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在SwiftUI应用程序中使用GRDB读取数据?

如何在SwiftUI应用程序中使用GRDB读取数据?
EN

Stack Overflow用户
提问于 2020-04-07 10:29:47
回答 3查看 2.9K关注 0票数 1

我试图在这里遵循这个指南:https://elliotekj.com/2019/12/11/sqlite-ios-getting-started-with-grdb,虽然它很有帮助,但并不完全是一个教程。

到目前为止,我有这样的代码:

AppDatabase

代码语言:javascript
复制
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中,这段代码:

代码语言:javascript
复制
    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

代码语言:javascript
复制
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,但是接下来,我想使用按钮添加人员。

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 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):

代码语言:javascript
复制
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演示应用程序,其中包含特定的编辑和功能:

代码语言:javascript
复制
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文件:

代码语言:javascript
复制
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

代码语言:javascript
复制
 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确实有一些观察代码,但对于我来说,它真的非常不清楚如何使用它,或者即使这是正确的解决方案。

我希望这对人们有帮助。

票数 0
EN

Stack Overflow用户

发布于 2020-04-18 13:20:03

我目前正在开发一个包,使从SwiftUI访问GRDB变得容易。例如,请参阅演示项目的用法。

GRDBSwiftUI

在你的模型中:

代码语言:javascript
复制
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
        }
    }

在你看来:

代码语言:javascript
复制
struct YourView: View
{
    @GRDBFetch(request: YourModelType.Request(maxCount: 20))
    var result

    var body: some View {
        //Use the result as needed here
    }
票数 4
EN

Stack Overflow用户

发布于 2020-08-01 13:55:08

SwiftUI应用程序生命周期

下面是一个更新,展示了在"SWiftUI应用生命周期“中使用GRDB的一种方法

首先,创建一个类似于此的environmentObject:

代码语言:javascript
复制
import UIKit
import GRDB

class EnvSettings: ObservableObject {
    @Published var players: [Player] = getData(withQuery: "SELECT * FROM Player")
}

其次,在应用程序结构中使用@StateObject,如下所示:

代码语言:javascript
复制
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参数:

代码语言:javascript
复制
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扩展名:

代码语言:javascript
复制
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文件:

代码语言:javascript
复制
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
    }
}

然后,在实际视图结构中,您可以这样做:

代码语言:javascript
复制
struct ContentView: View {

    @EnvironmentObject var env: EnvSettings

    var body: some View {
        VStack {
            ForEach(players, id: \.id) { player in
                Text("\(player.name)")
            }
        }
    }
}

我希望这一点或多或少是明确的。任何能够加入的人都是绝对受欢迎的。它还没有经过广泛的测试,只是在一个应用程序到目前为止。但是,它在那里运行得很好。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61077936

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档