ios - PHImageManager requestImageForAsset devuelve nil a veces para las fotos de iCloud
photosframework (7)
Aproximadamente el 10% de las veces PHImageManager.defaultManager (). RequestImageForAsset devuelve nil en lugar de un UIImage válido después de devolver primero un UIImage válido aunque "degradado". Ningún error u otra pista que puedo ver se devuelve en la información con el nil.
Esto parece suceder con las fotos que deben descargarse de iCloud, con iCloud Photo Library y Optimize iPad Storage habilitadas. Intenté cambiar las opciones, el tamaño, etc. pero nada parece importar.
Si vuelvo a intentar el requestImageForAsset después de la falla, normalmente devolverá correctamente un UIImage, aunque a veces requiere un par de intentos.
¿Alguna idea de lo que podría estar haciendo mal? ¿O es solo un error en el marco de Photos?
func photoImage(asset: PHAsset, size: CGSize, contentMode: UIViewContentMode, completionBlock:(image: UIImage, isPlaceholder: Bool) -> Void) -> PHImageRequestID? {
let options = PHImageRequestOptions()
options.networkAccessAllowed = true
options.version = .Current
options.deliveryMode = .Opportunistic
options.resizeMode = .Fast
let requestSize = !CGSizeEqualToSize(size, CGSizeZero) ? size : PHImageManagerMaximumSize
let requestContentMode = contentMode == .ScaleAspectFit ? PHImageContentMode.AspectFit : PHImageContentMode.AspectFill
return PHImageManager.defaultManager().requestImageForAsset(asset, targetSize: requestSize, contentMode: requestContentMode, options: options)
{ (image: UIImage!, info: [NSObject : AnyObject]!) in
if let image = image {
let degraded = info[PHImageResultIsDegradedKey] as? Bool ?? false
completionBlock(image: photoBlock.rotatedImage(image), isPlaceholder: degraded)
} else {
let error = info[PHImageErrorKey] as? NSError
NSLog("Nil image error = /(error?.localizedDescription)")
}
}
}
Descubrí que esto no tenía nada que ver con la red o iCloud. En ocasiones fallaba, incluso en imágenes que eran completamente locales. A veces se trataba de imágenes de mi cámara, a veces de imágenes guardadas de la web.
No encontré una solución, pero una solución inspirada en @Nadzeya que funcionó el 100% del tiempo para mí siempre fue solicitar un tamaño objetivo igual al tamaño del activo .
P.ej.
PHCachingImageManager().requestImage(for: asset,
targetSize: CGSize(width: asset.pixelWidth, height: asset.pixelHeight) ,
contentMode: .aspectFit,
options: options,
resultHandler: { (image, info) in
if (image == nil) {
print("Error loading image")
print("/(info)")
} else {
view.image = image
}
});
Creo que los inconvenientes de esto serían que recuperamos la imagen completa en la memoria, y luego forzamos el ImageView para hacer la escala, pero al menos en mi caso de uso, no había un problema de rendimiento notable, y era mucho mejor que cargar una imagen borrosa o nula.
Una posible optimización aquí es volver a solicitar la imagen en su tamaño de activo solo si la imagen vuelve a ser nula.
He intentado muchas cosas
- targetSize mayor que (400, 400): no funciona
- targetSize equivale al tamaño de los activos: no funciona
- Deshabilite el
Optimize Storage
en iCloud Photos en Configuración: no funciona -
requestImage
envíorequestImage
a la cola de fondo: no funciona - Use
PHImageManagerMaximumSize
: no funciona - Use
isNetworkAccessAllowed
: no funciona - Juega con diferentes valores en
PHImageRequestOptions
, comoversion
,deliveryMode
,resizeMode
: not work - Agregar un
progressHandler
: no funciona - Llamada
requestImage
nuevamente caso de que fallara: no funciona
Todo lo que obtengo es nulo UIImage
e información con PHImageResultDeliveredImageFormatKey
, como en este radar Photos Frameworks no devuelve ningún error o imagen para activos particulares
Usar ajuste de aspecto
Lo que funciona para mí, consulte https://github.com/hyperoslo/Gallery/blob/master/Sources/Images/Image.swift#L34
- Utilice
targetSize
con <200: esta es la razón por la que puedo cargar la miniatura - Usar
aspectFit
: Especificar paraaspectFit
me funciona
Aquí está el código
let options = PHImageRequestOptions()
options.isSynchronous = true
options.isNetworkAccessAllowed = true
var result: UIImage? = nil
PHImageManager.default().requestImage(
for: asset,
targetSize: size,
contentMode: .aspectFit,
options: options) { (image, _) in
result = image
}
return result
Buscar de forma asíncrona
Lo anterior puede causar una condición de carrera, así que asegúrese de buscar de forma asíncrona, lo que significa que no es isSynchronous
. Eche un vistazo a https://github.com/hyperoslo/Gallery/pull/72
Intente utilizar targetSize mayor que (400, 400). Me ayudó.
La solución para mí fue establecer un targetSize
var image: UIImage?
let options = PHImageRequestOptions()
options.isNetworkAccessAllowed = true
options.isSynchronous = true
options.resizeMode = PHImageRequestOptionsResizeMode.exact
let targetSize = CGSize(width:1200, height:1200)
PHImageManager.default().requestImage(for: self, targetSize: targetSize, contentMode: PHImageContentMode.aspectFit, options: options) { (receivedImage, info) in
if let formAnImage = receivedImage
{
image = formAnImage
}
}
Buena codificación!
Lo que funcionó para mí fue dejar que PHImageManager cargara los datos del activo de forma síncrona, pero a partir de un hilo de fondo asíncrono. Simplificado, se ve así:
DispatchQueue.global(qos: .userInitiated).async {
let requestOptions = PHImageRequestOptions()
requestOptions.isNetworkAccessAllowed = true
requestOptions.version = .current
requestOptions.deliveryMode = .opportunistic
requestOptions.isSynchronous = true
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFit, options: requestOptions) { image, _ in
DispatchQueue.main.async { /* do something with the image */ }
}
}
También estaba obteniendo nil
para las imágenes de iCloud. No hizo una diferencia si utilicé requestImage
o requestImageData
sabor del método. Resultó que mi problema fue que mi dispositivo estaba conectado a la red a través de Charles Proxy, ya que quería controlar las solicitudes y la aplicación de respuestas. Por alguna razón, el dispositivo no podría funcionar con iCloud si está conectado de esta manera. Una vez que apagué la aplicación proxy, pude obtener imágenes de iCloud.
Yo también pasé por esto. Según mis pruebas, el problema aparece en los dispositivos que tienen habilitada la opción "Optimizar almacenamiento" y reside en la diferencia entre los dos métodos siguientes:
[[PHImageManager defaultManager] requestImageForAsset: ...]
Esto obtendrá con éxito las imágenes remotas de iCloud si sus opciones están configuradas correctamente.
[[PHImageManager defaultManager] requestImageDataForAsset: ...]
Esta función solo funciona para las imágenes que residen en la memoria de los teléfonos o que su aplicación actualizó recientemente en iCloud en otra.
Aquí hay un fragmento de trabajo que estoy usando -llevar conmigo el Obj-c :)
PHImageRequestOptions *options = [[PHImageRequestOptions alloc] init];
options.deliveryMode = PHImageRequestOptionsDeliveryModeHighQualityFormat; //I only want the highest possible quality
options.synchronous = NO;
options.networkAccessAllowed = YES;
options.progressHandler = ^(double progress, NSError *error, BOOL *stop, NSDictionary *info) {
NSLog(@"%f", progress); //follow progress + update progress bar
};
[[PHImageManager defaultManager] requestImageForAsset:myPhAsset targetSize:self.view.frame.size contentMode:PHImageContentModeAspectFill options:options resultHandler:^(UIImage *image, NSDictionary *info) {
NSLog(@"reponse %@", info);
NSLog(@"got image %f %f", image.size.width, image.size.height);
}];
Gist completo disponible en github
Actualizado para Swift 3:
let options = PHImageRequestOptions()
options.deliveryMode = PHImageRequestOptionsDeliveryMode.highQualityFormat
options.isSynchronous = false
options.isNetworkAccessAllowed = true
options.progressHandler = { (progress, error, stop, info) in
print("progress: /(progress)")
}
PHImageManager.default().requestImage(for: myPHAsset, targetSize: view.frame.size, contentMode: PHImageContentMode.aspectFill, options: options, resultHandler: {
(image, info) in
print("dict: /(String(describing: info))")
print("image size: /(String(describing: image?.size))")
})