Create subscription for custom zone in private database fails - cloudkit

I create a custom zone in private database successfully and then try to create a subscription for that custom zone, but subscription creation fails with error: "An error indicating that some items failed, but the operation succeeded overall.".
ios document suggests using CKRecordZoneSubscription to create zone subscription. But I still get errors. How can I fix this?
var ContactZonePrivateDb: CKRecordZone = CKRecordZone(zoneName: Cloud.PrivateZone.Contact.ZoneName)
func createCustomContactInfoZone (completion: #escaping (Error?) -> Void){
let status = UserDefaults.standard.bool(forKey: Cloud.PrivateDB.CustomZoneCreated)
if status == true {
return
}
let createZoneGroup = DispatchGroup()
createZoneGroup.enter()
let createZoneOperation = CKModifyRecordZonesOperation(recordZonesToSave: [ContactZonePrivateDb], recordZoneIDsToDelete: [] )
createZoneOperation.modifyRecordZonesCompletionBlock = { (saved, deleted, error) in
if error != nil {
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
}
completion(error)
}
UserDefaults.standard.set(true, forKey: Cloud.PrivateDB.CustomZoneCreated)
self.subscribePrivateZoneContact()
completion(nil)
}
createZoneGroup.leave()
createZoneOperation.qualityOfService = .userInitiated
self.privateDB?.add(createZoneOperation)
}
func subscribePrivateZoneContact() {
let status = UserDefaults.standard.bool(forKey: Cloud.PrivateZone.Contact.SubscriptionID)
if (status == true) {
return
}
let subscriptionZone = CKRecordZoneSubscription(zoneID: ContactZonePrivateDb.zoneID)
let operation = CKModifySubscriptionsOperation(subscriptionsToSave: [subscriptionZone], subscriptionIDsToDelete: nil)
operation.modifySubscriptionsCompletionBlock = { saved, deleted, error in
guard error == nil else {
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
}
return
}
UserDefaults.standard.set(true, forKey: Cloud.PrivateZone.Contact.SubscriptionID)
DispatchQueue.main.async {
print("Successfully added Private zone subscription.\(self.ContactZonePrivateDb.zoneID)")
}
}
operation.qualityOfService = .userInitiated
self.privateDB?.add(operation)
}

Found that in order to create a subscription on custom zone, you have to create a record in the custom zone first. But it is not the case for other subscriptions. wonder why?

If you delete your subscriptions in the CloudKit Dashboard and then run your subscription creation code once, does it still show an error?
The reason I ask is because I believe you will see that error if you attempt to recreate a subscription by the same name. This is okay to do and the error can be ignored.

Related

Cannot share with UICloudSharingController; vanishes with "uploading" message

