How to load single image from Photos - ios

I use an UIImagePicker to choose an image from the photos app and would like to store the url and later reuse the same image.
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let chosenImage = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
let asset = info[UIImagePickerController.InfoKey.phAsset]
let url = info[UIImagePickerController.InfoKey.referenceURL] as! URL
...
Why is asset nil?
referenceURL is deprecated but contains something like assets-library://asset/asset.JPG?id=95554EBE-DAB8-4A36-9BEA-00BAB0174777&ext=JPG
private func loadImage() -> UIImage? {
let manager = PHImageManager.default()
let fetchOptions = PHFetchOptions()
fetchOptions.predicate = NSPredicate(format: "? = %#", "?")
let fetchResult: PHFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
var image: UIImage? = nil
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
requestOptions.deliveryMode = .highQualityFormat
manager.requestImage(for: fetchResult.object(at: 0), targetSize: CGSize(width: 500, height: 500), contentMode: .aspectFill, options: requestOptions) { img, err in
image = img
}
return image
}
What should the predicate look like? Thanks!

The PHAsset is nil because you didn't obtain user authorization for Photo Library usage beforehand. Your lack of authorization means yes, you can present the picker, and yes, the user can choose a photo, and yes, you can receive the photo image, but you can't look into the Photo Library.
If you get permission beforehand, the PHAsset will be an actual PHAsset and you can use that to go back and get further info about the asset from the Photo Library. Use the asset's identifier if you want to store a persistent reference to it.
https://developer.apple.com/documentation/photokit/phobject/1622400-localidentifier

Related

photo library access all photos cause PHImageManager to fetch image thumbnails

Below is how I fetch an image from the library. When I grant photo access to "Selected Photos" in the system settings for the app the image is as expected the largest size available. However, when I change photo access permissions to "All Photos" the code below produces a thumbnail of the original image. The app runs on ios 15. No iCloud is configured. Can anyone tell me please what is going on?
private func loadImage(assetId: String, done: #escaping (Image?) -> Void) {
let fetchResults: PHFetchResult<PHAsset> =
PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: nil)
guard let asset: PHAsset = fetchResults.firstObject else {
return
}
let manager = PHImageManager()
manager.requestImage(for: asset, targetSize: PHImageManagerMaximumSize,
contentMode: .aspectFit, options: nil) { (uiImage, _) in
if let uiImage = uiImage {
done(Image(uiImage: uiImage))
} else {
done(nil)
}
}
}
In general I can see why image quality depends on level of access. I was not able to find any documentation on this. What I did find though was that the deliveryMode of the request also matters. when I added the following options to the requestImage call the issue was solved.
let requestOptions = PHImageRequestOptions()
requestOptions.deliveryMode = .highQualityFormat
let manager = PHImageManager()
manager.requestImage(for: asset, targetSize: PHImageManagerMaximumSize,contentMode: .aspectFit, options: requestOptions) { (uiImage, _) in
//do something
}

how to get image from PHImageFileURLKey

guys.
I am getting images URL from photos by using below code.
func getAllImagesURL() -> [URL]
{
var arr_URL = [URL]()
for index in 0..<fetchResult.count
{
imgManager.requestImageData(for: fetchResult.object(at: index) as PHAsset, options: requestOptions, resultHandler: { (imagedata, dataUTI, orientation, info) in
if let fileName = (info?["PHImageFileURLKey"] as? URL)
{
//do sth with file name
arr_URL.append(fileName)
}
})
}
return arr_URL
}
By using this URL key I want to get the image from photos.I have searched and found below code.But it still not working.
func getImage(assetUrl: URL) -> UIImage? {
let asset = PHAsset.fetchAssets(withALAssetURLs: [assetUrl], options: nil)
guard let result = asset.firstObject else {
return nil
}
var assetImage: UIImage?
let options = PHImageRequestOptions()
options.isSynchronous = true
PHImageManager.default().requestImage(for: result, targetSize: UIScreen.main.bounds.size, contentMode: PHImageContentMode.aspectFill, options: options) { image, info in
assetImage = image
}
return assetImage
}
It returns nil.So please help me.How to get the image by using URL key.
Thanks in Advance..
In the getImage(assetUrl: URL) -> UIImage? method, you are using
let asset = PHAsset.fetchAssets(withALAssetURLs: [assetUrl], options: nil)
here assetUrl is the url that should be taken from if you are using the AssetsLibrary. This library is deprecated from iOS 9.0 onwards. We have to use Photos library instead.
BTW, you are already getting all the images(data) in getAllImageURLs() method. Just convert that method to get all the images and process those images as required. You can use the below method to get all the images.
func getAllImages() -> [UIImage]?
{
let imgManager = PHImageManager.default()
let requestOptions = PHImageRequestOptions()
requestOptions.isSynchronous = true
let fetchOptions = PHFetchOptions()
fetchOptions.sortDescriptors = [NSSortDescriptor(key:"creationDate", ascending: true)]
let fetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
var allImages = [UIImage]()
for index in 0..<fetchResult.count
{
let asset = fetchResult.object(at: index) as PHAsset
imgManager.requestImage(for: asset, targetSize: UIScreen.main.bounds.size, contentMode: .aspectFill, options: requestOptions, resultHandler: { (uiimage, info) in
allImages.append(uiimage!)
})
}
return allImages
}
NOTE: tweak this method as per your requirements.

