0

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.

Jules
  • 7,568
  • 14
  • 102
  • 186
  • Ahh, I've answered one of my questions, I see the bug has been fixed http://stackoverflow.com/questions/31108576/cloudkit-push-notifications-on-record-update-stopped-working – Jules Feb 26 '17 at 22:33

1 Answers1

1

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.

user3069232
  • 8,587
  • 7
  • 46
  • 87
  • Thanks that answers a lot, I see your error handing for various things, great. Does the iCloudKit framework handle local storage for us (I currently have a SqlLite database), say there's no network access and a record can not be committed ? – Jules Feb 27 '17 at 09:14
  • Local storage, unsure if I understand your question. Although I think the answer is No. You can of course use something like Core Data with it, which will someway to handling local storage within a framework. You can of course use SQL type queries within iCloud framework too so maybe that answers your question at least in part. – user3069232 Feb 27 '17 at 17:42
  • I mean, can I rely solely on the iCloud database functionality or do I need to cache data and hang on to any transactions which may have not been committed to iCloud? – Jules Feb 27 '17 at 18:11
  • Jules I have edited my answer and included a link to help you out on the last comment. The key words for looking for I think are atomic commits, something that is supported by CloudKit, but as ever with some limitations. – user3069232 Feb 28 '17 at 05:28