RealmDB does not get populated - swift

New to Realm... so hopefully a simple fix!
I have a data object of:
class GasFile : Object {
#objc dynamic var gasFilename : String = ""
// #objc dynamic var gasCategory : String? = ""
}
In my ViewController I have the following function:
func PopulateRealmWithFilenames() {
let fm = FileManager.default
var path = Bundle.main.resourcePath!
path += "/NBTFiles"
//let items = try! fm.contentsOfDirectory(atPath: path)
let items : [String] = try! fm.subpathsOfDirectory(atPath: path)
for item in items {
do {
print("item for realm is: \(item)")
try self.realm.write {
let newGasFile = GasFile()
newGasFile.gasFilename.append(item)
print("newGasFile written ok")
}
} catch {
print("Error writing new item to Realm \(error)")
}
}
}
This sets up the RealmDB ok, but never gets populated.
The aim of the function is to save all the filenames (not paths) of the files stored in a folder called NBTFiles.
I get the 'newGasfile written ok printed out' so it is getting into the loop.
datatype error ?
(I know I could just put it into an Array, but I want to use realm)

how do you want to add new object to realm without using adding function ?
you should use realm.add(object: newGasFile)
try this
func PopulateRealmWithFilenames() {
let fm = FileManager.default
var path = Bundle.main.resourcePath!
path += "/NBTFiles"
//let items = try! fm.contentsOfDirectory(atPath: path)
let items : [String] = try! fm.subpathsOfDirectory(atPath: path)
for item in items {
do {
print("item for realm is: \(item)")
try self.realm.write {
let newGasFile = GasFile()
newGasFile.gasFilename.append(item)
realm.add(object: newGasFile)
print("newGasFile written ok")
}
} catch {
print("Error writing new item to Realm \(error)")
}
}
}

Related

Opening a folder with ReferenceFileDocument in SwiftUI

I am trying to allow users to pick folders using the DocumentGroup context in SwiftUI, having defined a SourceFolderDocument class inheriting from ReferenceFileDocument.
However, when launching the app, only elements conforming to .sourceCode are pickable in the file picker. What would be the correct way to pick folders to open in SwiftUI?
Thank you in advance!
Here is the class in question:
final class SourceFolderDocument: ReferenceFileDocument{
static var readableContentTypes: [UTType] {[.folder, .sourceCode, .directory]}
init(configuration: ReadConfiguration) throws {
var documents : [SourceFile] = []
if(configuration.file.isDirectory){
guard let wrappers = configuration.file.fileWrappers
else{
throw CocoaError(.fileReadCorruptFile)
}
var documents : [SourceFile] = []
for document in wrappers {
if let fileContents = document.value.regularFileContents{
documents.append(SourceFile(name: document.key, contents: String(data: fileContents, encoding: .utf8)!, fileUTType: .sourceCode))
}
}
}else{
guard let data = configuration.file.regularFileContents
else{
throw CocoaError(.fileReadCorruptFile)
}
documents.append(SourceFile(name: configuration.file.filename ?? "blank", contents: String(data: data, encoding: .utf8) ?? "", fileUTType: .sourceCode))
}
self.sourceFolder = SourceFolder(name: configuration.file.filename!, documents: documents)
}
func snapshot(contentType: UTType) throws -> SourceFolder {
sourceFolder
}
func fileWrapper(snapshot: SourceFolder, configuration: WriteConfiguration) throws -> FileWrapper {
var fileWrappers: [String : FileWrapper] = [:]
for document in snapshot.documents {
fileWrappers[document.name] = FileWrapper(regularFileWithContents: document.contents.data(using: .utf8)!)
}
let fileWrapper = FileWrapper(directoryWithFileWrappers: fileWrappers)
return fileWrapper
}
init(){
sourceFolder = .defaultFolder
}
typealias Snapshot = SourceFolder
#Published var sourceFolder : SourceFolder
}
Since the menu is only using NSOpenPanel, I was about to suggest to create a small function:
fileprivate func openPanel() {
let panel = NSOpenPanel()
panel.allowsMultipleSelection = false
panel.canChooseDirectories = true
if panel.runModal() == .OK,
let url = panel.url {
Task {
try? await openDocument(at: url)
}
}
}
And then append to your view the new "Open Folder" menu:
.commandsReplaced {
CommandGroup(after: .newItem) {
Section {
Button("Open Folders") {
openPanel()
}
}
}
}
This would be creating (or replacing) the existing menus and it should also be possible to replace the "Open" menu.

Assigning MyScruct().var = results results in no assignment

