How to generate thumbnails of images on iCloud Drive?
Asked Answered
B

4

6

This appears easy, but the lack of documentation makes this question impossible to guess.

I have pictures and videos on my app's icloud drive and I want to create thumbnails of these assets. I am talking about assets on iCloud Drive, not the iCloud photo stream inside the camera roll. I am talking about the real iCloud Drive folder.

Creating thumbnails from videos are "easy" compared to images. You just need 2 weeks to figure out how it works, having in mind the poor documentation Apple wrote but thumbnails from images seem impossible.

What I have now is an array of NSMetadataItems each one describing one item on the iCloud folder.

These are the methods I have tried so far that don't work:


METHOD 1

[fileURL startAccessingSecurityScopedResource];

NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init];
__block NSError *error;

[coordinator coordinateReadingItemAtURL:fileURL
                                options:NSFileCoordinatorReadingImmediatelyAvailableMetadataOnly
                                  error:&error
                             byAccessor:^(NSURL *newURL) {
                               NSDictionary *thumb;
                               BOOL success = [newURL getResourceValue:&thumb forKey:NSURLThumbnailDictionaryKey error:&error];

                               UIImage *thumbnail = thumb[NSThumbnail1024x1024SizeKey];



                             }];

[fileURL stopAccessingSecurityScopedResource];

The results of this method are fantastic. Ready for that? Here we go: success = YES, error = nil and thumbnail = nil.


ANOTHER METHOD

AVURLAsset *asset = [[AVURLAsset alloc] initWithURL:fileURL
                                            options:nil];

AVAssetImageGenerator *imageGenerator = [[AVAssetImageGenerator alloc] initWithAsset:asset];

imageGenerator.appliesPreferredTrackTransform = YES;

CMTime time = CMTimeMake(0, 60); // time range in which you want
NSValue *timeValue = [NSValue valueWithCMTime:time];

[imageGenerator generateCGImagesAsynchronouslyForTimes:@[timeValue] completionHandler:^(CMTime requestedTime, CGImageRef image, CMTime actualTime, AVAssetImageGeneratorResult result, NSError * error) {

  thumbnail = [[UIImage alloc] initWithCGImage:image];

}];

error = The requested URL was not found on this server. and thumbnail = nil

This method appears to be just for videos. I was trying this just in case. Any equivalent of this method to images?


PRIMITIVE METHOD

NSData *tempData = [NSData dataWithContentsOfUrl:tempURL];

NOPE - data = nil


METHOD 4

The fourth possible method would be using ALAsset but this was deprecated on iOS 9.

I think that all these methods fail because they just work (bug or not) if the resource is local. Any ideas on how to download the image so I can get the thumbnail?

Any other ideas?

thanks


EDIT: after several tests I see that Method 1 is the only one that seems to be in the right direction. This method works poorly, sometimes grabbing the icon but most part of the time not working.


Another point is this. Whatever people suggests me, they always say about downloading the whole image to get the thumbnail. I don't think this is the way to go. Just see how getting thumbnails of video work. You don't download the whole video to get its thumbnail.

So this question remains open.

Blitzkrieg answered 10/9, 2015 at 2:38 Comment(2)
Have you looked at nshipster.com/phimagemanager – Histiocyte
yes but this article talks about PHAssets. PH means the Photos Framework and I think this framework cannot retrieve stuff from iCloud Drive. I am talking about iCloud assets. I am not sure how these two relates. What I have from iCloud is a bunch of NSMetadataItems. If you know how to transform a NSMetadataItem into an PHAsset item I am all years. – Blitzkrieg
B
2

After testing several solutions, the one that seems to work better is this one:

[fileURL startAccessingSecurityScopedResource];

NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init];
__block NSError *error;

[coordinator coordinateReadingItemAtURL:fileURL
                                options:NSFileCoordinatorReadingImmediatelyAvailableMetadataOnly
                                  error:&error
                             byAccessor:^(NSURL *newURL) {
                               NSDictionary *thumb;
                               BOOL success = [newURL getResourceValue:&thumb forKey:NSURLThumbnailDictionaryKey error:&error];

                               UIImage *thumbnail = thumb[NSThumbnail1024x1024SizeKey];



                             }];

[fileURL stopAccessingSecurityScopedResource];

This solution is not perfect. It will fail to bring the thumbnails sometimes but I was not able to find any other solution that works 100%. Others are worst than that.

