59

I'm attempting to create a UIImage (like a thumbnail or something) from a PHAsset so that I can pass it into something that takes a UIImage. I've tried adapting solutions I found on SO (since they all just directly pass it into say a tableview or something), but I have no success (likely because I'm not doing it right).

func getAssetThumbnail(asset: PHAsset) -> UIImage {
    var retimage = UIImage()
    println(retimage)
    let manager = PHImageManager.defaultManager()
    manager.requestImageForAsset(asset, targetSize: CGSize(width: 100.0, height: 100.0), contentMode: .AspectFit, options: nil, resultHandler: {(result, info)->Void in
            retimage = result
    })
    println(retimage)
    return retimage
}

The printlns are telling me that the manager.request line isn't doing anything right now. How do I get it to give me the asset as a UIImage.

Thanks.

Icaro
  • 13,141
  • 5
  • 51
  • 70
dcheng
  • 1,673
  • 1
  • 9
  • 19
  • For example, say I want to do this: var image = a.getAssetThumbnail(firstAsset) How can I achieve this? – dcheng Jun 12 '15 at 21:27
  • If anyone needs more information, check out Apple's Sample Code: https://developer.apple.com/documentation/photokit/browsing_and_modifying_photo_albums – iAleksandr May 31 '20 at 16:46

9 Answers9

88

This did what I needed it to do, in case anyone also needs this.

func getAssetThumbnail(asset: PHAsset) -> UIImage {
    let manager = PHImageManager.defaultManager()
    let option = PHImageRequestOptions()
    var thumbnail = UIImage()
    option.synchronous = true
    manager.requestImageForAsset(asset, targetSize: CGSize(width: 100.0, height: 100.0), contentMode: .AspectFit, options: option, resultHandler: {(result, info)->Void in
            thumbnail = result!
    })
    return thumbnail
}

Edit: Swift 3 update

func getAssetThumbnail(asset: PHAsset) -> UIImage {
    let manager = PHImageManager.default()
    let option = PHImageRequestOptions()
    var thumbnail = UIImage()
    option.isSynchronous = true
    manager.requestImage(for: asset, targetSize: CGSize(width: 100, height: 100), contentMode: .aspectFit, options: option, resultHandler: {(result, info)->Void in
        thumbnail = result!
    })
    return thumbnail
}
RRikesh
  • 12,503
  • 5
  • 45
  • 65
dcheng
  • 1,673
  • 1
  • 9
  • 19
  • 4
    how would you get the original width + height of the PHAsset? – Tim Nuwin Dec 03 '15 at 00:47
  • I'm also wondering how to get the original width and height, did you find a solution, @TimNuwin ? – Bram Roelandts Dec 16 '15 at 14:31
  • 1
    If you are talking about the pixelWidth and pixelHeight attributes then that's what you would use. If not, I'd need slightly more information to help. Hopefully this link helps: https://developer.apple.com/library/ios/documentation/Photos/Reference/PHAsset_Class/ – dcheng Dec 28 '15 at 03:36
  • 8
    You can get original image by settin PHImageManagerMaximumSize for targetSize. – Borut Tomazin May 12 '16 at 09:08
  • @BorutTomazin I seem to get a weird crash of `This application is modifying the autolayout engine from a background thread after the engine was accessed from the main thread. This can lead to engine corruption and weird crashes.` when i change the `targetSize` – luke Mar 09 '17 at 11:43
  • Does this returned thumbnail image holds the metadata like location or creation date? – Bhavin Ramani Apr 05 '17 at 05:46
29

try this it works for me, hope it helps you too,

func getUIImage(asset: PHAsset) -> UIImage? {

    var img: UIImage?
    let manager = PHImageManager.default()
    let options = PHImageRequestOptions()
    options.version = .original
    options.isSynchronous = true
    manager.requestImageData(for: asset, options: options) { data, _, _, _ in

        if let data = data {
            img = UIImage(data: data)
        }
    }
    return img
}
Patel Jigar
  • 1,971
  • 1
  • 21
  • 30
20

Simple Solution (Swift 4.2)

Method 1:

extension PHAsset {

    var image : UIImage {
        var thumbnail = UIImage()
        let imageManager = PHCachingImageManager()
        imageManager.requestImage(for: self, targetSize: CGSize(width: 100, height: 100), contentMode: .aspectFit, options: nil, resultHandler: { image, _ in
            thumbnail = image!
        })
        return thumbnail
    }
}                          

let image = asset.image 

Use this method if you only need UIImage from PHAsset.

OR

extension PHAsset {
    func image(targetSize: CGSize, contentMode: PHImageContentMode, options: PHImageRequestOptions?) -> UIImage {
        var thumbnail = UIImage()
        let imageManager = PHCachingImageManager()
        imageManager.requestImage(for: self, targetSize: targetSize, contentMode: contentMode, options: options, resultHandler: { image, _ in
            thumbnail = image!
        })
        return thumbnail
    }
}

let image = asset.image(targetSize: CGSize, contentMode: PHImageContentMode, options: PHImageRequestOptions?)

Use this method for your desired UIImage.

OR

extension PHAsset {

    func image(completionHandler: @escaping (UIImage) -> ()){
        var thumbnail = UIImage()
        let imageManager = PHCachingImageManager()
        imageManager.requestImage(for: self, targetSize: CGSize(width: 100, height: 100), contentMode: .aspectFit, options: nil, resultHandler: { img, _ in
            thumbnail = img!
        })
        completionHandler(thumbnail)
    }
}

let image = asset.image(completionHandler: {(img) in
    print("Finished")
})

Use this method for notify after completion.

Method 2:

extension PHAsset {
    var data : (UIImage, [AnyHashable : Any]) {
        var img = UIImage(); var information = [AnyHashable : Any](); let imageManager = PHCachingImageManager()
        imageManager.requestImage(for: self, targetSize: CGSize(width: 100, height: 100), contentMode: .aspectFit, options: nil, resultHandler: { image,info in
            img = image!
            information = info!
        })
        return (img,information)
    }
} 


let image_withData : (UIImage, [AnyHashable : Any]) = asset.data

Use this method if you want UIImage And Result Info of PHAsset

OR

extension PHAsset {

    func data(targetSize: CGSize, contentMode: PHImageContentMode, options: PHImageRequestOptions?) -> (UIImage, [AnyHashable : Any]) {
        var img = UIImage(); var information = [AnyHashable : Any](); let imageManager = PHCachingImageManager()
        imageManager.requestImage(for: self, targetSize: targetSize, contentMode: contentMode, options: options, resultHandler: { image,info in
            img = image!
            information = info!
        })
        return (img,information)
    }
}

let data = asset?.data(targetSize: CGSize, contentMode: PHImageContentMode, options: PHImageRequestOptions?)

Use this method for your desired Data.

ZAFAR007
  • 2,545
  • 1
  • 26
  • 40
7

I'd suggest using Apple's PHCachingImageManager (that inherits from PHImageManager):

A PHCachingImageManager object fetches or generates image data for photo or video assets

Also, PHCachingImageManager support a better caching mechanism.

Example of fetching a thumbnail synchronous:

let options = PHImageRequestOptions()
options.deliveryMode = .HighQualityFormat
options.synchronous = true // Set it to false for async callback

let imageManager = PHCachingImageManager()
imageManager.requestImageForAsset(YourPHAssetVar,
                                  targetSize: CGSizeMake(CGFloat(160), CGFloat(160)),
                                  contentMode: .AspectFill,
                                  options: options,
                                  resultHandler: { (resultThumbnail : UIImage?, info : [NSObject : AnyObject]?) in

                                                   // Assign your thumbnail which is the *resultThumbnail*
                                                  }

In addition, you can use PHCachingImageManager to cache your images for faster UI response:

To use a caching image manager:

  1. Create a PHCachingImageManager instance. (This step replaces using the shared PHImageManager instance.)

  2. Use PHAsset class methods to fetch the assets you’re interested in.

  3. To prepare images for those assets, call the startCachingImagesForAssets:targetSize:contentMode:options: method with the target size, content mode, and options you plan to use when later requesting images for each individual asset.

  4. When you need an image for an individual asset, call the requestImageForAsset:targetSize:contentMode:options:resultHandler: method, and pass the same parameters you used when preparing that asset.

If the image you request is among those already prepared, the PHCachingImageManager object immediately returns that image. Otherwise, Photos prepares the image on demand and caches it for later use.

In our example:

var phAssetArray : [PHAsset] = []

for i in 0..<assets.count
{
  phAssetArray.append(assets[i] as! PHAsset)
}

let options = PHImageRequestOptions()
options.deliveryMode = .Opportunistic
options.synchronous = false

self.imageManager.startCachingImagesForAssets(phAssetArray,
                                              targetSize: CGSizeMake(CGFloat(160), CGFloat(160)),
                                              contentMode: .AspectFill,
                                              options: options)
OhadM
  • 3,991
  • 1
  • 41
  • 52
7

For Swift 3.0.1:

func getAssetThumbnail(asset: PHAsset, size: CGFloat) -> UIImage {
    let retinaScale = UIScreen.main.scale
    let retinaSquare = CGSize(width: size * retinaScale, height: size * retinaScale)//(size * retinaScale, size * retinaScale)
    let cropSizeLength = min(asset.pixelWidth, asset.pixelHeight)
    let square = CGRect(x:0, y: 0,width: CGFloat(cropSizeLength),height: CGFloat(cropSizeLength))
    let cropRect = square.applying(CGAffineTransform(scaleX: 1.0/CGFloat(asset.pixelWidth), y: 1.0/CGFloat(asset.pixelHeight)))

    let manager = PHImageManager.default()
    let options = PHImageRequestOptions()
    var thumbnail = UIImage()

    options.isSynchronous = true
    options.deliveryMode = .highQualityFormat
    options.resizeMode = .exact
    options.normalizedCropRect = cropRect

    manager.requestImage(for: asset, targetSize: retinaSquare, contentMode: .aspectFit, options: options, resultHandler: {(result, info)->Void in
        thumbnail = result!
    })
    return thumbnail
}

Resource : https://gist.github.com/lvterry/f062cf9ae13bca76b0c6#file-getassetthumbnail-swift

Sour LeangChhean
  • 4,895
  • 4
  • 29
  • 35
7

Swift 4.

resizeMode,deliveryMode - These can be set according to user requirement.

isNetworkAccessAllowed - set this to "true" for fetching images from the cloud

imageSize- required image size

func getImageFromAsset(asset:PHAsset,imageSize:CGSize, callback:@escaping (_ result:UIImage) -> Void) -> Void{

    let requestOptions = PHImageRequestOptions()
    requestOptions.resizeMode = PHImageRequestOptionsResizeMode.fast
    requestOptions.deliveryMode = PHImageRequestOptionsDeliveryMode.highQualityFormat
    requestOptions.isNetworkAccessAllowed = true
    requestOptions.isSynchronous = true
    PHImageManager.default().requestImage(for: asset, targetSize: imageSize, contentMode: PHImageContentMode.default, options: requestOptions, resultHandler: { (currentImage, info) in
        callback(currentImage!)
    })
}
Faysal Ahmed
  • 6,464
  • 5
  • 23
  • 43
Thomas Paul
  • 257
  • 4
  • 11
4

The problem is that requestImageForAsset is a resultHandler and this block of code happens in the future after your functions has already printed and returned the value you was expecting. I did come changes to show you this happening and also suggest some simple solutions.

func getAssetThumbnail(asset: PHAsset) {
    var retimage = UIImage()
    println(retimage)
    let manager = PHImageManager.defaultManager()
    manager.requestImageForAsset(asset, targetSize: CGSize(width: 100.0, height: 100.0), contentMode: .AspectFit, options: nil, resultHandler: {
    (result, info)->Void in
            retimage = result
      println("This happens after")
      println(retimage)
      callReturnImage(retimage) // <- create this method
    })
    println("This happens before")
}

Learn more about closures and completion handle and async funcs at Apple documentation

I hope that helps you!

Icaro
  • 13,141
  • 5
  • 51
  • 70
  • Still kinda confused. So we are changing getAssetThumbnail to not return anything? In this case, how are we going to pass the retimage to what we need? When and how can I get the block to execute so that I can get something useful. Hopefully, callReturnImage is separate from the other function we are trying to pass retimage to. – dcheng Jun 12 '15 at 21:12
  • You can't return anything as your return will happen before your retrieve it, add another function that do what you want to do with the image or add what you want to do with the image inside of the completion block. Those are the two easier ways to fix this problem. – Icaro Jun 12 '15 at 21:14
  • Right. Ok lets say I have 2 functions (x & y) that both take UIImages. I want them to operate on the UIImage that we are creating in that block. Is it impossible for me to do this outside of that block by passing a UIImage into x & y, and getting that image from the request method? Or do I have to put x & y as the callReturnImage function that you hinted. If it is impossible, then if I was to create function z in the future, wouldn't I have to go back and edit this function for it to work? – dcheng Jun 12 '15 at 21:22
  • I am not saying is impossible I am saying this is the easier way, you could create function that also return completion blocks but the problem would just grow bigger for you, I will post a link in the answer so you can learn more about it. – Icaro Jun 12 '15 at 21:30
4

Swift 5

extension PHAsset {
func getAssetThumbnail() -> UIImage {
    let manager = PHImageManager.default()
    let option = PHImageRequestOptions()
    var thumbnail = UIImage()
    option.isSynchronous = true
    manager.requestImage(for: self,
                         targetSize: CGSize(width: self.pixelWidth, height: self.pixelHeight),
                         contentMode: .aspectFit,
                         options: option,
                         resultHandler: {(result, info) -> Void in
                            thumbnail = result!
                         })
    return thumbnail
    }
}
spacecash21
  • 1,243
  • 1
  • 16
  • 39
1

Objective-c version of code based on dcheng answer.

-(UIImage *)getAssetThumbnail:(PHAsset * )asset {

    PHImageRequestOptions *options = [[PHImageRequestOptions alloc]init];
    options.synchronous = true;

    __block UIImage *image;
    [PHCachingImageManager.defaultManager requestImageForAsset:asset targetSize:CGSizeMake(100, 100) contentMode:PHImageContentModeAspectFit options:options resultHandler:^(UIImage * _Nullable result, NSDictionary * _Nullable info) {
        image = result;
    }];
    return image;
  }
Saikiran Komirishetty
  • 6,345
  • 1
  • 26
  • 36