web-dev-qa-db-fra.com

Comment charger de manière asynchrone une image dans un UIImageView?

J'ai un UIView avec une sous-vue UIImageView. J'ai besoin de charger une image dans UIImageView sans bloquer l'interface utilisateur. L'appel bloquant semble être: UIImage imageNamed:. Voici ce que je pensais avoir résolu ce problème:

-(void)updateImageViewContent {
    dispatch_async(
        dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        UIImage * img = [UIImage imageNamed:@"background.jpg"];
        dispatch_sync(dispatch_get_main_queue(), ^{
            [[self imageView] setImage:img];
        });
    });
}

L'image est petite (150x100).

Cependant, l'interface utilisateur est toujours bloquée lors du chargement de l'image. Qu'est-ce que je rate ?

Voici un petit exemple de code présentant ce comportement:

Créez une nouvelle classe basée sur UIImageView, définissez son interaction utilisateur sur YES, ajoutez deux instances dans UIView et implémentez sa méthode touchesBegan de la manière suivante:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    if (self.tag == 1) {
        self.backgroundColor= [UIColor redColor];
    }

    else {
    dispatch_async(dispatch_get_main_queue(), ^{
    [self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
  });
    [UIView animateWithDuration:0.25 animations:
        ^(){[self setFrame:CGRectInset(self.frame, 50, 50)];}];
    }
}

Attribuez le tag 1 à l'une de ces imageViews. 

Que se passe-t-il exactement lorsque vous appuyez sur les deux vues presque simultanément, en commençant par la vue qui charge une image? L'interface utilisateur est-elle bloquée car elle attend le retour de [self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];? Si oui, comment puis-je le faire de manière asynchrone?

Voici un projet sur github avec du code ipmcc 

Utilisez un appui long, puis faites glisser pour dessiner un rectangle autour des carrés noirs. Si j'ai bien compris sa réponse, le rectangle de sélection blanc ne devrait en principe pas être bloqué lors du premier chargement de l'image, mais c'est le cas.

Deux images sont incluses dans le projet (une petite: woodTile.jpg et une plus grande: bois.jpg). Le résultat est le même avec les deux.

Format d'image

Je ne comprends pas vraiment en quoi cela est lié au problème que j'ai toujours avec l'interface utilisateur bloquée lorsque l'image est chargée pour la première fois, mais les images PNG décodent sans bloquer l'interface utilisateur, tandis que les images JPG bloquent l'interface utilisateur.

Chronologie des événements 

step 0step 1

Le blocage de l'interface utilisateur commence ici ..

step 2

.. et se termine ici.

step 3

Solution AFNetworking 

    NSURL * url =  [ [NSBundle mainBundle]URLForResource:@"bois" withExtension:@"jpg"];
    NSURLRequest * request = [NSURLRequest requestWithURL:url];
    [self.imageView setImageWithURLRequest:request
                          placeholderImage:[UIImage imageNamed:@"placeholder.png"]
                                   success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
                                       NSLog(@"success: %@", NSStringFromCGSize([image size]));
                                   } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
                                       NSLog(@"failure: %@", response);
                                   }];

// this code works. Used to test that url is valid. But it's blocking the UI as expected.
if (false)       
if (url) {
        [self.imageView setImage: [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]]; }

La plupart du temps, il se connecte: success: {512, 512}

Il enregistre également occasionnellement: success: {0, 0}

Et parfois: failure: <NSURLResponse: 0x146c0000> { URL: file:///var/mobile/Appl...

Mais l'image n'est jamais changée.

24
alecail

Le problème est que UIImage ne lit pas et ne décode pas réellement l'image avant sa première utilisation ou son dessin. Pour forcer ce travail sur un thread d'arrière-plan, vous devez utiliser/dessiner l'image sur le thread d'arrière-plan avant de créer le thread principal -setImage:. Cela a fonctionné pour moi:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    UIImage * img = [UIImage imageNamed:@"background.jpg"];

    // Make a trivial (1x1) graphics context, and draw the image into it
    UIGraphicsBeginImageContext(CGSizeMake(1,1));
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), [img CGImage]);
    UIGraphicsEndImageContext();

    // Now the image will have been loaded and decoded and is ready to rock for the main thread
    dispatch_sync(dispatch_get_main_queue(), ^{
        [[self imageView] setImage: img];
    });
});

EDIT: L'interface utilisateur ne bloque pas. Vous l'avez spécifiquement configuré pour utiliser UILongPressGestureRecognizer qui attend, par défaut, une demi-seconde avant de faire quoi que ce soit. Le fil principal est toujours le traitement des événements, mais rien ne se passera tant que le délai imparti à GR n'aura pas expiré. Si tu fais ça:

    longpress.minimumPressDuration = 0.01;

... vous remarquerez que cela devient beaucoup plus vif. Le chargement de l'image n'est pas le problème ici.

EDIT 2: J'ai lu le code, tel que posté sur github, fonctionnant sur un iPad 2, et je ne comprends tout simplement pas le hoquet que vous décrivez. En fait, c'est assez lisse. Voici une capture d'écran de l'exécution du code dans l'instrument CoreAnimation:

