I have a "loading" view added to an uiimageview to indicate the image is being loaded, then on a dispatch async I am charging the image and removing the "loading" view once it has finished, problem is that if I call twice this method the "loading" view is added twice and the second one is never removed.
Edited:
on view.h
UIView *loading;
on view.m
loading = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
loading.backgroundColor = [UIColor blackColor];
loading.alpha = 0.8;
loading.layer.cornerRadius = 10;
loading.center = CGPointMake(imageView.frame.size.width/2, imageView.frame.size.height/2);
if (![imageView.subviews containsObject:loading]) {
[imageView addSubview:loading];
}
dispatch_queue_t downloadFoto = dispatch_queue_create("Get Photo", NULL);
dispatch_async(downloadFoto, ^{
UIImage *image = [[UIImage alloc]initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[selectedImage objectForKey:#"url"]]]];
dispatch_sync(dispatch_get_main_queue(), ^{
if (image) {
[imageView setImage:image];
[imageView setNeedsLayout];
if ([imageView.subviews containsObject:loading]) {
[loading removeFromSuperview];
}
}
});
});
If this is called just once or if I call it after the loading is already removed everything works fine, the problem is if I call this before the block had finished.
Thank you guys, this is my solution at the end.
if (!blackView) {
UIActivityIndicatorView *load = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
blackView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 100, 100)];
blackView.backgroundColor = [UIColor blackColor];
blackView.alpha = 0.8;
blackView.layer.cornerRadius = 10;
blackView.center = CGPointMake(fotoVisual.frame.size.width/2, fotoVisual.frame.size.height/2);
load.center = CGPointMake(blackView.frame.size.width/2, blackView.frame.size.height/2);
[load startAnimating];
[blackView addSubview:load];
[fotoVisual setImage:[UIImage imageNamed:#"previewImagen.png"]];
descripcionFotoView.text = [selectedImage objectForKey:#"titulo"];
}
if (![fotoVisual.subviews containsObject:blackView]) {
[fotoVisual addSubview:blackView];
}
dispatch_queue_t downloadFoto = dispatch_queue_create("Get Photo", NULL);
dispatch_async(downloadFoto, ^{
[fotoVisual setImageWithURL:[NSURL URLWithString:[selectedImage objectForKey:#"url"]]
placeholderImage:[UIImage imageNamed:#"previewImagen.png"]];
dispatch_async(dispatch_get_main_queue(), ^{
[blackView removeFromSuperview];
blackView = nil;
});
});
dispatch_release(downloadFoto);
It looks like the issue you have is that you are creating a new loading view each time, which is not what you want. Your [imageView.subviews containsObject:loading] will never be true as you make a new loading view each time.
You could change the creation logic to do the checking like this
if (!loading) {
// configure loading
[imageView addSubview:loading];
}
Then modify your call back to something like
dispatch_sync(dispatch_get_main_queue(), ^{
if (image) {
[imageView setImage:image];
[imageView setNeedsLayout];
[loading removeFromSuperview];
loading = nil;
}
});
Is there any specific reason you are using dispatch_sync instead of dispatch_async?
Add you loading subview in dispatch_async block
dispatch_queue_t downloadFoto = dispatch_queue_create("Get Photo", NULL);
dispatch_async(downloadFoto, ^{
if (!loading.superview) {
// we assume your loading view doesn't have a superview so we can add it to imageView
dispatch_sync(dispatch_get_main_queue(), ^{
[imageView addSubview:loading];
};
}
UIImage *image = [[UIImage alloc]initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[selectedImage objectForKey:#"url"]]]];
if (image) {
dispatch_sync(dispatch_get_main_queue(), ^{
[imageView setImage:image];
[imageView setNeedsLayout];
if ([imageView.subviews containsObject:loading]) {
[loading removeFromSuperview];
}
});
}
});
For Async image downloading you can also use SDWebimage
Just download the project and add SDWebimage folder to your project and use as folows
#import "UIImageView+WebCache.h"
[self.imageView setImageWithURL:[NSURL URLWithString:[selectedImage objectForKey:#"url"]]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
This will also cache the image.
Related
I have a UICollectionView displaying a bunch of images. If I don't load the images asynchronously the scrolling is very choppy and provides a poor user experience. When I load the images asynchronously the scrolling is smooth but it takes a good 5 to 10 seconds to load each image.
Why does it take so long for images to appear when loaded in the background? Here is my code for the background thread which is inside of the cellForItemAtIndexPath delegate:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImageView *bg = (id)self.backgroundView;
UIImageView *selbg = (id)self.selectedBackgroundView;
if (![bg isKindOfClass:[UIImageView class]])
bg = [[UIImageView alloc] initWithImage:thumb];
else
[bg setImage:thumb];
if (![selbg isKindOfClass:[UIImageView class]]){
selbg = [[UIImageView alloc] initWithImage:thumb];
coloroverlay = [[UIView alloc] initWithFrame:selbg.bounds];
[coloroverlay setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[selbg addSubview:coloroverlay];
} else
[selbg setImage:thumb];
[bg setContentMode:UIViewContentModeScaleAspectFill];
[bg setTag: 1];
[coloroverlay setBackgroundColor:[col colorWithAlphaComponent:0.33f]];
[selbg setContentMode:UIViewContentModeScaleAspectFill];
dispatch_sync(dispatch_get_main_queue(), ^{
[self setBackgroundView:bg];
[self setSelectedBackgroundView:selbg];
});
});
EDIT: As #geraldWilliam pointed out, I shouldn't be accessing views from the secondary thread. Here is what I have updated my code to and fixed the issue of images getting set to the wrong cell:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImageView *bg = (id)self.backgroundView;
UIImageView *selbg = (id)self.selectedBackgroundView;
if (![bg isKindOfClass:[UIImageView class]]) bg = [[UIImageView alloc] initWithImage:thumb];
if (![selbg isKindOfClass:[UIImageView class]]){
selbg = [[UIImageView alloc] initWithImage:thumb];
coloroverlay = [[UIView alloc] initWithFrame:selbg.bounds];
[coloroverlay setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[selbg addSubview:coloroverlay];
}
dispatch_sync(dispatch_get_main_queue(), ^{
[bg setImage:thumb];
[selbg setImage:thumb];
[bg setContentMode:UIViewContentModeScaleAspectFill];
[bg setTag: 1];
[coloroverlay setBackgroundColor:[col colorWithAlphaComponent:0.33f]];
[selbg setContentMode:UIViewContentModeScaleAspectFill];
[self setBackgroundView:bg];
[self setSelectedBackgroundView:selbg];
});
});
Most of the code you have here is fine for the main queue. The loading of the image should be on a global queue, but the rest, especially setting the image view's image, should be on the main queue. What's going on in your code is that you're dispatching back to the main queue to set the background view but leaving the assignment of the image property in the background. So, try something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:myImageURL]];
dispatch_sync(dispatch_get_main_queue(), ^{
imageView.image = image;
[self setBackgroundView:imageView];
});
});
I strongly recommend watching WWDC 2012 Session 211: Building Concurrent User Interfaces on iOS, which is the best WWDC session ever. It’s full of clearly presented, practical advice.
Doing stuff with UIImageView off the main queue is worrying and should be fixed, but is probably not the cause of slowness. You haven’t showed us where thumb comes from, which is likely the slow bit.
I am creating many UIImageview using for loop like a wallpaper app. And i am adding UIActivityIndicatorView to each UIImageview. UIImageview load image from the webservice. when the image is loaded to the UIImageview that time the UIActivityIndicatorView of this UIImageview is hide.
I am give tag of both UIImageview and UIActivityIndicatorView.
Please tell me how do this.
Just use a completion block for the image fetching.
dispatch_queue_t q = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(q, ^{
// Fetch the image from the server
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *img = [[UIImage alloc] initWithData:data];
// Add activity indicatory to image view
[imageview addSubview:activityIndicator];
dispatch_async(dispatch_get_main_queue(), ^{
imageview.image = img;
// After finishing image loading remove activity indicator
[activityIndicator stopAnimating];
[activityIndicator removeFromSuperview];
});
});
if you have a reference to an instance of UIActivityIndicatorView, just remove it from superView:
[activityIndicator stopAnimating];
[activityIndicator removeFromSuperview];
After build code, it doesn't have any animation on my cellphone
but the code looks good, i don't know how to figure it out ,
and my image is 829 * 445, does it cause this problem ?
Could someone help me to solve this problem i will really appreciate it, thanks
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSArray *animationImages = [NSArray arrayWithObjects:[UIImage imageNamed:#"1.jpg"],
[UIImage imageNamed:#"2.jpg"],
[UIImage imageNamed:#"3.jpg"],
[UIImage imageNamed:#"4.jpg"],nil];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,150,150)];
imageView.animationImages = animationImages ;
imageView.animationRepeatCount = 2;
imageView.animationDuration= 4.0;
[imageView startAnimating];
[NSTimer scheduledTimerWithTimeInterval:4.0 target:self
selector:#selector(animationDone:)
userInfo:nil repeats:NO];
}
-(void)animationDone:(NSTimer*)inTimer
{
[inTimer invalidate];
inTimer = nil;
NSLog(#"animationDone ");
}
#end
You don't added imageView to your ViewController view.
Try [self.view addSubview:imageView]; in -viewDidLoad
Please use animation block code instead of the older method. The animation will tell you when its done. You don't need to set a timer.
NSArray *animationImages = [NSArray arrayWithObjects:[UIImage imageNamed:#"1.jpg"],
[UIImage imageNamed:#"2.jpg"],
[UIImage imageNamed:#"3.jpg"],
[UIImage imageNamed:#"4.jpg"],nil];
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,150,150)];
[UIView animateWithDuration:1.0f animations:^{
for (int loop = 0; loop < 4; loop++) {
UIImage *image = [UIImage imageNamed:[NSString stringWithFornmat:#"%d.jpg", (loop + 1)]];
[imageView setImage:image];
}
} completion:^(BOOL finished) {
NSLog(#"animationDone");
}];
It looks like you're not adding the animating UIImageView to your view hierarchy. Right before your [imageView startAnimating]; line add the below line of code:
[self.view addSubview:imageView];
When app launches I add UIImageViews to screen with no image. Then I load image one by one using NSThread and set it for those views. The UiImageViews remain blank until after all images have been loaded and the thread finishes. Why doesn't the image show as soon as I set it for the UIImageView, why does it wait for NSThread to end?
To add image views :
for(i = value; i < val; i++){
UIButton *newbutton = [UIButton buttonWithType:UIButtonTypeCustom];
[newbutton setBackgroundColor:[UIColor whiteColor]];
UIImageView *newback = [[UIImageView alloc] init];
newback.contentMode = UIViewContentModeScaleAspectFit;
[newback setFrame:CGRectMake(0, 0, 140, 140)];
[newbutton addSubview:newback];
height = MIN(300, newSize.height);
[newbutton setFrame:CGRectMake(currentX, currentY, width, height)];
newbutton.layer.cornerRadius = 10;
newbutton.clipsToBounds = YES;
[newbutton addTarget:self action:#selector(processButtonClick:) forControlEvents:UIControlEventTouchUpInside];
}
To add images in a thread I call a function which does following :
for(i = value; i < val; i++){
UIImage *newimage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#“http://test.com/a.jpg”, hash]]]];
UIButton *newbutton = (UIButton*)[self.subviews objectAtIndex:i];
[((UIImageView*)[newbutton.subviews objectAtIndex:0]) newimage];
}
Generally speaking, UIKit is not thread-safe. Fetch data from URL in a background thread and set the image in the main thread.
EDIT:
In your main thread do something like
dispatch_async(someDispatchQ, ^{
[self functionToGetData];
});
and inside functionToGetData do
for(i = value; i < val; i++){
NSData *data = //get Data
UIImage *newImage = //make image from data
dispatch_async(dispatch_get_main_queue(), ^{
[imageView setImage:newImage];
});
}
This will ensure that you are using a background thread to fetch data/make image and using the main thread to set the image. This also eliminates the need to use a timer to constantly poll the background thread since the background thread automatically dispatches image-setting to the main thread as soon as an image is received.
use-
import class ImageDownloader.h & .m
link-> https://github.com/psychs/imagestore/tree/master/Classes/Libraries/ImageStore
//Now use this method to download
-(void)downloadImageWithURL:(NSString *)url{
if(self.downloder)return; //If already downloading please stay away
url=[NSString stringWithFormat:#"%#%0.0fx%0.0f/%#/%#",IMAGE_ENDPOINT,self.imgViewExhibitors.frame.size.width,self.imgViewExhibitors.frame.size.height,#"fit",[ImagePath forURLString:url]];
self.downloder=[[ImageDownloader alloc] init];
self.downloder.delegate=self;
self.downloder.indicator=self.indicator;
[self.downloder downloadImageWithURL:url];
}
-(void)imageDownloaded:(UIImage *)image forIndexPath:(NSIndexPath *)indexPath{
if(image){
//Put the downloaded image in dictionary to update while scrolling
//[self.speakerData setObject:image forKey:#"image"];
self.imgViewExhibitors.image=image;
}
}
A simple fix would be
for(i = value; i < val; i++){
UIImage *newimage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#“http://test.com/a.jpg”, hash]]]];
UIButton *newbutton = (UIButton*)[self.subviews objectAtIndex:i];
// this ensures that the UI update (setting the image) is done on the main thread.
// This is important for the UI to update properly.
dispatch_async(dispatch_get_main_queue(), ^{
[((UIImageView*)[newbutton.subviews objectAtIndex:0]) setImage:newimage];
}
}
I'm trying to load images in the background using gcd. My first attempt didn't work:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
// create UI Image, add to subview
});
});
commenting out the background queue code and leaving just the main queue dispatch block didn't work:
dispatch_async(dispatch_get_main_queue(), ^{
// create UI Image, add to subview
});
Is there some trick to doing ui stuff inside a gcd block?
In response to mattjgalloway's comment, I'm simply trying to load a big image in a background thread. Here's the full code that I tried originally:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0), ^{
UIImage* img = [[UIImage alloc] initWithContentsOfFile: path];
dispatch_async(dispatch_get_main_queue(), ^{
imageView = [[[UIImageView alloc] initWithImage: img] autorelease];
imageView.contentMode = UIViewContentModeScaleAspectFill;
CGRect photoFrame = self.frame;
imageView.frame = photoFrame;
[self addSubview:imageView];
});
});
I simplified it so that I was running everything inside the main queue, but even then it didn't work. I figure if I can't get the whole thing to work in the main queue, no way the background queue stuff will work.
================ UPDATE ==============
I tried the above technique (gcd and all) in a brand new project and it does indeed work as expected. Just not in my current project. So I'll have to do some slow, painful process of elimination work to figure out what's going wrong. I'm using this background loading to display images in a uiscrollview. Turns out bits of the images do sometimes show up, but not always. I'll get to the bottom of this....
================ UPDATE ==============
Looks like the issue is related to the UIScrollView all of this is inside. I think stuff isn't getting drawn/refreshed when it should
I ran your code. It works with one exception. Also did you mean self.view rather than self?
Case 1:
path is declared at a property.
imageView is declared at an ivar
- (IBAction)buttonImagePressed:(id)sender
{
self.path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"picture1.jpg"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0), ^{
UIImage* img = [[UIImage alloc] initWithContentsOfFile: self.path];
dispatch_async(dispatch_get_main_queue(), ^{
imageView = [[[UIImageView alloc] initWithImage: img] autorelease];
imageView.contentMode = UIViewContentModeScaleAspectFill;
CGRect photoFrame = self.view.frame;
imageView.frame = photoFrame;
[self.view addSubview:imageView];
});
});
}
Case 2:
path and imageView are both ivars. - no property is used.
- (IBAction)buttonImagePressed:(id)sender
{
// will crash if path is defined here
// path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"picture1.jpg"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0), ^{
//if path is defined here then it will work
path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"picture1.jpg"];
UIImage* img = [[UIImage alloc] initWithContentsOfFile:path];
dispatch_async(dispatch_get_main_queue(), ^{
imageView = [[[UIImageView alloc] initWithImage: img] autorelease];
imageView.contentMode = UIViewContentModeScaleAspectFill;
CGRect photoFrame = self.view.frame;
imageView.frame = photoFrame;
[self.view addSubview:imageView];
});
});
}
On case 2, where it crashed, the message in the console said to file a bug at apple.com ...
Try to call setNeedDisplay right after [self addSubview:imageView], by the way most of UIKit isn't thread safe, I don't know about UIImage methods specifically (maybe is ok on iOS4 and higher), but I wouldn't do that. If you want to load images from background better use ImageIO that is thread safe, or Core Graphics functions.
An alternative could be load an NSData object with your image data on a background thread an later call [UIImage alloc]inithWithData: data] on the main thread.
Displaying my image as a background image, rather than a UIImageView added as a subview works. No clue why
#interface PhotoView(){
UIImageView* imageView;
}
#end
#implementation PhotoView
-(id)initWithFrame:(CGRect)frame andPathToPhoto:(NSString*)path andSequenceView:(SequencerView*) sequencerView{
if(self = [super initWithFrame:frame]){
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,0), ^{
UIImage* img = [[UIImage alloc] initWithContentsOfFile: path];
dispatch_async(dispatch_get_main_queue(), ^{
self.backgroundColor = [UIColor colorWithPatternImage:img];
});
[img release];
});
}
return self;
}
#end