Why does PHImageManager() change the image extension and degrade the image quality when I retrieve images from the photo library? - swift

When I use PHImageManager() to retrieve an image from the photo library, the image is converted from a jpeg image to a png image, and the image is degraded.
How can I retrieve the image without image degradation?
Is it a standard specification that the extension is changed from jpeg to png?
If anyone knows, please respond.
Here is a sample code I made.
img is returned as png.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let assets: PHFetchResult = PHAsset.fetchAssets(with: .image, options: nil)
let mg: PHImageManager = PHImageManager()
guard let asset = assets.lastObject else {
return
}
let options = PHImageRequestOptions()
options.isNetworkAccessAllowed = true
options.deliveryMode = .highQualityFormat
mg.requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFit, options: options, resultHandler: { image, info in
let img = image
})
}
}

When requesting a high quality image, the result handler of requestImage may be called initially with a low quality version of the image, then later called again with the high quality version.
You can check this by inspecting the PHImageResultIsDegradedKey of the info dictionary parameter of the result handler, for example:
mg.requestImage(for: asset, targetSize: PHImageManagerMaximumSize, contentMode: .aspectFit, options: options, resultHandler: { image, info in
if info?[PHImageResultIsDegradedKey] as? Bool == true {
print("Degraded image returned, will wait for high quality image")
return
}
let img = image
})
From the documentation for PHImageManager.requestImage:
[...] Photos may call your result handler block more than once. Photos first calls the block to provide a low-quality image suitable for displaying temporarily while it prepares a high-quality image. (If low-quality image data is immediately available, the first call may occur before the method returns.) When the high-quality image is ready, Photos calls your result handler again to provide it. If the image manager has already cached the requested image at full quality, Photos calls your result handler only once. The PHImageResultIsDegradedKey key in the result handler’s info parameter indicates when Photos is providing a temporary low-quality image.

Related

Re-downloading images with different sizes when using Kingfisher

i have an app that works with API JSON response. Just retrieving a simple array of items. Each item has an url with a high-res image. I'm using Kingfisher to get this images and resize to my ImageView size (that placed on my MainView)
//MainViewController
itemImageMainVC.kf.setImage(
with: URL(string: itemModel.artImage),
options: [
.processor(DownsamplingImageProcessor(size: itemImageMainVC.bounds.size) >> RoundCornerImageProcessor(cornerRadius: 16, targetSize: itemImageMainVC.bounds.size)),
.scaleFactor(UIScreen.main.scale),
.transition(.fade(0.2)),
.cacheOriginalImage
])
Everything is fine here, i've got my images on my MainView inside TableView, and they cached well. BUT! If i try to open and display the same images, but with different sizes on the SecondView, i see the same transition animation on the SecondView, so i guess that they started to download again when they shouldn't.
//SecondViewController
itemImageSecondVC.kf.setImage(
with: URL(string: itemModel.artImage),
options: [
.processor(DownsamplingImageProcessor(size: itemImageSecondVC.bounds.size) >> RoundCornerImageProcessor(cornerRadius: 16, targetSize: itemImageSecondVC.bounds.size)),
.scaleFactor(UIScreen.main.scale),
.transition(.fade(0.2)),
.cacheOriginalImage
])
itemImageMainVC and itemImageSecondVC have different sizes.
What i'm doing wrong? I just want to download/display images on the MainView and instantly show them when i open SecondView, without re-downloading
Lets try to use the cacheKey, it should be the last path component (or event the absoluteUrlString) from your image url or something that is unique.
let resource = ImageResource(downloadURL: url, cacheKey: url.lastPathComponent)
self.imageView.kf.setImage(with: resource, placeholder: UIImage(named: "placeholder-image"), options: nil, progressBlock: nil) { (result) in
switch result {
case .success(_):
// Success case
case .failure(_):
// Failed case
}
}

PHImageManager is sending duplicated photos

My code as below. It is sending duplicated photos 1) high quality and 2) low quality. Just want to understand why this library is doing that ?
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFit, options: nil) { result, info in
guard let image = result else {
return
}
self.sendPhoto(image)
}
FIXed by to force the options to send a quality
fileprivate func imageRequestOptions() -> PHImageRequestOptions {
let requestOption = PHImageRequestOptions()
requestOption.deliveryMode = .highQualityFormat
return requestOption
}
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFit, options: self.imageRequestOptions()) { result, info in
guard let image = result else {
return
}
self.sendPhoto(image)
print("sendPhoto iOS 11.0 * asset")
}
From the document of Apple
For an asynchronous request, Photos may call your result handler block more than once. Photos first calls the block to provide a low-quality image suitable for displaying temporarily while it prepares a high-quality image. (If low-quality image data is immediately available, the first call may occur before the method returns.) When the high-quality image is ready, Photos calls your result handler again to provide it. If the image manager has already cached the requested image at full quality, Photos calls your result handler only once. The PHImageResultIsDegradedKey key in the result handler’s info parameter indicates when Photos is providing a temporary low-quality image.
You can use this method for both photo and video assets—for a video asset, an image request provides a thumbnail image or poster frame.
Maybe this is business of them. I think we should note with this case

iOS 11: [ImageManager] Unable to load image data

