Search code examples
swiftcompletionhandlerbackground-threadphphotolibrary

Accessing PHPhotoLibrary while app is in the background


I have a NSURLDownloadTask which is successfully downloading files in the background (large image or video files). And I successfully copy url's and call this function to save my url to photo library. As you can see, I would like to send a UILocalNotification to notify user that their download is complete.

My problem is that while the PHPhotoLibrary.sharedPhotoLibrary().performChanges will get called while app is in background , It's completion block will not. (however its called upon the app coming back into foreground) I tried commenting out grabbing the main thread to see if that helped, Which it didn't. And I don't want to send a local notification before the completion block, Because I want to tell user in notification of success/unsuccess of download.

I suppose I can send a notification in a NSURLDownloadDelegateTask method. Which will let the user know that a file was successfully downloaded, but not whether or not saving it to their photos was successful. And I'd hate to tell my user their download was successful and then them not being able to find it in their photos library.

Here is my code where I access and modify the Photos Library.

func saveURLToPhotosLibrary(url: NSURL, fileName: String) {

        if let fileExtension = url.pathExtension {
            PHPhotoLibrary.sharedPhotoLibrary().performChanges({

                let fileUnmanagedIDTag = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension, nil)

                let fileIDTag = fileUnmanagedIDTag?.takeRetainedValue()
                if let fileUTType = fileIDTag {
                    if UTTypeConformsTo(fileUTType, kUTTypeImage) {
                        PHAssetChangeRequest.creationRequestForAssetFromImageAtFileURL(url)
                    } else if UTTypeConformsTo(fileUTType, kUTTypeMovie){
                        PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(url)
                    }
                } else {
                    print("Error getting type of file from download")
                }

            }) { (success, error) in
                //dispatch_async(dispatch_get_main_queue(), {

                    if success {
                        print("finished")
                        self.sendLocalNotification(downloadSuccessful: true, error : nil, fileName: fileName)
                    } else {
                        if let error = error {
                            self.sendLocalNotification(downloadSuccessful: false, error : error, fileName: fileName)
                        }
                    }
                //})
            }
        }
    }

Solution

  • Alright found my working solution. Found out that the app is immediately suspended after not having code to run therefore app is suspended by the time my completion handler is ready to be called. I used UIApplications shared instance to create a new background task. This gives my app enough time to call the completion handler. Then I end the background task as soon as I get my notification sent out.

    func saveURLToPhotosLibrary(url: NSURL, fileName: String) {
    
        //Returns id to later be passed into method that ends task.
        let backgroundID : Int = UIApplication.sharedApplication().beginBackgroundTaskWithName("Save to Photo Library Task") {
            print("Background task expired")
        }
    
        if let fileExtension = url.pathExtension {
            PHPhotoLibrary.sharedPhotoLibrary().performChanges({
    
                let fileUnmanagedIDTag = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension, nil)
    
                let fileIDTag = fileUnmanagedIDTag?.takeRetainedValue()
                if let fileUTType = fileIDTag {
                    if UTTypeConformsTo(fileUTType, kUTTypeImage) {
                        PHAssetChangeRequest.creationRequestForAssetFromImageAtFileURL(url)
                    } else if UTTypeConformsTo(fileUTType, kUTTypeMovie){
                        PHAssetChangeRequest.creationRequestForAssetFromVideoAtFileURL(url)
                    }
                } else {
                    print("Error getting type of file from download")
                }
    
    
            }) { (success, error) in
    
                    if success {
                        print("finished")
                        self.sendLocalNotification(downloadSuccessful: true, error : nil, fileName: fileName)
                    } else {
                        if let error = error {
                            self.sendLocalNotification(downloadSuccessful: false, error : error, fileName: fileName)
                        }
                    }
                //End background task here passing in id of task from earlier.
                UIApplication.sharedApplication().endBackgroundTask(backgroundID)
            }
        }
    }