Search code examples
swiftintegerintcloudkit

how to save and load a number (integer) using CloudKit (swift)


I am creating an app that needs to save a counter variable (which is an integer) into the cloud when the app exits, then loads the counter when the app becomes active. Ive never used CloudKit before could someone simplify how i could do this using swift? many of the examples I've tried to replicate are too complex for what I am trying to achieve.

Note: Before anyone mentions it , I know there are other ways to achieve this but I want to do it using CloudKit.

Also, I already understand how appDelegate transitions work so i don't need help with that :)


Solution

  • CloudKit: Ok lets do some planning and get out assumptions agreed.

    1. You need to check the network is up and reachable
    2. You need check said user is logged into the cloud

    Unclear as to the nature of what your really want to do here beyond writing a noddy method; lets assuming you want something a bit more.

    1. You save your integer using cloud kit, ensuring any errors that come thru are handled. What sort of errors. Here a list for you.

    enum CKErrorCode : Int { case InternalError case PartialFailure case NetworkUnavailable case NetworkFailure case BadContainer case ServiceUnavailable case RequestRateLimited case MissingEntitlement case NotAuthenticated case PermissionFailure case UnknownItem case InvalidArguments case ResultsTruncated case ServerRecordChanged case ServerRejectedRequest case AssetFileNotFound case AssetFileModified case IncompatibleVersion case ConstraintViolation case OperationCancelled case ChangeTokenExpired case BatchRequestFailed case ZoneBusy case BadDatabase case QuotaExceeded case ZoneNotFound case LimitExceeded case UserDeletedZone }

    1. You might want to read the thing back to check it even if you don't get any errors, it is a very important integer. You need to handle these errors if you do that too.

    OK, you saved it; what about next time. Ok its the same palaver, network, cloud kit, read, deal with errors etc etc.

    If your still here, well done. Here the code just to save a record.

    func save2Cloud(yourInt:Int) {
    
        let container = CKContainer(identifier: "iCloud.blah")
        let publicDB = container.publicCloudDatabase
    
        let newRecord = CKRecord(recordType: "BlahBlah")
        newRecord.setObject(yourInt, forKey: "theInt")
    
        var localChanges:[CKRecord] = []
        var recordIDsToDelete:[CKRecord] = []
    
        localChanges.append(newRecord)
    
        let saveRecordsOperation = CKModifyRecordsOperation(recordsToSave: localChanges, recordIDsToDelete: nil)
        saveRecordsOperation.perRecordCompletionBlock =  { record, error in
            if error != nil {
                self.showAlert(message: error!.localizedDescription)
                print(error!.localizedDescription)
            }
            dispatch_async(dispatch_get_main_queue()) {
                    // give the UI a all good sign for that record
                }
        }
        saveRecordsOperation.modifyRecordsCompletionBlock = { savedRecords, deletedRecordIDs, error in
            if error != nil {
                self.showAlert(message: error!.localizedDescription)
                print(error!.localizedDescription)
            } else {
                 dispatch_async(dispatch_get_main_queue()) {
                    // give the UI a all good sign for all records
                }
            }
        }
        saveRecordsOperation.qualityOfService = .Background
        publicDB.addOperation(saveRecordsOperation)
    }
    

    And here the code to read it back.

    var readerOperation: CKQueryOperation!
    
    func read4Cloud(theLink: String, theCount: Int) {
        var starCount:Int = 0
        let container = CKContainer(identifier: "iCloud.blah")
        let publicDB = container.publicCloudDatabase
        let predicate = NSPredicate(value: true)
        let query = CKQuery(recordType: "BlahBlah", predicate: predicate)
    
        readerOperation = CKQueryOperation(query: query)
        readerOperation.recordFetchedBlock = { (record) in
            let YourInt = record["theInt"] as! Int
        }
    
        readerOperation.queryCompletionBlock = {(cursor, error) in
        if error != nil {
                // oh dingbats, you need to check for one of those errors
        } else {
           // got it
        }
        }
        readerOperation.qualityOfService = .Background
        publicDB.addOperation(readerOperation)
    }
    

    But wait Matt, this is going to save a new record everytime, and read back multiple Ints when you re-open. No this solution needs some more work; and I haven't done the network or the cloud check or any of the errors... :\

    Disclaimer; I edited this code in SO, it may not compile cleanly :)