while presenting the UICloudSharingController on top of a view, it presents the screen and when I select the messages option to send a message to a person whom I want to share with, it gives a spinning wheel with "uploading" message and vanishes - attached.
However when I go to cloudkit dashboard the root record has been shared. But I cannot share it with specific person. Is it because it has shared global? How can I fix it?
self.shareInfraRecord(zoneID: appDelegate.privateContactZoneID, completion: { (status) in
if ( status == false) {
return
}
})
func shareInfraRecord(zoneID: CKRecordZone.ID, completion: #escaping(Bool) -> Void) {
if let rootRecord = self.rootRecord {
if self.rootRecord?.share == nil {
let sharingController = UICloudSharingController { (controller, preparationHandler: #escaping (CKShare?, CKContainer?, Error?) -> Void) in
let shareID = CKRecord.ID(recordName: UUID().uuidString, zoneID: zoneID)
var share = CKShare(rootRecord: rootRecord, shareID: shareID)
share[CKShare.SystemFieldKey.title] = Cloud.ShareInfrastructure.ContactShareTitleKey as CKRecordValue?
share[CKShare.SystemFieldKey.shareType] = Cloud.ShareInfrastructure.ContactShareTypeKey as CKRecordValue?
let modifyRecZoneOp = CKModifyRecordsOperation(recordsToSave:[rootRecord, share], recordIDsToDelete: nil)
modifyRecZoneOp.modifyRecordsCompletionBlock = { (records, recordID, error) in
if error != nil {
if let ckerror = error as? CKError {
if let serverVersion = ckerror.serverRecord as? CKShare {
share = serverVersion
}
completion(false)
}
}
preparationHandler(share, self.defaultContainer, error)
}
self.privateDB?.add(modifyRecZoneOp)
}
sharingController.availablePermissions = [.allowReadOnly, .allowPrivate]
sharingController.delegate = self
sharingController.popoverPresentationController?.sourceView = self.view
self.present(sharingController, animated:true, completion:nil)
} else {
let shareRecordID = rootRecord.share!.recordID
let fetchRecordsOp = CKFetchRecordsOperation(recordIDs: [shareRecordID])
fetchRecordsOp.fetchRecordsCompletionBlock = { recordsByRecordID, error in
guard error == nil, let share = recordsByRecordID?[shareRecordID] as? CKShare else {
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
//self.saveToCloudKitStatus(recordName: myRecordName, success: false)
}
completion(false)
return
}
DispatchQueue.main.async {
let sharingController = UICloudSharingController(share: share, container: self.defaultContainer!)
completion(true)
//completionHandler(sharingController)
}
}
self.privateDB?.add(fetchRecordsOp)
}
}
}
This might be a bit late but I was running into this issue too, while using NSPersistentCloudKitContainer and it seems the issue was just making sure that my iCloud container name in the Capabilities section of the settings matched my app bundle name ie iCloud.com.goddamnyouryan.MyApp

AWS Cognito GetDetails() Method not calling

I'm using AWS Cognito for my app Sign In and Sign up. In my app first the user register with email and phone number. After that, I'm redirecting to Verification Screen(Here OTP is sending by Cognito) After Verifying the OTP user will create some stores and then enter into the Dashboard. In this flow, I want to get the User details Attribute from Cognito in Verification code success. I've implemented the getDetails() method to get the userAttributes in Verification code success but it is not calling. I need the userAttributes when the time of store creation. Any help appreciated.
Here is my code:
#IBAction func submitButtonAction(_ sender: GradientButton) {
let code = firstChar+secondChar+thirdChar+fourthChar+fifthChar+sixthChar
guard code.count == 6 else{
self.showAlert(message: ErrorMessages.kEnterValidOTP)
return
}
let currentUser = self.userPool?.getUser("xxxx#gmail.com")
currentUser?.confirmSignUp(code, forceAliasCreation: true).continueWith(block: { [weak self] (task) -> Any? in
guard let strongSelf = self else { return nil }
DispatchQueue.main.async {
if let error = task.error as NSError? {
if let message = error.userInfo["message"] as? String{
self?.showAlert(message: message, onOkAction: {
strongSelf.clearTextFieldData()
})
}else{
self?.showAlert(message: error.localizedDescription, onOkAction: {
strongSelf.clearTextFieldData()
})
}
}else{
print(task.result)
strongSelf.clearTextFieldData()
print(AWSUserDetails.shared.userPool.currentUser()?.username)
let user = AWSUserDetails.shared.userPool.currentUser()
//I've tried the above `user` and `currentUser`. But not working.
user?.getDetails().continueOnSuccessWith(block: { (task) -> Any? in
DispatchQueue.main.async {
if task.error == nil{
print(task.error)
}else{
print(task.result)
}
}
})
// strongSelf.performSegue(withIdentifier: SegueIdentifiers.createStoreSegue, sender: self)
}
}
return nil
})
}

Is there a way to use Firebase with a ShareExtension without AppGroups