How to retrieve PHAsset from UIImagePickerController

I'm trying to retrieve a PHAsset however PHAsset.fetchAssets(withALAssetURLs:options:) is deprecated from iOS 8 so how can I properly retrieve a PHAsset?
I had the same the issue, first check permissions and request access:
let status = PHPhotoLibrary.authorizationStatus()
if status == .notDetermined {
PHPhotoLibrary.requestAuthorization({status in
})
}
Just hook that up to whatever triggers your UIImagePickerController. The delegate call should now include the PHAsset in the userInfo.
guard let asset = info[UIImagePickerControllerPHAsset] as? PHAsset
Here is my solution:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
if #available(iOS 11.0, *) {
let asset = info[UIImagePickerControllerPHAsset]
} else {
if let assetURL = info[UIImagePickerControllerReferenceURL] as? URL {
let result = PHAsset.fetchAssets(withALAssetURLs: [assetURL], options: nil)
let asset = result.firstObject
}
}
}
The PHAsset will not appear in the didFinishPickingMediaWithInfo: info result unless the user has authorized, which did not happen for me just by presenting the picker. I added this in the Coordinator init():
let status = PHPhotoLibrary.authorizationStatus()
if status == .notDetermined {
PHPhotoLibrary.requestAuthorization({status in
})
}
I am not sure what you want.
Are you trying to target iOS 8?
This is how I fetch photos and it works in iOS (8.0 and later), macOS (10.11 and later), tvOS (10.0 and later).
Code is commented where it may be confusing
The first functions sets the options to fetch the photos
The second function will actually fetch them
//import the Photos framework
import Photos
//in these arrays I store my images and assets
var images = [UIImage]()
var assets = [PHAsset]()
fileprivate func setPhotoOptions() -> PHFetchOptions{
let fetchOptions = PHFetchOptions()
fetchOptions.fetchLimit = 15
let sortDescriptor = NSSortDescriptor(key: "creationDate", ascending: false)
fetchOptions.sortDescriptors = [sortDescriptor]
return fetchOptions
}
fileprivate func fetchPhotos() {
let allPhotos = PHAsset.fetchAssets(with: .image, options: setPhotoOptions())
DispatchQueue.global(qos: .background).async {
allPhotos.enumerateObjects({ (asset, count, stop) in
let imageManager = PHImageManager.default()
let targetSize = CGSize(width: 200, height: 200)
let options = PHImageRequestOptions()
options.isSynchronous = true
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: .aspectFit, options: options, resultHandler: { (image, info) in
if let image = image {
self.images.append(image)
self.assets.append(asset)
}
if count == allPhotos.count - 1 {
DispatchQueue.main.async {
//basically, here you can do what you want
//(after you finish retrieving your assets)
//I am reloading my collection view
self.collectionView?.reloadData()
}
}
})
})
}
}
Edit based on OP's clarification
You need to set the delegate UIImagePickerControllerDelegate
then implement the following function
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject]) {
within said method, get the image like this:
var image : UIImage = info[UIImagePickerControllerEditedImage] as! UIImage

Swift 3 load image from local directory

I'm new to iOS dev and cannot figure out how to load an image in an imageView from a local directory.
I use imagePickerController to pick an image, get its info, then use this information to display the image in the imageView.
Here is the code to pick the image and its info dictionary:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
let image = info[UIImagePickerControllerOriginalImage] as! UIImage
let imageUrl = info[UIImagePickerControllerReferenceURL] as! NSURL
let imageName = imageUrl.lastPathComponent
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
let photoURL = NSURL(fileURLWithPath: documentDirectory)
let localPath = photoURL.appendingPathComponent(imageName!)!
imagesList.append(localPath)
picker.dismiss(animated: true, completion: nil)
}
Then, and here is where I'm looking for some help, I want to use either localPath or imageUrl to load the image in another imageView but cannot figure out how.
I tried
func changeImage(_ sender: Any) {
if imagesList.count > 0 {
let imageUrlPath = imagesList[indexList]
let urlString: String = imageUrlPath.absoluteString
imageView.image = UIImage(contentsOfFile: urlString)
indexList = (indexList + 1) % imagesList.count
}
}
But I cannot have something working.
Any idea how I can achieve that?
Thank you for your help.
You'll want to save a reference to PHAsset objects, not URL strings.
Start with defining your Array as:
var imagesList = [PHAsset]()
Then, in didFinishPickingMediaWithInfo:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
dismiss(animated: true)
// get the selected image as a UIImage object
let image = info[UIImagePickerControllerOriginalImage] as! UIImage
imageView.image = image
// save a PHAsset reference in imagesList array for later use
if let imageUrl = info[UIImagePickerControllerReferenceURL] as? URL{
let assets = PHAsset.fetchAssets(withALAssetURLs: [imageUrl], options: nil)
if let p = assets.firstObject {
imagesList.append(p)
}
}
}
Next, add a couple "convenience" functions:
func getAssetFullsize(asset: PHAsset) -> UIImage {
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
var img = UIImage()
option.isSynchronous = true
let w = asset.pixelWidth
let h = asset.pixelHeight
manager.requestImage(for: asset, targetSize: CGSize(width: w, height: h), contentMode: .aspectFit, options: option, resultHandler: {(result, info)->Void in
img = result!
})
return img
}
func getAssetThumbnail(asset: PHAsset) -> UIImage {
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
var thumbnail = UIImage()
option.isSynchronous = true
let w = 100
let h = 100
manager.requestImage(for: asset, targetSize: CGSize(width: w, height: h), contentMode: .aspectFit, options: option, resultHandler: {(result, info)->Void in
thumbnail = result!
})
return thumbnail
}
and finally, when you want to get the actual image:
func changeImage(_ sender: Any) {
if imagesList.count > 0 {
// get the full-size image from PHAsset
imageView.image = getAssetFullsize(asset: imagesList[indexList])
// or, just get a thumbnail-sized image
//imageView.image = getAssetThumbnail(asset: imagesList[indexList])
indexList = (indexList + 1) % imagesList.count
}
}
Naturally, you'll want to add appropriate error-checking, your own sizing, naming, tracking, etc... but I think this is the direction you want to head.

