ioscocoa-touchicloudicloud-api

iOS, How to handle post notification request(s) for iCloudKit Transactions?


Firstly, what if the user denies the request to accept push notifications, how can this be evaluated.

From what I've read it seems that there was a problem with silent notifications, although I think it was a beta, has this issue been rectified.

I'm developing a kids app, when I call registerForRemoteNotifications to capture silent notifications, will this show the notification permission message.

I'm also wondering what happens with the iCloud database setup if the user only has internet access infrequently. I mean I guess they must have when the app is first downloaded, but if they never run the app after it's been downloaded how will they get the database schema? Is this something we would have to handle and ask the user to connect to the internet?

I know a lot of this is hypothetical, but obviously we would need to capture and handle this.

I'd also be grateful if anyone could point out and useful pods which may be helpful.


Solution

  • Jules,

    When you create a record with an iCloud enabled app you do so using a piece of code much like this one.

    func files_saveNotes(rex: Int) {
         var localChanges:[CKRecord] = []
    
            let newRecordID = CKRecordID(recordName: sharedDataAccess.returnRexID(index2seek: rex))
            let newRecord = CKRecord(recordType: "Blah", recordID: newRecordID)
    
            let theLinkID = CKReference(recordID: sharedDataAccess.iCloudID, action: .deleteSelf)
            let thePath = sharedDataAccess.fnGet(index2seek: rex)
            newRecord["theLink"] = theLinkID
            newRecord["theBLAHnumber"] = rex as CKRecordValue?
            newRecord["theBLAHpath"] = thePath as CKRecordValue?
    
        localChanges.append(newRecord)
        let records2Erase:[CKRecordID] = []
    
        let saveRecordsOperation = CKModifyRecordsOperation(recordsToSave: localChanges, recordIDsToDelete: records2Erase)
        saveRecordsOperation.savePolicy = .allKeys
        saveRecordsOperation.perRecordCompletionBlock =  { record, error in
            if error != nil {
                //print(error!.localizedDescription)
            }
            // deal with conflicts
            // set completionHandler of wrapper operation if it's the case
        }
        saveRecordsOperation.modifyRecordsCompletionBlock = { savedRecords, deletedRecordIDs, error in
            self.theApp.isNetworkActivityIndicatorVisible = false
    
            guard error == nil else {
                if let ckerror = error as? CKError {
                    if ckerror.code == CKError.requestRateLimited {
                        let retryInterval = ckerror.userInfo[CKErrorRetryAfterKey] as? TimeInterval
                        DispatchQueue.main.async {
                            Timer.scheduledTimer(timeInterval: retryInterval!, target: self, selector: #selector(self.files_saveNotes), userInfo: nil, repeats: false)
                        }
                    } else if ckerror.code == CKError.zoneBusy {
                        let retryInterval = ckerror.userInfo[CKErrorRetryAfterKey] as? TimeInterval
                        DispatchQueue.main.async {
                            Timer.scheduledTimer(timeInterval: retryInterval!, target: self, selector: #selector(self.files_saveNotes), userInfo: nil, repeats: false)
                        }
                    } else if ckerror.code == CKError.limitExceeded {
                        let retryInterval = ckerror.userInfo[CKErrorRetryAfterKey] as? TimeInterval
                        DispatchQueue.main.async {
                            Timer.scheduledTimer(timeInterval: retryInterval!, target: self, selector: #selector(self.files_saveNotes), userInfo: nil, repeats: false)
                        }
                    } else if ckerror.code == CKError.notAuthenticated {
                        NotificationCenter.default.post(name: Notification.Name("noCloud"), object: nil, userInfo: nil)
                    } else if ckerror.code == CKError.networkFailure {
                        NotificationCenter.default.post(name: Notification.Name("networkFailure"), object: nil, userInfo: nil)
                    } else if ckerror.code == CKError.networkUnavailable {
                        NotificationCenter.default.post(name: Notification.Name("noWiFi"), object: nil, userInfo: nil)
                    } else if ckerror.code == CKError.quotaExceeded {
                        NotificationCenter.default.post(name: Notification.Name("quotaExceeded"), object: nil, userInfo: nil)
                    } else if ckerror.code == CKError.partialFailure {
                        NotificationCenter.default.post(name: Notification.Name("partialFailure"), object: nil, userInfo: nil)
                    } else if (ckerror.code == CKError.internalError || ckerror.code == CKError.serviceUnavailable) {
                        NotificationCenter.default.post(name: Notification.Name("serviceUnavailable"), object: nil, userInfo: nil)
                    }
                } // end of guard statement
                return
            }
            if error != nil {
                print(error!.localizedDescription)
            } else {
                print("ok \(savedRecords)")
            }
        }
    
        saveRecordsOperation.qualityOfService = .background
        privateDB.add(saveRecordsOperation)
        theApp.isNetworkActivityIndicatorVisible = true
    }
    

    Now forgive me there is a lot in here, and I haven't the time to explain in it all in detail, but the record scheme comes from CKRecord creation call 4th line in this code, the record type called "Blah" in this case.

    The fields within it [the schema], you need to detail in your code as I do here, so we have theLink, the BLAHnumber and the BLAHpath in this case. It doesn't download this information and indeed once you go into production, you cannot change it. So new schemas need to be new record types and you need to take care with field updates to current ones that you make sure your app is backward. compatible. Hope that helps make things a little clearer.

    This article https://www.shinobicontrols.com/blog/ios8-day-by-day-day-33-cloudkit talks in a great deal more detail about the whole subject. Atomic commits, one of your questions are mentioned here in particular.