Episode #277

CloudKit Notes Manager Continued

Series: Hello CloudKit

14 minutes
Published on June 22, 2017

This video is only available to subscribers. Get access to this video and 584 others.

We finish the CloudKitNotesManager by providing a generic save and delete methods that we can use for any CKRecordWrapper type. We also implement a custom notification when a note is saved so that we can update an interested view controllers to update their UI.

new folder

New Folder Function

func newFolder(name: String) -> Folder {
  return CloudKitFolder(name: name)
}

Saving Records Generically

fun save<R: CKRecordWrapper>(record: R, 
    conversion: @escaping (T) -> R, 
    completion: @escaping OperationCompletionBlock<T>) {

    let modifyOp = CKModifyRecordsOperation(recordsToSave: [record.record], recordIDsToDelete: nil)
        modifyOp.modifyRecordsCompletionBlock = { savedRecords, deletedRecords, error in
            if let e = error {
                print("Error saving folder: \(e)")
                completion(.error(e))
            }
            if let savedRecord = savedRecords?.first {
                let result = R(record: savedRecord)
                completion(.success(conversion(result)))
            }
        }
    database.add(modifyOp)
}

We can use this to save a note like this:

func save(note: Note, completion: @escaping (Result<Note>) -> Void) {
    guard let note = note as? CloudKitNote else { fatalError("must pass in a CloudKitNote") }
    save(record: note, conversion: { $0 }) { result in

        switch result {
        case .success(let savedNote):
            NotificationCenter.default.post(name: .NoteWasUpdated, object: savedNote)
            completion(.success(savedNote))
        case .error(let e):
            completion(.error(e))
        }
    }
}

Here we post a notification when a note is saved so any view controllers that are displaying the note can update their cnotents with the new note that is returned by the server.

Deleting Records Generically

private func delete<R:CKRecordWrapper>(record: R, completion: @escaping OperationCompletionBlock<Bool>) {
    let modifyOp = CKModifyRecordsOperation(recordsToSave: nil, recordIDsToDelete: [record.record.recordID])
    modifyOp.modifyRecordsCompletionBlock = { saved, deletedIds, error in
        if let e = error {
            print("Error deleting record: \(e)")
            completion(.error(e))
        } else {
            let deletedCount = deletedIds?.count ?? 0
            completion(.success(deletedCount > 0))
        }
    }
    database.add(modifyOp)
}

We can the change our delete(folder:) method to use this:

func delete(folder: Folder, completion: @escaping (Result<Bool>) -> Void) {
    guard let folder = folder as? CloudKitFolder else { fatalError("must pass in a CloudKitFolder") }
    delete(record: folder, completion: completion)
}

Fetching Notes in a Folder

func fetchNotes(for folder: Folder, completion: @escaping (Result<[Note]>) -> Void) {
    guard let folder = folder as? CloudKitFolder else { fatalError("must pass in a CloudKitFolder") }

    let inFolderPredicate = NSPredicate(format: "folder == %@", CKReference(recordID: folder.record.recordID, action: .deleteSelf))
    let sortDescriptors = [NSSortDescriptor(key: "modificationDate", ascending: false)]

    query(predicate: inFolderPredicate,
          sortDescriptors: sortDescriptors,
          conversion: { (note: CloudKitNote) -> Note in note },
          completion: completion)
}

This episode uses Ios 10.3, Xcode 8.3.