After update to iOS 11, photo assets now load slowly and I get this message in console:
[ImageManager] Unable to load image data,
/var/mobile/Media/DCIM/103APPLE/IMG_3064.JPG
I use static function to load image:
class func getAssetImage(asset: PHAsset, size: CGSize = CGSize.zero) -> UIImage? {
let manager = PHImageManager.default()
let option = PHImageRequestOptions()
option.isSynchronous = true
var assetImage: UIImage!
var scaleSize = size
if size == CGSize.zero {
scaleSize = CGSize(width: asset.pixelWidth, height: asset.pixelHeight)
}
manager.requestImage(for: asset, targetSize: scaleSize, contentMode: .aspectFit, options: option) { (image, nil) in
if let image = image {
assetImage = image
}
}
if assetImage == nil {
manager.requestImageData(for: asset, options: option, resultHandler: { (data, _, orientation, _) in
if let data = data {
if let image = UIImage.init(data: data) {
assetImage = image
}
}
})
}
return assetImage
}
Request image for asset usually always succeeds, but it prints this message. If I use requestImageData function only, there is no such message, but photos made with Apple camera lose their orientation and I get even more issues while loading big amount of images (I use image slideshow in my app).
Apple always sucks when it comes to updates, maybe someone got a solution how to fix this? It even fails to load an asset, when there is a big list of them in user camera. Switching to requestImageData is not an option for me as it brings nil data frequently now.
I would like to point out, that I call this function only once. It is not used in UITableView etc. I use other code for thumbs with globally initialised manager and options, so assets are definitely not nil or etc.
I call this function only when user clicks at certain thumb.
When gallery has like 5000 photos, maybe connection to assets is just overloaded and later it can't handle request and crashes?
So many questions.
Hey I was having the warning as well and here is what worked for me.
Replacing
CGSize(width: asset.pixelWidth, height: asset.pixelHeight)
by
PHImageManagerMaximumSize in requestImage call
removed the warning log 🎉
Hope this helps,
I had the same problem. Though this did not completely solve it, but it definitely helped.
option.isNetworkAccessAllowed = true
This helps only on the devices where Optimise iPhone Storage option for Photos app has been turned on.
Your code has some serious issues. You are saying .isSynchronous = true without stepping into a background thread to do the fetch. That is illegal and is what is causing the slowness. Plus, you are asking for a targetSize without also saying .resizeMode = .exact, which means you are getting much bigger images than you are asking for.
However, the warning you're seeing is irrelevant and can be ignored. It in no way signals a failure of image delivery; it seems to be just some internal message that has trickled up to the console by mistake.
This seems to be a bug with iOS 11, but I found I could work around by setting synchronous option false. I reworked my code to deal with the async delivery. Probably you can use sync(execute:) for quick fix.
Also, I believe the problem only occurred with photos delivered by iCloud sharing.
You can try method "requestImageData" with following options. This worked for me in iOS 11.2 (both on device and simulator).
let options = PHImageRequestOptions()
options.deliveryMode = .highQualityFormat
options.resizeMode = .exact
options.isSynchronous = true
PHImageManager.default().requestImageData(for: asset, options: options, resultHandler: { (data, dataUTI, orientation, info) in

Display selected photos in Photos Project extension

I have been unable to find enough documentation to get started on a Photos Project extension (available in High Sierra).
How do I retrieve what a user has selected (like how Apple does it with their Prints extension), and display that in my extension's view?
I think you just want to look at PHProjectInfo which is passed to you in beginProject. That's where you get the real context of what was selected. For example:
let sections = projectInfo.sections
guard let firstContentSection = sections.first(where: { section in section.sectionType == .content }),
let firstContents = firstContentSection.sectionContents.first
You then need to convert the cloud identifiers to local ones for fetching:
localIdentifiers = context.photoLibrary.localIdentifiers(for: cloudIdentifiers)
From there, you can fetch the actual PHAssets:
let fetchResult = PHAsset.fetchAssets(withLocalIdentifiers: localIdentifiers, options: nil)
For any PHAsset, when you want the image you want to use PHImageManager:
imageManager.requestImage(for: asset, targetSize: targetSize, contentMode: PHImageContentMode.aspectFit, options: nil)

How can I improve image quality in the pdf?

Working on a pdf photo report app, and struggling with the low image quality in the pdfs that are generated.
func drawImage(index: Int, rectPos: Int) {
let image = getImage(index)
let xPosition = CGFloat(rectArray[rectPos][0])
let yPosition = CGFloat(rectArray[rectPos][1])
image.drawInRectAspectFill(CGRectMake(xPosition, yPosition, 325, 244))
}
func getImage(index: Int) -> UIImage {
var thumbnail = UIImage()
if self.photoAsset.count != 0 {
let initialRequestOptions = PHImageRequestOptions()
initialRequestOptions.resizeMode = .Exact
initialRequestOptions.deliveryMode = .HighQualityFormat
initialRequestOptions.synchronous = true
PHImageManager.defaultManager().requestImageForAsset(self.photoAsset[index], targetSize: CGSizeMake(325, 244), contentMode: PHImageContentMode.Default, options: initialRequestOptions, resultHandler: { (result, info) -> Void in
thumbnail = result!
})
}
return thumbnail
}
I then use these functions to grab the image and place it into a position on a page after UIGraphicsBeginPDFPageWithInfo(page, nil)...
I'm using BSImagePicker pod to get the images.
And finally my photoAsset is just an array of PHAsset photos that is generated after the user selects the images from the pod's CollectionView...
So far I tried all the settings for the initialRequestOptions.deliveryMode... highquality doesn't seem to make images any better.
What am I doing wrong here? Thanks!
Changing the target resolution when requesting image sets the minimum resolution of the image you are going to use.
instead of:
PHImageManager.defaultManager().requestImageForAsset(self.photoAsset[index], targetSize: CGSizeMake(325, 244)...
I simply doubled the size of the image and the image that is drawn to the pdf is better quality.
PHImageManager.defaultManager().requestImageForAsset(self.photoAsset[index], targetSize: CGSizeMake(650, 488)...