SQLite.swift in SwiftUI app will not compile - swift

I'm trying to read a database and copy the rows into an array in swiftUI but I don't know the correct syntax to append the array. Here is my databaseHelper.swift
import Foundation
import SQLite
class DatabaseHelper {
var database: Connection!
var path: String
let buttonsTable = "Button"
var db: String
var english: String
var categoryID: Int
var indonesian: String
init() {
do {
let path = Bundle.main.path(forResource: "sga", ofType: "db")!
let database = try Connection(path, readonly: true)
self.database = database
print("Database initialized at path \(path)")
} catch {
print("error")
}
}
func queryDatabase() -> [ButtonData] {
var buttonVars = [ButtonData]()
do {
let buttons = try self.database.prepare(self.buttonsTable)
for user in buttons {
// print("English: \(user[self.english]), ID: \(user[self.ID]), Indonesian: \(user[self.indonesian])")
buttonVars.append(ButtonData(english: row[english], categoryID: row[categoryID], indonesian: row[indonesian]))
}
} catch {
print(error)
}
return buttonVars
}
}
struct ButtonData: Hashable {
let english: String
let categoryID: Int
let indonesian: String
}
I'm not sure what is really happening in append statement. I want to append columns in the row but it doesn't accept anything.
The error message I am getting is...
"Cannot subscript a value of type 'Statement.Element' (aka 'Array>') with an argument of type 'String'
Thanks in advance :)

buttonTable is a sqlite.swift table and needs to be more than a string.
At some point you need to define your table like this:
static let buttonsTable = Table("Button")
static let english = Expression<String>("english")
static let indonesian = Expression<String>("indonesian")
static let categoryID = Expression<Int>("categoryID")
Assuming your db already exists, you should then be able to access it.
This section looks correct:
init() {
do {
let path = Bundle.main.path(forResource: "sga", ofType: "db")!
let database = try Connection(path, readonly: true)
self.database = database
print("Database initialized at path \(path)")
} catch {
print("error")
}
}
But then you would access it like this:
func queryDatabase() -> [ButtonData] {
var buttonVars = [ButtonData]()
do {
let buttons = try self.database.prepare(self.buttonsTable)
for row in buttons {
buttonVars.append(ButtonData(english: row[english], categoryID: row[categoryID], indonesian: row[indonesian]))
}
} catch {
print(error)
}
return buttonVars
}
I'll often put my return stuff in a map, like this:
func queryDatabase() -> [ButtonData] {
do {
return try self.database.prepare(self.buttonsTable).compactMap { row in
ButtonData(english: row[english], categoryID: row[categoryID], indonesian: row[indonesian]))
}
} catch {
print(error)
}
// Return an empty array if we caught an error
return []
}

Related

Data from csv file won't show up when called in list on Swift