I'm setting up a shareExtension in iOS and want to use the FirebaseSDK to upload data direct instead of using AppGroups. This works as expected, but after 1 hour the UserToken get's invalidated and i can't reach the Firestore Backend anymore.
I'm using the FirebaseSDK (6.2.0) and enabled Keychain sharing to access the current signedIn User. I have the same Google-Plist in the MainApp and the shareExtension. The data gets also uploaded correctly from the shareExtension and was also updated via the snapshotListener in the MainApp.
Relevant code in the MainApp
lazy var db = Firestore.firestore()
//TEAMID form the Apple Developer Portal
let accessGroup = "TEAMID.de.debug.fireAuthExample"
override func viewDidLoad() {
super.viewDidLoad()
do {
try Auth.auth().useUserAccessGroup("\(accessGroup)")
} catch let error as NSError {
print("Error changing user access group: %#", error)
}
guard let user = Auth.auth().currentUser else {
self.statusLabel.text = "user get's lost"
return
}
statusLabel.text = "UserID: \(user.uid)"
// Do any additional setup after loading the view.
db.collection("DummyCollection").addSnapshotListener { (querySnapshot, error) in
if let err = error {
print(err.localizedDescription)
}
guard let snapshot = querySnapshot else {
return
}
DispatchQueue.main.async {
self.dbCountLabel.text = "\(snapshot.count)"
}
}
}
func signIN(){
// https://firebase.google.com/docs/auth/ios/single-sign-on
do {
try Auth.auth().useUserAccessGroup("\(accessGroup)")
} catch let error as NSError {
print("Error changing user access group: %#", error)
}
Auth.auth().signInAnonymously { (result, error) in
if let err = error{
print(err.localizedDescription)
return
}
print("UserID: \(Auth.auth().currentUser!.uid)")
}
}
}
}
Code in the shareExtension:
override func viewDidLoad() {
super.viewDidLoad()
if FirebaseApp.app() == nil {
FirebaseApp.configure()
}
do {
try Auth.auth().useUserAccessGroup(accessGroup)
} catch let error as NSError {
print("Error changing user access group: %#", error)
}
tempUser = Auth.auth().currentUser
if tempUser != nil {
userIDLabel.text = "UserID: \(tempUser!.uid)"
doneButton.isEnabled = true
db.collection("DummyCollection").addSnapshotListener { (querySnapshot, error) in
if let err = error {
print(err.localizedDescription)
}
guard let snapshot = querySnapshot else {
return
}
DispatchQueue.main.async {
self.dataCountLabel.text = "\(snapshot.count)"
}
}
} else {
// No user exists in the access group
self.navigationItem.title = "No User"
}
}
I expect that this should be possible, but the Token gets somehow invalid in the MainApp and i could not reach the Firestore backend.
6.2.0 - [Firebase/Auth][I-AUT000003] Token auto-refresh re-scheduled in 01:00 because of error on previous refresh attempt.
6.2.0 - [Firebase/Firestore][I-FST000001] Could not reach Cloud Firestore backend. Connection failed 1 times. Most recent error: An internal error has occurred, print and inspect the error details for more information.
Answering my own question: This should be fixed in the next release (Firebase 6.4.0) Details can be found in this PR 3239.

update firebase database from current user

