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