So I'm learning to import data from csv files to my swift project.
I'm trying to see everything is imported correctly by displaying some of the items in a list. However, even though I'm getting no errors at all, the list doesn't show up.
Can anyone help me out?
My csv setup code:
import Foundation
struct Leden: Identifiable {
var voorNaam: String = ""
var achterNaam: String = ""
var functie: String = ""
var id = UUID()
init (raw: [String]) {
voorNaam = raw[0]
achterNaam = raw[1]
functie = raw[2]
}
}
func loadCSV(from csvName: String) -> [Leden] {
var csvToStruct = [Leden]()
guard let filePath = Bundle.main.path(forResource: csvName, ofType: "csv") else {
return[]
}
var data = ""
do {
data = try String(contentsOfFile: filePath)
} catch {
print(error)
return[]
}
var rows = data.components(separatedBy: "\n")
let columnCount = rows.first?.components(separatedBy: ",").count
rows.removeFirst()
for row in rows {
let csvColumns = row.components(separatedBy: ",")
if csvColumns.count == columnCount {
let ledenStruct = Leden.init(raw: csvColumns)
csvToStruct.append(ledenStruct)
}
}
return csvToStruct
}
My code to make the csv items appear in a list:
struct PraesidiumView: View {
var individu = loadCSV(from: "Ledenlijst")
var body: some View {
NavigationView{
List(individu){Leden in
Text(Leden.voorNaam)
}
.navigationTitle("Praesidium")
}
}
}
try to implement this way, you also learn mvvm way.
class PraesidiumViewModel: ObservableObject {
#Public var ledens: [Leden] = []
func loadCSV() {
// ...
self.ledens = csvToStruct
}
}
struct PraesidiumView: View {
#StateObject var viewModel = PraesidiumViewModel()
var body: some View {
NavigationView {
List(viewModel.ledens){ leden in
Text(leden.voorNaam)
}
.navigationTitle("Praesidium")
}.onAppear() {
viewModel.loadCSV()
}
}
}
you could try something like this (untested) to read your csv data:
func loadCSV(from csvName: String) -> [Leden] {
var csvToStruct = [Leden]()
guard let filePath = Bundle.main.path(forResource: csvName, ofType: "csv") else {
return[]
}
var data = ""
do {
data = try String(contentsOfFile: filePath)
} catch {
print(error)
return []
}
var rows = data.components(separatedBy: "\n")
if let firstRow = rows.first {
rows.removeFirst()
for row in rows {
let csvColumns = row.components(separatedBy: ",")
if csvColumns.count >= 3 {
let ledenCol = Array(csvColumns[0..<3])
let ledenStruct = Leden(raw: ledenCol)
csvToStruct.append(ledenStruct)
}
}
}
return csvToStruct
}
So I find a solution.
I just restarted following the tutorial and this time, I gave all parameters the exact same name as they did in the tutorial and that worked.
So I just messed up because of probably a wrong name assigned to a certain parameter.

Firestore Swift update text realtime

I have this way of collecting information.
struct MainText {
var mtext: String
var memoji: String
}
class MainTextModel: ObservableObject {
#Published var maintext : MainText!
init() {
updateData()
}
func updateData() {
let db = Firestore.firestore()
db.collection("maintext").document("Main").getDocument { (snap, err) in
if err != nil{
print((err?.localizedDescription)!)
return
}
let memoji = snap?.get("memoji") as! String
let mtext = snap?.get("mtext") as! String
DispatchQueue.main.async {
self.maintext = MainText(mtext: mtext, memoji: memoji)
}
}
}
}
And such a way of displaying.
#ObservedObject private var viewModel = MainTextModel()
self.viewModel.maintext.memoji
self.viewModel.maintext.mtext
How can I update online without rebooting the view?
Instead of using getDocument, which only gets the document once and doesn't return updates, you'll want to add a snapshot listener.
Here's the Firestore documentation for that: https://firebase.google.com/docs/firestore/query-data/listen
In your case, you'll want to do something like:
db.collection("maintext").document("Main")
.addSnapshotListener { documentSnapshot, error in
guard let document = documentSnapshot else {
print("Error fetching document: \(error!)")
return
}
guard let data = document.data() else {
print("Document data was empty.")
return
}
if let memoji = data["memoji"] as? String, let mtext = data["mtext"] as? String {
self.maintext = MainText(mtext: mtext, memoji: memoji)
}
}

Swift Firestore cannot get data to Object

Hello i try to parse a array of strings into a dao. To do so i created this:
func getUsersAbos(){
let userid = Auth.auth().currentUser?.uid
let docRef = db.collection("Users").document(userid!)
docRef.getDocument { (document, error) in
if let city = document.flatMap({
$0.data().flatMap({ (data) in
return UserBlogObject(channelAbos: data)
})
}) {
print("City: \(city)")
} else {
print("Document does not exist")
}
}
}
and here is my Dao:
import Foundation
class UserBlogObject{
var channelAbos = Any
init(channelAbos: [String]) {
self.channelAbos = channelAbos
}
init(){
}
}
i get an error at this line:
return UserBlogObject(channelAbos: data)
Cannot convert value of type '[String : Any]' to expected argument type '[String]'
please note that i want to download just an array inside the document, not the whole document.
You can do something like this. I don't think there is a need to overcomplicate the function with flatMap() but you can do it if you want. Here is a code example of how you could create your method.
func getUsersAbos() {
guard let userID = Auth.auth().currentUser?.uid else { return }
let docRef = db.collection("Users").document(userID)
docRef.getDocument { (document, error) in
if error != nil { return }
guard let data = document?.data() else { return }
guard let channelAbos = data["channelAbos"] as? [String] else { return }
let userBlogObject = UserBlogObject.init(channelAbos: channelAbos) // This is the created object. Handle it.
}
}
And your class:
class UserBlogObject {
var channelAbos : [String]
init(channelAbos: [String]) {
self.channelAbos = channelAbos
}
}