Ok.. probably bad title. But here, the problem.
struct DeckView: View {
#State public var results = [ScryfallCard]()
var body: some View {
List(results, id: \.id ) { item in
Mkae a list containing the results.
}.onAppear {
ScryfallData().parseBulkData()
print("Type of results::", type(of: results))
print("results.capacity:", results.capacity)
}
}
}
struct ScryfallData {
func parseBulkData() {
let fm = FileManager.default
let path = Bundle.main.resourcePath
let items = try! fm.contentsOfDirectory(atPath: path!)
var oracleFileName = ""
for fileName in items {
if fileName .hasPrefix("oracle-cards"){
oracleFileName = fileName
}
}
print("if let savedJson = Bundle.main.url")
if let savedJson = Bundle.main.url(forResource: oracleFileName, withExtension: "") {
if let dataOfJson = try? Data(contentsOf: savedJson) {
print("if let dataOfJSON: \(dataOfJson)")
do {
let scryfallDecodeData = try JSONDecoder().decode([ScryfallCard].self, from: dataOfJson)
print("scryfallDecodeData.capacity:", scryfallDecodeData.capacity)
/* error here*/ DeckView().results = scryfallDecodeData
print("DeckView().results: ", DeckView().results)
print("Decoded data:", type(of: scryfallDecodeData))
} catch {
debugPrint("decode failed")
}
}
}
}
}
I keep getting a blank List this in the debugger...
if let dataOfJSON: 73545913 bytes
scryfallDecodeData.capacity: 24391
DeckView().results: []
Decoded data: Array<ScryfallCard>
Type of results:: Array<ScryfallCard>
results.capacity: 0
This means that oiver on the line marked Error Here, I'm asigning the decoded data to the DeckView().results var, but the end result is the data is not getting asigned. Any idea what I'm doing wrong?
You should not be creating a View from view model (ScryfallData), but instead return the decoded data from the parseBulkData function and assign that to results inside the onAppear of your View.
Your models should never know about your UI. Your UI (View in case of SwiftUI) should own the models, not the other way around. This achieves good separation of concerns and also makes your business logic platform and UI agnostic.
struct DeckView: View {
#State public var results = [ScryfallCard]()
var body: some View {
List(results, id: \.id ) { item in
Text(item.text)
}.onAppear {
self.results = ScryfallData().parseBulkData()
}
}
}
struct ScryfallData {
func parseBulkData() -> [ScryfallCard] {
let fm = FileManager.default
let path = Bundle.main.resourcePath
let items = try! fm.contentsOfDirectory(atPath: path!)
var oracleFileName = ""
for fileName in items {
if fileName .hasPrefix("oracle-cards"){
oracleFileName = fileName
}
}
if let savedJson = Bundle.main.url(forResource: oracleFileName, withExtension: "") {
do {
let jsonData = try Data(contentsOf: savedJson)
let scryfallDecodeData = try JSONDecoder().decode([ScryfallCard].self, from: jsonData)
return scryfallDecodeData
} catch {
debugPrint("decode failed")
return []
}
}
return []
}
}

SQLite.swift in SwiftUI app will not compile

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 []
}

swift element is empty

I am trying to get the first or current exercise from my core data but swift keeps telling me that the element is empty. When i run the app and set the break points the debugger shows that the element is empty but no errors. here are the functions i am using to get the element data.
func currentWorkout() -> Workout? {
let client = currentClient()
return (appointment?.workouts as? Set<Workout>)?.first(where: { $0.client == client })
}
private func currentCard() -> Card? {
return currentWorkout()?.card
}
private func currentClientPlannedExercises() -> [ExerciseInfo] {
if let currentCard = currentCard(), let template = currentCard.template, let exerciseSets = template.exerciseSets?.array as? [ExerciseSet] {
let numCardsWithThis = (template.cardsWithThisTemplate as? Set<Card>)?.filter { $0.client != currentClient() }.count ?? 0
let exercsiseSetNumber = numCardsWithThis % exerciseSets.count
if let result = exerciseSets[exercsiseSetNumber].exercises?.array as? [ExerciseInfo] {
return result
}
}
return [ExerciseInfo]()
}
private func currentExercise() -> Exercise? {
// we can't have an exercise without a selection
guard let selectedExercise = currentExerciseInfo(), let currentCard = currentCard() else{
return nil
}
// get the first exercise on the current card that has the same exercise info as the one selected
if let exercises = currentWorkout()?.exercises as? Set<Exercise>{
return exercises.first(where: { $0.exerciseInfo == selectedExercise })
}
let exercise = Exercise(context: context)
exercise.workout = currentWorkout()
exercise.exerciseInfo = selectedExercise
//TODO: Set Seat
return exercise
}
private func currentExerciseInfo() -> ExerciseInfo? {
guard let selectedRow = exercisesTableView.indexPathForSelectedRow else {
return nil
}
return currentClientPlannedExercises()[selectedRow.row]
}
if the Issue is fetching then You can use this Code:
For Fetching the data from Core Data
var tasks: [Task] = [] //Your Entity Name in Bracket
func getData() {
do {
tasks = try context.fetch(Task.fetchRequest()) //Instead of Task your Entity Name
} catch {
print("Fetching Failed")
}
}
And use it like:
for data in tasks{
print(data.name) // All the Attributes name after data.attributename
print(data.address)
}
If it is in tableView:
let data = tasks[indexPath.row]
print(data.name)
You will get the data if it is there.
Edit to Check if Data entered or not
Print the Path like this:
let paths = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true)
print(paths[0])
Go to sqlite file and open and check if there is Data or not inside that.
Edit If you are facing the issue in Adding Data to Core Data
Simple code to add Data
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let task = Task(context: context) //Entity Name here instead of Task
task.name = taskTextField.text! // Attribute name here after task.attributename
// Save the data to coredata
(UIApplication.shared.delegate as! AppDelegate).saveContext()
Hope this help.
I found the issue was in the currentExercise function it wasn't calling the first exercise until the it had an exercise. I fixed by rewriting the function
private func currentExercise() -> Exercise? {
// we can't have an exercise without a selection
guard let selectedExercise = currentExerciseInfo() else{
return nil
}
// get the first exercise on the current card that has the same exercise info as the one selected
if let exercises = currentWorkout()?.exercises as? Set<Exercise>, let firstExercise = exercises.first(where: { $0.exerciseInfo == selectedExercise }) {
return firstExercise
}
let exercise = Exercise(context: context)
exercise.workout = currentWorkout()
exercise.exerciseInfo = selectedExercise
//TODO: Set Seat
return exercise
}

