web-dev-qa-db-fra.com

PHImageManager requestImageForAsset renvoie nil parfois pour les photos iCloud

Environ 10% du temps, PHImageManager.defaultManager (). RequestImageForAsset renvoie nil au lieu d'un UIImage valide après le premier retour d'un UIImage valide mais "dégradé". Aucune erreur ou autre indice que je peux voir n'est renvoyé dans l'info avec le nil. 

Cela semble se produire avec les photos qui doivent être téléchargées à partir d'iCloud, avec iCloud Photo Library et Optimiser le stockage iPad activés. J'ai essayé de changer les options, la taille, etc., mais rien ne semble avoir d'importance.

Si je réessaie le requestImageForAsset après l'échec, il retournera généralement correctement un UIImage, bien que parfois cela nécessite quelques tentatives.

Une idée de ce que je pourrais faire mal? Ou est-ce juste un bug dans le framework 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)")
           }
    }
}
39
LenK

Je viens de traverser cela aussi. D'après mes tests, le problème apparaît sur les appareils sur lesquels l'option "Optimiser le stockage" est activée et réside dans la différence entre les deux méthodes ci-dessous:

[[PHImageManager defaultManager] requestImageForAsset: ...] 

Cela permettra d'extraire des images iCloud distantes avec succès si vos options sont correctement configurées.


[[PHImageManager defaultManager] requestImageDataForAsset: ...]

Cette fonction ne fonctionne que pour les images résidant dans la mémoire du téléphone ou récemment extraites d'iCloud par votre application.


Voici un extrait de travail que j'utilise -bear avec moi l'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);
    }];

Full Gist disponible sur github

Mise à jour pour Swift 4:

    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))")
    })
33
ahbou

J'ai trouvé que cela n'avait rien à voir avec le réseau ou iCloud. Cela a parfois échoué, même sur des images complètement locales. Parfois, c'étaient des images de mon appareil photo, parfois, des images enregistrées à partir du Web.

Je n'ai pas trouvé de solution, mais un travail inspiré par @Nadzeya qui a fonctionné 100% du temps pour moi a été de demander toujours une taille cible égale à la taille de l'actif .

Par exemple.

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
        }
    });

Je pense que l’inconvénient à cela est que nous récupérons l’image complète en mémoire, puis que nous obligeons ImageView à effectuer la mise à l’échelle, mais au moins dans mon cas d’utilisation, il n’y avait pas de problème de performances notable. beaucoup mieux que de charger une image floue ou nulle.

Une optimisation possible consiste à demander à nouveau l'image à sa taille d'actif uniquement si l'image redevient nulle.

4
Jeremy

J'ai essayé beaucoup de choses

  • taille cible supérieure à (400, 400): ne fonctionne pas
  • targetSize est égal à la taille de l'actif: ne fonctionne pas
  • Désactiver Optimize Storage dans iCloud Photos dans les paramètres: ne fonctionne pas
  • Dispatch requestImage vers la file d'attente en arrière-plan: ne fonctionne pas
  • Utilisez PHImageManagerMaximumSize: ne fonctionne pas
  • Utilisez isNetworkAccessAllowed: ne fonctionne pas
  • Jouez avec différentes valeurs dans PHImageRequestOptions, comme version, deliveryMode, resizeMode: ne fonctionne pas
  • Ajouter une progressHandler: ne fonctionne pas
  • Appelez requestImage encore une fois s'il a échoué: pas de travail

Tout ce que je reçois est nul UIImage et info avec PHImageResultDeliveredImageFormatKey, comme dans ce radar Photos Frameworks ne renvoie aucune erreur ni image pour des éléments particuliers

Utiliser les ajustements d'aspect

Ce qui fonctionne pour moi, voir https://github.com/hyperoslo/Gallery/blob/master/Sources/Images/Image.Swift#L34

  • Utilisez targetSize avec <200: c’est pourquoi je peux charger la vignette
  • Utiliser aspectFit: Spécifier à contentMode fait le tour pour moi

Voici le code

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

Fetch asynchrone

Ce qui précède peut être à l'origine d'une situation critique, assurez-vous donc que vous effectuez une récupération asynchrone, ce qui signifie que vous n'avez pas isSynchronous. Jetez un oeil à https://github.com/hyperoslo/Gallery/pull/72

3
onmyway133

Essayez d’utiliser targetSize supérieur à (400, 400) .

2
Nadzeya

Je le voyais aussi, et la seule chose qui fonctionnait pour moi était de régler options.isSynchronous = false. Mes options particulières sont:

options.isNetworkAccessAllowed = true
options.deliveryMode = .highQualityFormat
options.version = .current
options.resizeMode = .exact
options.isSynchronous = false
0
nathan

J'obtenais aussi nil pour les images iCloud. Cela ne faisait aucune différence si j'utilisais la variante requestImage ou requestImageData de la méthode. Il se trouve que mon problème était que mon appareil était connecté au réseau via Charles Proxy car je voulais surveiller les demandes et les réponses de l'application. Pour une raison quelconque, le périphérique ne pourrait pas fonctionner avec iCloud s'il était connecté de cette façon. Une fois que j'ai désactivé l'application proxy pourrait obtenir des images iCloud.

0
Nemanja Kovacevic

La solution pour moi était de définir 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     
    }
}

Bon codage!

0
Faustino Gagneten

Ce qui a fonctionné pour moi a été de laisser PHImageManager charger les données d'actif de manière synchrone, mais à partir d'un thread en arrière-plan asynchrone. Simplifié, il ressemble à ceci:

    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 */ }
        }
    }
0
Fran Pugl