ios photosframework

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ío requestImage a la cola de fondo: no funciona
  • Use PHImageManagerMaximumSize : no funciona
  • Use isNetworkAccessAllowed : no funciona
  • Juega con diferentes valores en PHImageRequestOptions , como version , 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 para aspectFit 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))") })