Home > Blockchain >  How to create a local Object from a CKRecord and append it to an array for NSTableView data
How to create a local Object from a CKRecord and append it to an array for NSTableView data

Time:03-17

I'm working on a note app project for macOS with cloud capabilities, particularly downloading another person's note given the notes "CloudID" which is just the unique name which the cloud database gives the record when the note is uploaded to the cloud. I'm using an sqlite database for local data storage and a cloudKit database for public data storage. When I test the download feature it adds the data to the sqlite DB correctly but the local object which I append to my notesArray which is used for populating the NSTableView in my home page doesn't get modified.

Here's the code:

@IBAction func downloadNoteFromCloud(_ sender: Any) {
    // 1.) check to make sure text field isn't empty
    if (noteIDTextField.stringValue == "") {
        noteIDTextField.stringValue = "Please enter the desired note's cloudID"
    }
    
    // 2.) query the note with the given cloudID
    
    // make note object for the downloaded note bc of scope issues
    var downloadedNote = Note(0, "", "")
    
    //sets cloudDB to the public cloud database
    let container = CKContainer.default()
    let cloudDB = container.publicCloudDatabase
    
    let noteRecordID = CKRecord.ID(recordName: noteIDTextField.stringValue)
    
    // returns a CKRecord
    cloudDB.fetch(withRecordID: noteRecordID) { record, error in
        if let error = error {
            // Handle error
            print(error)
            return
        }
        // Record fetched successfully
        if let noteRecord = record {
            // set the values of the downloadedNote obj equal the the values of the noteRecord
            downloadedNote.setValues((notesArray.count), noteRecord["Title"] as! String, noteRecord["Body"] as! String)
            
            // add to local database ~ this works
            do {
                let path = NSSearchPathForDirectoriesInDomains(
                    .applicationSupportDirectory, .userDomainMask, true
                ).first!   "/"   Bundle.main.bundleIdentifier!

                // create parent directory iff it doesn’t exist
                try FileManager.default.createDirectory(
                atPath: path, withIntermediateDirectories: true, attributes: nil
                )
                
                //connect to DB
                let db = try Connection("\(path)/db.sqlite3")
                //Define the Notes Table and its Columns
                let notes = Table("Notes")
                //ID is primary key so it doesn't need to be defined for inserts
                let title = Expression<String>("Title")
                let body = Expression<String>("Body")
                
                try db.run(notes.insert(title <- downloadedNote.title, body <- downloadedNote.body))
            }
            catch {
                print(error)
            }
        }
    }
    // append downloadedNote to notesArray
    notesArray.append(downloadedNote)
    
    //Send Notification to reload the tableView data
    NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)
    
    //Return to home page
    self.view.window?.close()
}

I'm assuming it's a scope issue and I've tried moving the append func around and I've tried making the original downloadedNote declaration be an optional type which isn't initialized like so: var downloadedNote: Note? and then initializing it inside of the fetch func like so: downloadedNote = Note(id: notesArray.count, title: noteRecord["Title"] as! String, body: noteRecord["Body"] as! String) none of which have worked correctly. The closest I've gotten it to work is how the code is now. Please let me know where I went wrong and how to fix it. Thank you very much for any help!

CodePudding user response:

CloudKit works asynchronously. Move the code to notify and reload the table into the closure

Replace

           try db.run(notes.insert(title <- downloadedNote.title, body <- downloadedNote.body))
        }
        catch {
            print(error)
        }
    }
}
// append downloadedNote to notesArray
notesArray.append(downloadedNote)

//Send Notification to reload the tableView data
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)

//Return to home page
self.view.window?.close()

with

           try db.run(notes.insert(title <- downloadedNote.title, body <- downloadedNote.body))

           DispatchQueue.main.async {
               // append downloadedNote to notesArray
               notesArray.append(downloadedNote)

               //Send Notification to reload the tableView data
               NotificationCenter.default.post(name: NSNotification.Name(rawValue: "load"), object: nil)

               //Return to home page
               self.view.window?.close()
           }
        }
        catch {
            print(error)
        }
    }
}

And create also the new note inside the closure. There are no scope issues anymore.


Side note:

NSSearchPathForDirectoriesInDomains smells objective-c-ish and is outdated. Use the FileManager URL related API

do {
    let fm = FileManager.default
    let applicationSupportURL = try fm.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
    let applicationSupportBundleURL = applicationSupportURL.appendingPathComponent(Bundle.main.bundleIdentifier!)
    
    if !fm.fileExists(atPath: applicationSupportBundleURL.path) {
        // create parent directory iff it doesn’t exist
        try fm.createDirectory(at: applicationSupportBundleURL, withIntermediateDirectories: true, attributes: nil)
    }
    let dbPath = applicationSupportBundleURL.appendingPathComponent("db.sqlite3").path
    
    //connect to DB
    let db = try Connection(dbPath)
    ...
  • Related