Blitzkrieg answered 21/9, 2015 at 8:11 Comment(0)
I
3

The Photos-Framework or AssetsLibrary will not work here as you would have to import your iCloud Drive Photos first to the PhotoLibrary to use any methods of these two frameworks.

What you should look at is ImageIO: Get the content of the iCloud Drive Photo as NSData and then proceed like this:

CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)(imageData), NULL );

NSDictionary* thumbOpts = [NSDictionary dictionaryWithObjectsAndKeys:
                                    (id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailWithTransform,
                                    (id)kCFBooleanTrue, (id)kCGImageSourceCreateThumbnailFromImageAlways,
                                    [NSNumber numberWithInt:160],(id)kCGImageSourceThumbnailMaxPixelSize,
                                    nil];

CGImageRef  thumbImageRef = CGImageSourceCreateThumbnailAtIndex(source,0,(__bridge CFDictionaryRef)thumbOpts);

UIImage *thumbnail = [[UIImage alloc] initWithCGImage: thumbImageRef];
Instrumentation answered 17/9, 2015 at 0:34 Comment(4)
OK, but how do I get imageData? That is all I need πŸ˜ƒ I remember you that my answer says that [NSData dataWithContentsOfUrl:tempURL]; give me nil. – Blitzkrieg
Did you check, that your NSMetaDataItem is actually downloaded from iCloud? What does [metadataItem valueForAttribute:NSMetadataUbiquitousItemIsDownloadedKey]; return? – Instrumentation
this is the whole point of the question. The thumbnails are not being generated probably because they are not on the device and Apple never cared to explain how to do it and the methods they explain don't download anything. BTW it seems stupid to download all pictures from the camera roll, wasting tons of bandwidth, when all you want is a 100x100 thumbnail. – Blitzkrieg
Well iCloud behavior is not very transparent. I would recommend the following approach: 1. Use your code to request the pregenerated thumbnail. In my testing i often got a result the 2nd time I called it. 2. If you still don't get a regenerated thumbnail, force a download using startDownloadingUbiquitousItemAtURL: and then use my code to generate the thumbnail. – Instrumentation
B
2

After testing several solutions, the one that seems to work better is this one:

[fileURL startAccessingSecurityScopedResource];

NSFileCoordinator *coordinator = [[NSFileCoordinator alloc] init];
__block NSError *error;

[coordinator coordinateReadingItemAtURL:fileURL
                                options:NSFileCoordinatorReadingImmediatelyAvailableMetadataOnly
                                  error:&error
                             byAccessor:^(NSURL *newURL) {
                               NSDictionary *thumb;
                               BOOL success = [newURL getResourceValue:&thumb forKey:NSURLThumbnailDictionaryKey error:&error];

                               UIImage *thumbnail = thumb[NSThumbnail1024x1024SizeKey];



                             }];

[fileURL stopAccessingSecurityScopedResource];

This solution is not perfect. It will fail to bring the thumbnails sometimes but I was not able to find any other solution that works 100%. Others are worst than that.

Blitzkrieg answered 21/9, 2015 at 8:11 Comment(0)
K
1

This works for me.It has a little bit different

func genereatePreviewForOnce(at size: CGSize,completionHandler: @escaping (UIImage?) -> Void) {

        _ = fileURL.startAccessingSecurityScopedResource()

        let fileCoorinator = NSFileCoordinator.init()

        fileCoorinator.coordinate(readingItemAt: fileURL, options: .immediatelyAvailableMetadataOnly, error: nil) { (url) in

            if let res = try? url.resourceValues(forKeys: [.thumbnailDictionaryKey]),

                let dict = res.thumbnailDictionary {

                let image = dict[.NSThumbnail1024x1024SizeKey]

                completionHandler(image)

            } else {

                fileURL.removeCachedResourceValue(forKey: .thumbnailDictionaryKey)

                completionHandler(nil)

            }

            fileURL.stopAccessingSecurityScopedResource()

        }

    }
Kilderkin answered 17/5, 2018 at 6:6 Comment(1)
Because the thumbnail is generating background, you may need call this many times – Kilderkin
S
0

It looks like you are generating your thumbnail after the fact. If this is your document and you are using UIDocument, override fileAttributesToWriteToURL:forSaveOperation:error: to insert the thumbnail when the document is saved.

Schnorr answered 10/11, 2017 at 16:48 Comment(0)

© 2022 - 2024 β€” McMap. All rights reserved.