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

j'ai un uivi avec un UIImageView subview. Je dois charger une image dans UIImageView sans bloquer L'interface utilisateur. L'appel de blocage semble être: UIImage imageNamed: . Voici ce que je pensais résoudre 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. Ce qui me manque ?

voici un petit échantillon de code qui montre ce comportement:

créer une nouvelle classe basée sur UIImageView, définir son interaction utilisateur à YES, ajouter deux instances dans UIView, et mettre en œuvre sa méthode touchesBegan comme ceci:

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

assignez la balise 1 à l'un de ces imageViews.

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

voici un projet sur github avec le code ipmcc

utilisez une longue presse puis faites glisser pour dessiner un rectangle autour des carrés noirs. Comme je comprends sa réponse, en théorie le rectangle de sélection blanc ne devrait pas être bloqué la première fois que l'image est chargée, mais il est effectivement.

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

format d'Image

Je ne comprends pas vraiment comment cela est lié au problème que j'ai encore avec L'interface utilisateur étant bloqué alors que 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 0 step 1

le blocage de l'INTERFACE utilisateur commence ici..

step 2

.. et se termine ici.

step 3

AFNetworking solution

    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 est aussi occasionnellement grumes: success: {0, 0}

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

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

24
demandé sur alecail 2013-10-04 14:37:19

10 réponses

le problème est que UIImage ne lit et ne décode l'image que la première fois qu'elle est utilisée/dessinée. Pour forcer ce travail à se produire sur un fil d'arrière-plan, vous devez utiliser/Dessiner l'image sur le fil d'arrière-plan avant de faire le fil 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'UI ne bloque pas. Vous 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 en train de traiter les événements, mais rien ne va se passer jusqu'à ce que ce GR times out. Si vous faites cela:

    longpress.minimumPressDuration = 0.01;

...vous remarquerez qu'il obtient beaucoup plus vif. Le chargement d'image n'est pas le problème ici.

EDIT 2: j'ai regardé le code, tel que posté sur github, tournant sur un iPad 2, et je n'ai tout simplement pas le hoquet que vous décrivez. En fait, il est tout à fait lisse. Voici une capture d'écran de l'exécution du code dans la Coréanimation instrument:

enter image description here

comme vous pouvez le voir sur le graphique du haut, le FPS va jusqu'à ~60FPS et reste là tout au long du geste. Sur le graphique du bas, vous pouvez voir le blip à environ 16s qui est l'endroit où l'image est d'abord chargée, mais vous pouvez voir qu'il n'y a pas de baisse dans le taux de cadre. Juste après l'inspection visuelle, je vois la couche de sélection se croiser, et il y a un petit, mais observable retard entre la première intersection et l'apparence de l'image. Pour autant que je sache, le code de chargement de l'arrière-plan fait son travail comme prévu.

j'aimerais pouvoir vous aider plus, mais je ne vois pas le problème.

52
répondu ipmcc 2013-10-14 11:26:38

vous pouvez utiliser AFNetworking bibliothèque , dans laquelle en important la catégorie

" UIImageView+AFNetworking.m" et en utilisant la méthode comme suit :

[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];

espérons que cette aide .

5
répondu itechnician 2013-10-14 08:02:36

j'ai eu un problème très similaire avec mon application où j'ai dû télécharger beaucoup d'images et avec cela mon interface utilisateur était continuellement mise à jour. Ci-dessous, le lien tutoriel simple qui a résolu mon problème:

NSOperations & NSOperationQueues Tutorial

4
répondu Geekoder 2013-10-14 14:56:03

c'est la bonne voie:

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

        UIImage * img = [UIImage imageNamed:@"background.jpg"];
        [[self imageView] setImage:img];
    });
}
3
répondu Mirko Catalano 2013-10-04 10:41:00

pourquoi n'utilisez-vous pas une bibliothèque tierce comme AsyncImageView ? En utilisant ceci, tout ce que vous avez à faire est de déclarer votre objet AsyncImageView et de passer l'url ou l'image que vous voulez charger. Un indicateur d'activité s'affichera pendant le chargement de l'image et rien ne bloquera l'interface utilisateur.

2
répondu Luciano Rodríguez 2013-10-04 11:23:03

- (vide)touchesBegan: est appelé dans le fil principal. En appelant dispatch_async (dispatch_get_main_queue), vous mettez le bloc dans la file d'attente. Ce bloc sera traité par GCD lorsque la file d'attente sera prête (c.-à-d. que le système est terminé avec le traitement de vos touches). C'est pourquoi vous ne pouvez pas voir votre woodenTile chargé et assigné à soi-même.image jusqu'à ce que vous relâchiez votre doigt et laissiez GCD traiter tous les blocs qui ont été placés dans la file d'attente principale.

remplaçant :

    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 que des expositions.

1
répondu Patrock 2013-10-15 22:07:16

envisagez d'utiliser SDWebImage : non seulement il télécharge et Cache l'image en arrière-plan, mais aussi le charge et le rend.

Je l'ai utilisé avec de bons résultats dans un tableview qui avait de grandes images qui étaient lentes à charger même après le téléchargement.

0
répondu Lescai Ionel 2013-10-15 06:15:41

https://github.com/nicklockwood/FXImageView

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

Utilisation

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 pourriez trouver l'intéressant suivant:

prise en bundle références de fichier / chemins d'accès à l'application se lance

0
répondu Marc 2017-05-23 12:17:55

lorsque vous utilisez AFNetwork dans une application, vous n'avez pas besoin d'utiliser un bloc pour charger l'image parce que AFNetwork fournit la solution pour cela. Comme ci-dessous:

#import "UIImageView+AFNetworking.h"

et

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

Merci

0
répondu Hindu 2013-10-15 11:11:42

L'une des façons dont je l'ai mis en œuvre est la suivante: (même si Je ne sais pas si c'est la meilleure)

dans un premier temps, je crée une file d'attente en utilisant L'asynchrone en série ou la file D'attente parallèle

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

or

queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);

* *

que vous trouverez toujours mieux pour vos besoins.

après:

 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
répondu hyphestos 2014-06-18 07:45:02