3

I am trying to integrate an SQLite database (with SQLite.swift) into my project (since my unrecovered apparent limitations of CoreData referenced here.

Ive got a helper file with a class that I instantiate to do all of the heavy lifting with regards to SQLite queries.

I can not figure out how to get that data, let's say a simple list of the contents of the database, into a ForEach statement in SwiftUI.

I have tried to set a return from the function that runs the query, but its return type is AnySequence<Row>, and I'm not sure what to do with that. When I try to return it after the data is fetched, I get errors about not returning is on failure (understandably so). Don't know how to return a blank AnySequence<Row> in that case. See code below.

Any thoughts?

Code from helper class (built following some of the instructions at Kilo Loco's YouTube video:


import Foundation
import SQLite



class DataModel {

    var database: Connection!
    let usersTable = Table("users")
    let id = Expression<Int>("id")
    let name = Expression<String>("name")
    let email = Expression<String>("email")
    var dataDict = [Dictionary<String, String>()]

    init() {

        do {

            let documentDirectory = try FileManager.default.url(for: .applicationSupportDirectory, in: .allDomainsMask, appropriateFor: nil, create: true)
            let fileUrl = documentDirectory.appendingPathComponent("users").appendingPathExtension("sqlite3")
            let database = try Connection(fileUrl.path)
            self.database = database
            print("Database initialized at path \(fileUrl)")
        } catch {
            print(error)
        }

    }


    func createTable() {

        print("Create Tapped")

        let createTable = self.usersTable.create { (table) in
            table.column(self.id, primaryKey: true)
            table.column(self.name)
            table.column(self.email, unique: true)
        }

        do {

            try self.database.run(createTable)
            print("Table Created")

        } catch {
            print(error)
        }

    }


    func insertRecord(name: String, email: String) {

        let insertRecord = self.usersTable.insert(self.name <- name, self.email <- email)

        do {

            try self.database.run(insertRecord)
            print("record added")
        } catch {
            print(error)
        }

    }

    func listUsers() {

        print("Listed Users")

        do {

            let users = try self.database.prepare(self.usersTable)

            for user in users {
                print("User ID: \(user[self.id]), UserName: \(user[self.name]), Email: \(user[self.email])")

            }

        } catch {
            print(error)
        }
    }
}

and simple SwiftUI content View


import SwiftUI

let myData = DataModel()

struct ContentView: View {

    @State var name: String = ""
    @State var email: String = ""

    var body: some View {


        VStack {

            TextField("Enter Name", text: self.$name)
            TextField("Enter email", text: self.$email)

            Button(action: {
                myData.insertRecord(name: self.$name.wrappedValue, email: self.$email.wrappedValue)
            }) {
                Text("Insert Record")
            }

            Button(action: {
                myData.createTable()
            }) {
                Text("Create Table")
            }


            Button(action: {
                myData.listUsers()
            }) {
                Text("List users")
            }
        }
    }
}

In theory, what I would like to be able to do is something like in my VStack:

ForEach (myData.listUsers, id: \.self) { record in 
      Text("UserName \(record.userName)")
}

I was trying to have the listUsers function return its result so it could be used like this, but that did not work out so well..

Update #1

Trying to have the function return the data, then I can iterate over it in the SwiftUI View. I was trying this:

    func listUsers() -> (Int, String, String) {
        print("Listed Users")
        do {
            let users = try self.database.prepare(self.usersTable)
            for user in users {
                print("User ID: \(user[self.id]), UserName: \(user[self.name]), Email: \(user[self.email])")
                return (user[self.id], user[self.name], user[self.email])
            }
        } catch {
            print(error)
        }
        return (0, "No Name", "No Email")
    }

and then in the View I was trying to call:

                let response = myData.listUsers()
                for resp in response {
                    Text(resp.name)
                }

but I then get the error:

Type '(Int, String, String)' does not conform to protocol 'Sequence'

Update #2

Holy C*&@ - What a pain..

I was able to get the function to return something that I can work with, but it is most definitely not the most efficient way to do this. Any idea how I can do it better:

My function now is:

    func listUsers() ->  [Dictionary<String,Any>]  {
        print("Listed Users")
        do {
            let users = try self.database.prepare(self.usersTable)
            for user in users {
                print("User ID: \(user[self.id]), UserName: \(user[self.name]), Email: \(user[self.email])")
                dataDictArray.append(["id":user[self.id], "name":user[self.name], "email":user[self.email]])
            }
        } catch {
            print(error)
        }
        return dataDictArray
    }


}

Which returns an Array of Dictionary of String:Any (because I'm mixing String and Int in my database. Then my function looks like:

                let response = myData.listUsers()
                print(response)
                for dicts in response {
                    for record in dicts {
                        if (record.key.datatypeValue == "name") {
                            print("Record Key: \(record.key)")
                            print("Record Value: \(record.value)")
                        }
                    }
                }
                }) {
                     Text("List users")
                 }

So what its doing, which is ridiculous, is looping through the array, then looping through the dictionary to get the key value pairs..

It just feels cumbersome and overly complicated.

  • Still can't figure out how to get it into the actual view. I can print the output to the log, but not in a Text() tag..
1
  • Sorry to notice this 3 years later but Stephen Celis SQLite framework for Swift is truly truly excellent! Compared to standard SQLite libraries it's a pleasure to use because it is so brilliantly integrated. I've been using it for a long time and it is a very solid framework. Can't recommend it enough. Commented Apr 16, 2022 at 4:32

1 Answer 1

4

Ive discovered how to do this:

in my SQLite worker file, I needed to create a struct to represent the data model:

struct TestDataModel: Hashable {
    let id: Int
    let name: String
    let email: String
}

Then in my helper class, my function loads the data into an array of TestDataModel objects. The function then returns the array like this:

    func listUsers2() ->  [TestDataModel]  {
         print("Listed Users")
        var theseVars = [TestDataModel]()
         do {
             let users = try self.database.prepare(self.usersTable)
             for user in users {
                 print("User ID: \(user[self.id]), UserName: \(user[self.name]), Email: \(user[self.email])")
                theseVars.append(TestDataModel(id: user[self.id], name: user[self.name], email: user[self.email]))
            }
         } catch {
             print(error)
         }
         return theseVars
     }

Then, in my SwiftUI view, I can use the array of TestDataModel objects like normal:

            ForEach (myData.listUsers2(), id: \.self) { record in
                Text(record.email)

            }

This works, not sure if its the most elegant approach, but results are good..

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.