Typecasting causing struct values to change (Swift)

After downcasting an array of structs, my Variables View window shows that all of the values in my struct have shifted "down" (will explain in a second). But when I print(structName), the values are fine. However, when I run an equality check on the struct, it once again behaves as though my values have shifted.
For example, I am trying to downcast Model A to ModelProtocol. var m = Model A and has the values {id: "1234", name: "Cal"}. When I downcast, m now has the values { id:"\0\0", name:"1234" }.
Actual Example Below:
Models that I want to downcast:
struct PrivateSchoolModel: Decodable, SchoolProtocol {
var id: String
var name: String
var city: String
var state: String
}
struct PublicSchoolModel: Decodable, SchoolProtocol {
var id: String
var name: String
var city: String
var state: String
var latitude: String
var longitude: String
}
Protocol I want to downcast to:
protocol SchoolProtocol {
var id: String { get set }
var name: String { get set }
var city: String { get set }
var state: String { get set }
var longitude: Float { get set }
var latitude: Float { get set }
}
extension SchoolProtocol {
var longitude: Float {
get { return -1.0 }
set {}
}
var latitude: Float {
get { return -1.0 }
set {}
}
}
Downcasting:
guard let downcastedArr = privateSchoolArray as? [SchoolProtocol] else { return [] }
Result (item at index 0) or originalArr:
id = "1234"
name = "Leo High School"
city = "Bellview"
state = "WA"
Result (item at index 0) of downcastedArr:
id = "\0\0"
name = "1234"
city = "Leo High School"
state = "Bellview"
But if I print(downcastArr[0]), it will show:
id = "1234"
name = "Leo High School"
city = "Bellview"
state = "WA"
But if I try originalArray[0].id == downcastArr[0].id, it returns false
My Code with the problem:
class SchoolJSONHandler {
private enum JSONFile: String {
case publicSchool = "publicSchool"
case privateSchool = "privateSchool"
}
private lazy var privateSchoolArray = getPrivateSchools()
private lazy var publicSchoolArray = getPublicSchools()
func getSchoolArray(sorted: Bool, filtered: Bool, by stateAbbreviation: String?) -> [SchoolProtocol] {
var schools = combineArrays()
if sorted {
schools.sort(by: { $0.name < $1.name })
}
if filtered {
guard let abbr = stateAbbreviation else { return [] }
schools = schools.filter {
return $0.state == abbr
}
}
return schools
}
private func combineArrays() -> [SchoolProtocol] {
// EVERYTHING IS FINE IN NEXT LINE
let a = privateSchoolArray
// PROBLEM OCCURS IN NEXT 2 LINES WHEN DOWNCASTING
let b = privateSchoolArray as [SchoolProtocol]
let c = publicSchoolArray as [SchoolProtocol]
return b + c
}
private func getPublicSchools() -> [PublicSchoolModel] {
guard let jsonData = getJSONData(from: .publicSchool) else { return [] }
guard let schools = decode(jsonData: jsonData, using: [PublicSchoolModel].self) else { return [] }
return schools
}
private func getPrivateSchools() -> [PrivateSchoolModel] {
guard let jsonData = getJSONData(from: .privateSchool) else { return [] }
guard let schools = decode(jsonData: jsonData, using: [PrivateSchoolModel].self) else { return [] }
return schools
}
private func getJSONData(from resource: JSONFile) -> Data? {
let url = Bundle.main.url(forResource: resource.rawValue, withExtension: "json")!
do {
let jsonData = try Data(contentsOf: url)
return jsonData
}
catch {
print(error)
}
return nil
}
private func decode<M: Decodable>(jsonData: Data, using modelType: M.Type) -> M? {
do {
//here dataResponse received from a network request
let decoder = JSONDecoder()
let model = try decoder.decode(modelType, from:
jsonData) //Decode JSON Response Data
return model
} catch let parsingError {
print("Error", parsingError)
}
return nil
}
}
And then it is just called in another class by schoolJSONHandler.getSchoolArray(sorted: true, filtered: true, by: "WA")

