series of cloudkit callbacks - when to popover to previous controller - swift

I have series of sequential cloud kit calls to fetch records each based on previous fetch. Any one of these fetches may fail and then I have to popover to previous controller. Since there are so many places the fetches can fail, I have to embed popViewController to previous controller in many locations. Can I avoid this and call popover only once if it is possible?
func iCloudSaveMeterHubPrivateDbCz() {
self.clOps.iCloudFetchRecord(recordName: locId, databaseScope: CKDatabaseScope.private, customZone: true, completion: { (locationRecord, error) in
guard error == nil else {
self.navigationController!.popViewController(animated: true)
return
}
self.iCloudFetchMeter(withLocationCKRecord: locationRecord!) { records, error in
if (error != nil ) {
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
}
self.navigationController!.popViewController(animated: true)
}
if let _ = records?.first {
self.clOps.iCloudFetchRecord(recordName: contactId, databaseScope: CKDatabaseScope.private, customZone: true, completion: { (contactRecord, error) in
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
self.navigationController!.popViewController(animated: true)
}
DispatchQueue.main.async {
if let record = contactRecord {
record.setObject("true" as NSString, forKey:"assignedEEP")
}
}
}
self.navigationController!.popViewController(animated: true)
}
}
})
}

Whenever I get into nested callbacks like this, I try to consolidate handling a response to a single point in the code. The popular motto of coders helps in this case: "don't repeat yourself"
Here's a suggestion for consolidating error handling and popping to a single place by making your main function have a closure:
func iCloudSaveMeter(finished: #escaping (_ success: Bool, _ error: CKError?) -> Void){
self.clOps.iCloudFetchRecord(recordName: locId, databaseScope: CKDatabaseScope.private, customZone: true, completion: { (locationRecord, error) in
if error != nil {
//:::
finished(false, error)
}
self.iCloudFetchMeter(withLocationCKRecord: locationRecord!) { records, error in
if error != nil {
//:::
finished(false, error)
}
if let _ = records?.first {
self.clOps.iCloudFetchRecord(recordName: contactId, databaseScope: CKDatabaseScope.private, customZone: true, completion: { contactRecord, error in
//:::
finished(false, error)
DispatchQueue.main.async {
if let record = contactRecord {
record.setObject("true" as NSString, forKey:"assignedEEP")
}
}
}
}
//:::
finished(true, nil)
}
})
}
//----
//Call it like this
iCloudSaveMeter(){ success, error in
//Pop
if !success{
self.navigationController!.popViewController(animated: true)
}
//Hande error
if let error = error{
self.aErrorHandler.handleCkError(ckerror: error)
}
}

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

I can't change TabBarViewController when I Have an assync request

I can't change my view until my request and to find and fill my object.
I tried to put my code assync with GCD. That don't work
override func viewDidLoad() {
getHeartStroke()
NotificationCenter.default.addObserver(forName:NSNotification.Name("HeartStrokeNotification"), object: nil, queue: nil, using: notificationFinish)
}
func getHeartStroke() {
AF.request("http://localhost:8080/heartstroke", method: .get, headers: nil).responseJSON(completionHandler: {response in
if (response.error == nil)
{
let json = JSON(response.result.value!)
DispatchQueue.global(qos: .userInitiated).async {
guard let hearstrokeArray = try? JSONDecoder().decode([HeartStroke].self, from: json.rawData()) else{
debugPrint("An error has occurred")
return
}
NotificationCenter.default.post(name:NSNotification.Name("HeartStrokeNotification"), object: hearstrokeArray, userInfo: nil)
}
}else{
NotificationCenter.default.post(name:NSNotification.Name("HeartStrokeErrorNotification"), object: nil, userInfo: nil)
}
})
}
func notificationFinish(notification:Notification) -> Void{
if (notification.name.rawValue == "HeartStrokeNotification"){
arrayHeartstroke = notification.object as! [HeartStroke]
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
With this code I stay block on my page until the end of getHeartStroke(), I expect to navigate in my app in same time of the fetch.
What you need is a completion handler to handle this. Using the notification centre is just making your life difficult and complicated and could lead to unexpected behaviour. Here is some sample code:
func getHeartStroke(completionHandler: (_ heartStroke: [HeartStroke?], _ error: NSError?) -> ()) {
AF.request("http://localhost:8080/heartstroke", method: .get, headers: nil).responseJSON(completionHandler: {response in
if (response.error == nil)
{
let json = JSON(response.result.value!)
DispatchQueue.global(qos: .userInitiated).async {
guard let hearstrokeArray = try? JSONDecoder().decode([HeartStroke].self, from: json.rawData()) else{
debugPrint("An error has occurred")
return
}
completionHandler(hearstrokeArray, nil)
}
} else {
completionHandler(nil, response.error))
}
})
}
Then you can call it like so:
getHeartStroke { [weak self] (heartStrokeArray, error) in
guard let self = self else {return}
if error != nil {
self.processError(error)
} else {
self.processHeartStroke(heartStrokeArray)
}
}
processError and processHeartStroke will be functions that you should create to handle the heartStrokeArray and error objects.
These are standard callbacks or passing functions into functions. A lot of courses you find online seem to ignore callbacks but its definitely worth your time learning about them.
You can learn more about closures (completionHandler as one is named here) here: https://docs.swift.org/swift-book/LanguageGuide/Closures.html

Escaping completion handler that takes optional argument sometimes crashes Swift

I have this code:
func function(completion: #escaping (_ error: Error?) -> Void){
getSomethingFromUrl {(result) in
guard let documentData = result.property else {
completion(nil) //crashes with error Thread 1: EXC_BREAKPOINT (code=1, subcode=0x102ba1774)
return
}
}
}
sometimes it crashes on the completion(nil) line, with the error code
Thread 1: EXC_BREAKPOINT (code=1, subcode=0x102ba1774)
I have no idea why it is crashing considering the argument is optional, and I am simply passing nil to it.
Here is the exact code that I am using to call the completion handler. It is in the completion block of a firestore transaction:
let docSizesRef = FirebaseHelper.References.firestoreReference.collection(FirestoreCollections.onlineUsers).document(FirestoreDocuments.documentSizeTracker)
FirebaseHelper.References.firestoreReference.runTransaction({ (transaction, errorPointer) -> Any? in
let docSizesDocument: DocumentSnapshot
do {
try docSizesDocument = transaction.getDocument(docSizesRef)
} catch let fetchError as NSError {
errorPointer?.pointee = fetchError
return nil
}
let oldCount = docSizesDocument.data()?[documentIdToWriteTo] as? Int ?? 0
transaction.updateData([documentIdToWriteTo: oldCount + 1], forDocument: docSizesRef)
return nil
}) { (object, error) in
completion(error)
if let error = error {
print("Transaction failed: \(error)")
} else {
print("Transaction successfully committed!")
}
}
Andhere is the code that deals with completion
func addUserToOnlineDocs(){
User.shared.managers.accountManager.addToOnlineDocs(completion: { (error) in
if let error = error{
self.createTwoButtonAlert(title: AlertErrors.Account.Titles.errorAddingToOnlineList, message: error.localizedDescription, rightButtonText: self.retryButton, leftButtonText: self.ignoreButton, rightButtonStyle: .cancel, leftButtonStyle: .default, completion: { (buttonPressed) in
if buttonPressed == self.retryButton{
connectionSetup()
}
})
return
}
self.loadingCompleteDispatchGroup.leave()
})
}
could it be to do with the transaction calling the completion handler multiple times?
Try to add ;
else { completion(nil); return }

Value of type 'AVCapturePhotoOutput' has no member 'captureStillImageAsynchronously'

Please help me to refactor this code, it does not work, I have searched in the internet but did not found any solution. I could not refactor it by myself.
This line:
imageOutput.captureStillImageAsynchronously(from: connection) { (sampleBuffer, error) -> Void in
throws this error:
Value of type 'AVCapturePhotoOutput' has no member 'captureStillImageAsynchronously'
imageOutput.captureStillImageAsynchronously(from: connection) { (sampleBuffer, error) -> Void in
if (sampleBuffer == nil || error != nil) {
DispatchQueue.main.async {
completion(nil, error)
}
return
}
guard let data = AVCapturePhotoOutput.jpegPhotoDataRepresentation(forJPEGSampleBuffer: sampleBuffer!, previewPhotoSampleBuffer: nil) else {
DispatchQueue.main.async {
completion(nil, StillImageError.noData)
}
return
}
guard let image = UIImage(data: data) else {
DispatchQueue.main.async {
completion(nil, StillImageError.noImage)
}
return
}
var saver = ImageSaver()
.onSuccess { image, assetId in
completion(assetId, nil)
}
.onFailure { error in
}
saver = saver.save(image, filter: nil)
}
captureStillImageAsynchronously(from:completionHandler:) is a function of AVCaptureStillImageOutput, not AVCapturePhotoOutput.
Since AVCaptureStillImageOutput is deprecated since iOS 10, you should use AVCapturePhotoOutput instead, like you do in your code. It has a function with the same use case: capturePhoto(with:delegate:).
For more information about the usage of AVCapturePhotoOutput, check out this question.

Swift Flow of Control (Function Calls)

I am writing this code:
func checkUserImages() {
if (self.imagesAdded.count == 0) {
self.success()
} else {
if (self.checkNetwork() == false) {
self.displayNoNetworkConnection()
} else if (self.checkUser() == false) {
print("THERE IS NO CURRENT USER")
} else {
self.progressHUD = MBProgressHUD.showAdded(to: self.view, animated: true)
self.progressHUD.label.text = "Loading"
if (self.imagesAdded.contains("Header")) {
print("CALL 1")
self.uploadImage(image: self.headerImageView.image!, imageType: "headerPicture")
}
if (self.imagesAdded.contains("Profile")) {
print("CALL 2")
self.uploadImage(image: self.profileImageView.image!, imageType: "profilePicture")
}
self.addImageLinksToDatabase()
}
}
}
func uploadImage(image: UIImage, imageType: String) {
let imageUploadData = image.mediumQualityJPEGData
storageReference.child("users").child("\(imageType)s").child("\(currentUser!.uid)\(imageType)").putData(imageUploadData, metadata: nil) { (metadata, error) in
if let error = error {
self.progressHUD.hide(animated: true)
self.displayError(title: "Error", message: error.localizedDescription)
} else {
self.imageData[imageType] = metadata?.downloadURL()?.absoluteString
}
}
}
func addImageLinksToDatabase() {
databaseReference.child("users").child(currentUser!.uid).child("userDetails").updateChildValues(self.imageData, withCompletionBlock: { (error, ref) in
if let error = error { // Checks for an error
self.progressHUD.hide(animated: true)
self.displayError(title: "Error", message: error.localizedDescription)
} else {
self.success()
}
})
}
func success() {
self.progressHUD.hide(animated: true)
self.performSegue(withIdentifier: "successfulAddPhotosSegue", sender: self)
}
It appears that the last line of code in the checkUserImages() function gets called before the the images finish uploading in the uploadImage() function. Because of this, there is no data ready for the addImageLinksToDatabase() function yet. Is this a multi-threading error and how do I go about fixing the flow so that the images upload before addImageLinksToDatabase() is called?
This is due to multi-threading. The solution is simple. Just move the last line of code from checkUserImages() to the end of uploadImage().
Eg.
func uploadImage(image: UIImage, imageType: String) {
let imageUploadData = image.mediumQualityJPEGData
storageReference.child("users").child("\(imageType)s").child("\(currentUser!.uid)\(imageType)").putData(imageUploadData, metadata: nil) { (metadata, error) in
if let error = error {
self.progressHUD.hide(animated: true)
self.displayError(title: "Error", message: error.localizedDescription)
} else {
self.imageData[imageType] = metadata?.downloadURL()?.absoluteString
}
}
self.addImageLinksToDatabase()
}
That should work perfectly.