I want to store image in firebase storage and create url than update value from my current database in firebase, but it didn't store in firebase database from current user. this is my code, where I do wrong?
fileprivate func saveToFirebase(image: UIImage) {
guard let imageData = image.jpegData(compressionQuality: 0.3) else { return }
let uuidName = UUID().uuidString
Storage.storage().reference().child("profile_images").child(uuidName).putData(imageData, metadata: nil) { (metadata, error) in
if let err = error {
print("💥 -- Failed to upload images -- 💥")
print("💥 -- \(err) -- 💥")
return
}
metadata?.storageReference?.downloadURL(completion: { (url, error) in
if let err = error {
print("💥 -- Failed to create URL -- 💥")
print("💥 -- \(err) -- 💥")
return
}
guard let profileImageURL = url?.absoluteString else { return }
guard let currentUID = Auth.auth().currentUser?.uid else { return }
let userProfileURL = ["profileImageURL": profileImageURL]
Database.database().reference().child("users").child(currentUID).updateChildValues(userProfileURL, withCompletionBlock: { (error, reference) in
if let err = error {
print("💥 -- Failed to create URL -- 💥")
print("💥 -- \(err) -- 💥")
return
}
print("✅ -- Successfully update user data -- ✅")
})
})
}
}
Firstly we can create an enum to represent the different failures that could occur. Some scenarios we consider to be errors (such as a guard else returning void, or a broken chained optional) fail silently. Here we can encapsulate the different failure scenarios that need to be handled.
enum ProfileImageUploadError: Error {
case unauthenticatedUser
case invalidImageData
case failedDataUpload(Error?)
case failedDownloadURL(Error?)
case failedProfileUpdate(Error?)
var localizedDescription: String {
switch self {
case .failedDataUpload: return "Failed to upload data"
case .failedDownloadURL: return "Failed to download URL"
case .failedProfileUpdate: return "Failed to update profile"
default: return "\(self)"
}
}
var underlyingError: Error? {
switch self {
case .failedDataUpload(let err): return err
case .failedDownloadURL(let err): return err
case .failedProfileUpdate(let err): return err
default: return nil
}
}
}
Next, we can immediately determine that we have an authenticated user and that the image data checks out. When a guard fails, we call the completion block passing the error case for that scenario. Keeping this up, we construct our references to the storage and database providers, and attempt the sequence catching errors.
Given there is no error initially uploading the data, we can assume the image has been uploaded. Also rather than using the optional metadata, we can use the storage ref we constructed earlier to download the URL.
As we continue through the sequence of operations, we're trying to sufficiently handle what we consider to be the errors, until successful completion upon which point we can return the URL saved to Firebase Database.
func uploadProfileImage(_ image: UIImage, completion: #escaping (URL?, ProfileImageUploadError?) -> ()) {
guard let currentUser = Auth.auth().currentUser else {
return completion(nil, .unauthenticatedUser)
}
guard let imageData = image.jpegData(compressionQuality: 0.3) else {
return completion(nil, .invalidImageData)
}
let storagePath = "profile_images/\(UUID().uuidString)"
let databasePath = "users/\(currentUser.uid)/profileImageURL"
let profileImageDataRef = Storage.storage().reference(withPath: storagePath)
let profileImageURLRef = Database.database().reference(withPath: databasePath)
profileImageDataRef.putData(imageData, metadata: nil) { (metadata, error) in
guard error == nil else {
return completion(nil, .failedDataUpload(error))
}
profileImageDataRef.downloadURL { (url, error) in
guard let profileImageURL = url?.absoluteString else {
return completion(nil, .failedDownloadURL(error))
}
profileImageURLRef.setValue(profileImageURL, withCompletionBlock: { (error, ref) in
guard error == nil else {
return completion(nil, .failedProfileUpdate(error))
}
completion(url, nil)
})
}
}
}
Lastly, this is how you would use the function inside your existing one.
fileprivate func saveToFirebase(image: UIImage) {
uploadProfileImage(image) { (url, error) in
if let error = error {
print("💥 -- \(error.localizedDescription) -- 💥")
print("💥 -- \(error.underlyingError.debugDescription) -- 💥")
} else {
print("✅ -- Successfully update user data -- ✅")
print("✅ -- \(url.debugDescription) -- ✅")
}
}
}
This is not tested
So to recap, some of the lines in your function can 'fail' silently, this can be resolved by sufficiently handling the 'optionals' and errors with completions or simply print statements. I think it is the chain of optional properties leading up to the URL download causing the issue – specifically the metadata property in metadata?.storageReference?.downloadURL.
Try with this..
useruid is firbase uid
let storageRef = Storage.storage().reference().child("profile_images").child(useruid)
storageRef.putData(imagedata, metadata: nil, completion: { (metadatas, error) in
if error != nil {
print("Couldn't Upload Image")
} else {
print("Uploaded")
print(metadatas!)
storageRef.downloadURL(completion: { (url, error) in
if error != nil {
print(error!)
return
}
if url != nil {
let imageurl = String(describing: url!)
print(imageurl) }
})
}
})