Firestore - Subcollections Swift

So I'm trying to learn some Firestore basic functionality and have watched "Kilo Locos" videos on YouTube explaining CRUD operations. I want to take his method of code and create subcollections from it. Basically, how can I add a collection and make the 'User' collection a sub collection from this new collection. Any help is greatly appreciated, many thanks!!
Here is a link to download the project:
https://kiloloco.com/courses/youtube/lectures/3944217
FireStore Service
import Foundation
import Firebase
import FirebaseFirestore
class FIRFirestoreService {
private init() {}
static let shared = FIRFirestoreService()
func configure() {
FirebaseApp.configure()
}
private func reference(to collectionReference: FIRCollectionReference) -> CollectionReference {
return Firestore.firestore().collection(collectionReference.rawValue)
}
func create<T: Encodable>(for encodableObject: T, in collectionReference: FIRCollectionReference) {
do {
let json = try encodableObject.toJson(excluding: ["id"])
reference(to: collectionReference).addDocument(data: json)
} catch {
print(error)
}
}
func read<T: Decodable>(from collectionReference: FIRCollectionReference, returning objectType: T.Type, completion: #escaping ([T]) -> Void) {
reference(to: collectionReference).addSnapshotListener { (snapshot, _) in
guard let snapshot = snapshot else { return }
do {
var objects = [T]()
for document in snapshot.documents {
let object = try document.decode(as: objectType.self)
objects.append(object)
}
completion(objects)
} catch {
print(error)
}
}
}
func update<T: Encodable & Identifiable>(for encodableObject: T, in collectionReference: FIRCollectionReference) {
do {
let json = try encodableObject.toJson(excluding: ["id"])
guard let id = encodableObject.id else { throw MyError.encodingError }
reference(to: collectionReference).document(id).setData(json)
} catch {
print(error)
}
}
func delete<T: Identifiable>(_ identifiableObject: T, in collectionReference: FIRCollectionReference) {
do {
guard let id = identifiableObject.id else { throw MyError.encodingError }
reference(to: collectionReference).document(id).delete()
} catch {
print(error)
}
}
}
FIRCollectionReference
import Foundation
enum FIRCollectionReference: String {
case users
}
User
import Foundation
protocol Identifiable {
var id: String? { get set }
}
struct User: Codable, Identifiable {
var id: String? = nil
let name: String
let details: String
init(name: String, details: String) {
self.name = name
self.details = details
}
}
Encodable Extensions
import Foundation
enum MyError: Error {
case encodingError
}
extension Encodable {
func toJson(excluding keys: [String] = [String]()) throws -> [String: Any] {
let objectData = try JSONEncoder().encode(self)
let jsonObject = try JSONSerialization.jsonObject(with: objectData, options: [])
guard var json = jsonObject as? [String: Any] else { throw MyError.encodingError }
for key in keys {
json[key] = nil
}
return json
}
}
Snapshot Extensions
import Foundation
import FirebaseFirestore
extension DocumentSnapshot {
func decode<T: Decodable>(as objectType: T.Type, includingId: Bool = true) throws -> T {
var documentJson = data()
if includingId {
documentJson!["id"] = documentID
}
let documentData = try JSONSerialization.data(withJSONObject: documentJson!, options: [])
let decodedObject = try JSONDecoder().decode(objectType, from: documentData)
return decodedObject
}
}
The Firestore structure cannot have collection as children of other collections.
The answer to your question (How can I add a collection and make the 'User' collection a sub collection from this new collection?) is you cannot. Instead you must put a document between those two collections.
Read this for more information.
It says: Notice the alternating pattern of collections and documents. Your collections and documents must always follow this pattern. You cannot reference a collection in a collection or a document in a document.