Swift Remove Object from Realm

I have Realm Object that save list from the JSON Response. But now i need to remove the object if the object is not on the list again from JSON. How i do that?
This is my init for realm
func listItems (dic : Array<[String:AnyObject]>) -> Array<Items> {
let items : NSMutableArray = NSMutableArray()
let realm = try! Realm()
for itemDic in dic {
let item = Items.init(item: itemDic)
try! realm.write {
realm.add(item, update: true)
}
items.addObject(item)
}
return NSArray(items) as! Array<Items>
}
imagine your Items object has an id property, and you want to remove the old values not included in the new set, either you could delete everything with just
let result = realm.objects(Items.self)
realm.delete(result)
and then add all items again to the realm,
or you could also query every item not included in the new set
let items = [Items]() // fill in your items values
// then just grab the ids of the items with
let ids = items.map { $0.id }
// query all objects where the id in not included
let objectsToDelete = realm.objects(Items.self).filter("NOT id IN %#", ids)
// and then just remove the set with
realm.delete(objectsToDelete)
I will get crash error if I delete like top vote answer.
Terminating app due to uncaught exception 'RLMException', reason: 'Can only add, remove, or create objects in a Realm in a write transaction - call beginWriteTransaction on an RLMRealm instance first.'
Delete in a write transaction:
let items = realm.objects(Items.self)
try! realm!.write {
realm!.delete(items)
}
func realmDeleteAllClassObjects() {
do {
let realm = try Realm()
let objects = realm.objects(SomeClass.self)
try! realm.write {
realm.delete(objects)
}
} catch let error as NSError {
// handle error
print("error - \(error.localizedDescription)")
}
}
// if you want to delete one object
func realmDelete(code: String) {
do {
let realm = try Realm()
let object = realm.objects(SomeClass.self).filter("code = %#", code).first
try! realm.write {
if let obj = object {
realm.delete(obj)
}
}
} catch let error as NSError {
// handle error
print("error - \(error.localizedDescription)")
}
}
What you can do is assign a primary key to the object you are inserting, and when receiving a new parsed JSON you verify if that key already exists or not before adding it.
class Items: Object {
dynamic var id = 0
dynamic var name = ""
override class func primaryKey() -> String {
return "id"
}
}
When inserting new objects first query the Realm database to verify if it exists.
let repeatedItem = realm.objects(Items.self).filter("id = 'newId'")
if !repeatedItem {
// Insert it
}
The first suggestion that comes to mind is to delete all objects before inserting new objects from JSON.
Lear more about deleting objects in Realm at https://realm.io/docs/swift/latest/#deleting-objects
do {
let realm = try Realm()
if let obj = realm.objects(Items.self).filter("id = %#", newId).first {
//Delete must be perform in a write transaction
try! realm.write {
realm.delete(obj)
}
}
} catch let error {
print("error - \(error.localizedDescription)")
}
There is an easier option to remove 1 object:
$item.delete()
remember to have the Observable object like:
#ObservedRealmObject var item: Items
var realm = try! Realm()
open func DeleteUserInformation(){
realm.beginWrite()
let mUserList = try! realm.objects(User.self)
realm.delete(mUserList)
try! realm.commitWrite()
}