Save a big batch of photos using the new Photos framework?
The idea is doing it in small batches. PHPhotoLibrary::performChanges
submits change requests once all together, so even it was able to finish, you won't be able to get any updates on the progress from a delegate or block. The 2017 WWDC session "What's New in Photos APIs" came with a sample app "Creating Large Photo Libraries for Testing" that does exactly that. Each performChanges
has 10 images submitted, and UI updates are from the completion block of the performChanges
, with a Semaphore blocking the thread until one batch is processed. I'm posting the important code pieces here:
private func addPhotos() { let batchSize = min(photosToAdd, maxBatchSize) if batchSize <= 0 || !active { isAddingPhotos = false active = false return } workQueue.async { let fileURLs = self.generateImagesAndWriteToDisk(batchSize: batchSize) self.createPhotoLibraryAssets(with: fileURLs) DispatchQueue.main.async { self.addPhotos() } }}private func createPhotoLibraryAssets(with imageFileURLs: [URL]) { photoLibrarySemaphore.wait() // Wait for any ongoing photo library PHPhotoLibrary.shared().performChanges({ let options = PHAssetResourceCreationOptions() options.shouldMoveFile = true for url in imageFileURLs { let creationRequest = PHAssetCreationRequest.forAsset() creationRequest.addResource(with: .photo, fileURL: url, options: options) } }) { (success, error) in if !success { print("Error saving asset to library:\(String(describing: error?.localizedDescription))") } self.photoLibrarySemaphore.signal() }}
Above generateImagesAndWriteToDisk
is the method you need to replace with what ever your method to return a batch of 10 photo urls or so. I personally don't like writing in recursion. The code can be easily changed to non-recursion style.
instead of creationRequestForAssetFromImageAtFileURL
, I used this method and it worked perfect for 10 images (this part of code is repeated in a tableView:cellForRowAtIndexPath:
)
UIImage *thisImage=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@%@",serverInfo,self.URLs[indexPath.row]]]]]; [[PHPhotoLibrary sharedPhotoLibrary] performChanges:^{ PHAssetChangeRequest * assetReq = [PHAssetChangeRequest creationRequestForAssetFromImage:thisImage]; NSLog(@"Added %ld assets",(long)indexPath.row); } completionHandler:^(BOOL success, NSError *error) { if (!success){ NSLog(@"%@",error); }}];
Processing a large batch of images on-device you have to be very careful about memory management even in the modern days of ARC. I had a large number of images to process (50+ with resizing) and ended up choosing the CGImageSourceCreateThumbnailAtIndex() as suggested by the answer here. It uses ImageIO which is supposed to be very efficient. The only issue I had remaining is that for some reason it would still hang on to memory unless I wrapped my for-loop in an @autoreleasepool {}
for (ALAsset *asset in assets) { @autoreleasepool { resizedImage = [self thumbnailForAsset:asset maxPixelSize:JPEG_MAX_DIMENSION]; // do stuff here }}