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
le blocage de l'INTERFACE utilisateur commence ici..
.. et se termine ici.
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.
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:
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.
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 .
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:
c'est la bonne voie:
-(void)updateImageViewContent {
dispatch_async(dispatch_get_main_queue(), ^{
UIImage * img = [UIImage imageNamed:@"background.jpg"];
[[self imageView] setImage:img];
});
}
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.
- (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.
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.
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
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
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;
});
});