Works fine when healthkit access was previously authorized on device but crashes otherwise

override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
let healthKitTypes: Set = [
// access step count
HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!
]
healthStore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (_, _) in
print("authorized???")
}
healthStore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (bool, error) in
if let e = error {
print("oops something went wrong during authorization \(e.localizedDescription)")
} else {
print("User has completed the authorization flow")
}
}
getTodaysSteps { (result) in
print("\(result)")
self.steps = result
DispatchQueue.main.async {
if result == 0 {
self.StepDisplay.text = " You haven't walked"
} else {
self.StepDisplay.text = "\(result)"
}
}
}
getStepHistory()
}
func getStepHistory() {
let calendar = Calendar.current
var interval = DateComponents()
interval.day = 1
// Set the anchor date to Monday at 3:00 a.m.
var anchorComponents = calendar.dateComponents([.day, .month, .year, .weekday], from: Date())
let offset = (7 + (anchorComponents.weekday ?? 0) - 2) % 7
anchorComponents.day = (anchorComponents.day ?? 0) - offset
anchorComponents.hour = 0
anchorComponents.minute = 1
guard let anchorDate = calendar.date(from:anchorComponents) else {
fatalError("*** unable to create a valid date from the given components ***")
}
guard let quantityType = HKObjectType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount) else {
fatalError("*** Unable to create a step count type ***")
}
This code works fine when the authorization is already given on the device. If however, it was not authorized earlier, it will not work unless getStepHistory() is commented out in viewDidLoad. I tried requesting additional authorization from within the getStepHistory() function but it doesn't solve the problem
You need to call getStepHistory inside the completion block to requestAuthorization if it has been authorized.
healthStore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (success, error) in
if let e = error {
print("oops something went wrong during authorization \(e.localizedDescription)")
} else if success {
print("User has granted access")
getStepHistory()
} else {
print("User has completed the authorization flow but there is no access")
}
}
Requesting the User's Permission
To request authorization, we invoke
requestAuthorization(toShare:,readTypes:,completion:) on the
HKHealthStore instance. This method accepts three parameters:
an optional set of HKSampleType objects
an optional set of HKObjectType objects
a completion handler with two parameters, a boolean indicating the result of the authorization request (successful or unsuccessful) and an optional error
It is important to understand that the boolean of the completion handler does not indicate whether the user granted or denied access to the requested health data types. It only informs the application whether the user responded to the application's authorization request. If the user dismissed the form by canceling the authorization request, the boolean of the completion handler is set to false.
In Your View Did Load :
healthStore.requestAuthorization(toShare: healthKitTypes, read: healthKitTypes) { (success, error) in
if let err = error {
print("Error \(err.localizedDescription)")
} else if success {
// Get the Step Count....
getStepHistory()
} else {
print("No access to healthkit data")
}
}
Optionally You can try this function to get step count:
let healthStore = HKHealthStore()
func getTodaysSteps(completion: #escaping (Double) -> Void) {
let stepsQuantityType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
let now = Date()
let startOfDay = Calendar.current.startOfDay(for: now)
let predicate = HKQuery.predicateForSamples(withStart: startOfDay, end: now, options: .strictStartDate)
let query = HKStatisticsQuery(quantityType: stepsQuantityType, quantitySamplePredicate: predicate, options: .cumulativeSum) { _, result, _ in
guard let result = result, let sum = result.sumQuantity() else {
completion(0.0)
return
}
completion(sum.doubleValue(for: HKUnit.count()))
}
healthStore.execute(query)
}