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..