enter image description here

Comme vous pouvez le voir sur le graphique du haut, le FPS va jusqu'à ~ 60FPS et y reste tout au long du geste. Sur le graphique du bas, vous pouvez voir le blip à environ 16 s, c'est là que l'image est chargée pour la première fois, mais vous pouvez voir qu'il n'y a pas de baisse de la cadence. Juste à partir de l'inspection visuelle, je vois la couche de sélection se croiser, et il y a un petit délai, mais observable, entre la première intersection et l'apparition de l'image. Autant que je sache, le code de chargement en arrière-plan fonctionne comme prévu.

J'aimerais pouvoir vous aider davantage, mais je ne vois tout simplement pas le problème.

52
ipmcc

Vous pouvez utiliser AFNetworking library, dans lequel en important la catégorie

"UIImageView + AFNetworking.m" Et en utilisant la méthode suivante:

[YourImageView setImageWithURL:[NSURL URLWithString:@"http://image_to_download_from_serrver.jpg"] 
      placeholderImage:[UIImage imageNamed:@"static_local_image.png"]
               success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
                  //ON success perform 
               }
               failure:NULL];

j'espère que cela t'aides .

5
itechnician

J'ai eu un problème très similaire avec mon application où je devais télécharger beaucoup d'images et parallèlement, mon interface utilisateur était constamment mise à jour. Ci-dessous, le lien vers le tutoriel simple qui a résolu mon problème:

Tutoriel NSOperations & NSOperationQueues

4
Geekoder

c'est le bon moyen:

-(void)updateImageViewContent {
    dispatch_async(dispatch_get_main_queue(), ^{

        UIImage * img = [UIImage imageNamed:@"background.jpg"];
        [[self imageView] setImage:img];
    });
}
3
Mirko Catalano

Pourquoi n'utilisez-vous pas une bibliothèque tierce telle que AsyncImageView ? Avec cela, tout ce que vous avez à faire est de déclarer votre objet AsyncImageView et de transmettre l’URL ou l’image que vous voulez charger. Un indicateur d'activité s'affiche pendant le chargement de l'image et rien ne bloque l'interface utilisateur.

2

- (void) touchesBegan: est appelé dans le fil principal. En appelant dispatch_async (dispatch_get_main_queue), vous venez de mettre le bloc dans la file d'attente. Ce bloc sera traité par GCD lorsque la file d’attente sera prête (c’est-à-dire que le traitement de vos contacts est terminé). C'est pourquoi vous ne pouvez pas voir votre woodenTile chargé et attribué à self.image tant que vous n'avez pas relâché votre doigt et laissé GCD traiter tous les blocs mis en file d'attente dans la file d'attente principale.

Remplacement:

    dispatch_async(dispatch_get_main_queue(), ^{
    [self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];
  });

par :

[self setImage:[UIImage imageNamed:@"woodenTile.jpg"]];

devrait résoudre votre problème… au moins pour le code qui l'expose.

1
Patrock

Lorsque vous utilisez AFNetwork dans une application, vous n'avez pas besoin d'utiliser de bloc pour charger l'image car AFNetwork fournit la solution. Comme ci-dessous:

#import "UIImageView+AFNetworking.h"

Et 

   Use **setImageWithURL** function of AFNetwork....

Merci

0
Hindu

Pensez à utiliser SDWebImage : il télécharge et met en cache l'image en arrière-plan, mais le charge et le restitue. 

Je l'ai utilisé avec de bons résultats dans une table avec des images volumineuses et lentes à charger même après leur téléchargement.

0
Lescai Ionel

https://github.com/nicklockwood/FXImageView

Ceci est une vue d'image qui peut gérer le chargement en arrière-plan.

Usage

FXImageView *imageView = [[FXImageView alloc] initWithFrame:CGRectMake(0, 0, 100.0f, 150.0f)];
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.asynchronous = YES;

//show placeholder
imageView.processedImage = [UIImage imageNamed:@"placeholder.png"];

//set image with URL. FXImageView will then download and process the image
[imageView setImageWithContentsOfURL:url];

Pour obtenir une URL pour votre fichier, vous trouverez peut-être les éléments suivants intéressants:

Obtention des références de fichier/chemins au lancement de l’application

0
Marc

Une façon que j’ai implémentée est la suivante: (Bien que je ne sache pas si c’est le meilleur)

Au début, je crée une file d'attente en utilisant Serial Asynchronous ou Parallel Queue

queue = dispatch_queue_create("com.myapp.imageProcessingQueue", DISPATCH_QUEUE_SERIAL);**

or

queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);

**

Lequel que vous puissiez trouver mieux pour vos besoins.

Ensuite:

 dispatch_async( queue, ^{



            // Load UImage from URL
            // by using ImageWithContentsOfUrl or 

            UIImage *imagename = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];

            // Then to set the image it must be done on the main thread
            dispatch_sync( dispatch_get_main_queue(), ^{
                [page_cover setImage: imagename];
                imagename = nil;
            });

        });
0
hyphestos