Photos framework image from video at a certain frame

I have made a library collectionview filled with videos i fetch from the Photos framework, i use thumbnails to display all the different videos using the following method
func requestImageForAsset(_ asset: PHAsset!, targetSize targetSize: CGSize, contentMode contentMode: PHImageContentMode, options options: PHImageRequestOptions!, resultHandler resultHandler: ((UIImage!, [NSObject : AnyObject]!) -> Void)!) -> PHImageRequestID
which works fine.
But my videos always start with a black frame, so all the thumbnails are black. can I offset the frame from which it takes a snapshot image?
This is code i have used in my project which works like a charm for me
func generateThumnail(url: URL) -> UIImage? {
let asset = AVAsset(url: url)
let assetImgGenerate = AVAssetImageGenerator(asset: asset)
assetImgGenerate.appliesPreferredTrackTransform = true
assetImgGenerate.maximumSize = CGSize(width: 100, height: 100)
let time = CMTimeMake(1, 30)
if let img = try? assetImgGenerate.copyCGImage(at: time, actualTime: nil) {
return UIImage(cgImage: img)
}
return nil
}
The above function will return the image at 1 sec of the video
This is the way of passing the assetUrl of your video to get the thumbnail
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject : AnyObject]) {
let mediaType = info[UIImagePickerControllerMediaType] as! String
if (mediaType == kUTTypeMovie as! String){
let tempVideo = info[UIImagePickerControllerMediaURL] as! URL
if let thumbnail = self.generateThumbnail(url: tempVideo) {
// Use your thumbnail
}
You can fetch the associated AVAsset object with
func requestAVAssetForVideo(_ asset: PHAsset,
options options: PHVideoRequestOptions?,
resultHandler resultHandler: (AVAsset?,
AVAudioMix?,
[NSObject : AnyObject]?) -> Void) -> PHImageRequestID
then load that asset into an AVAssetImageGenerator which is capable of fetching time-specific thumbnails via
func copyCGImageAtTime(_ requestedTime: CMTime,
actualTime actualTime: UnsafeMutablePointer<CMTime>,
error outError: NSErrorPointer) -> CGImage!
I think you need to be looking at the AVFoundation libraries to decode the video and grab the frame you want yourself.
To update the placeholders in the library looking at the docs it seems that you need to create a PHAssetChangeRequest and set the placeholderForCreatedAsset property on it. Then you need to request that the change be performed by the library within a changeblock.
Note: If this is only for your use you can set the placeholder frame within Photos and that should be reflected in your app.

Resources