Image not showing on preloaded UIImageView - ios

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

Related

Is there a memory impact from not using returned function values?

Just a general question about the best practices for functions which return values. Say for example I have the following function (pseudo code):
- (UIImageView *)createImageViewAndAddWithImageName:(NSString *)sName
{
UIImageView *iv = nil;
if (sName)
{
UIImage *image = [UIImage imageNamed:sName];
if (image)
{
iv = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
[iv setImage:image];
[iv setContentMode:UIViewContentModeScaleAspectFit];
[self.view addSubview:iv];
}
}
return iv;
}
Now in some instances I want to perform further modifications like below:
UIImageView *iv = [self createImageViewAndAddWithImageName:#"Blah"];
[iv setAlpha:0.5f];
Where-as other times I want to just add an image:
[self createImageViewAndAddWithImageName:#"Blah"];
In the second instance, I presume that the memory will just be autoreleased? In the second instance, if this was running in a loop would it make sense to use an auto release block like below:
for (int i = 0; i < 100; i++)
{
#autoreleasepool {
[self createImageViewAndAddWithImageName:#"Blah"];
}
}
Just to free up the memory sooner? And is there any memory impact in having an autorelease pool in the main function like below too?
- (UIImageView *)createImageViewAndAddWithImageName:(NSString *)sName
{
UIImageView *iv = nil;
#autoreleasepool {
if (sName)
{
UIImage *image = [UIImage imageNamed:sName];
if (image)
{
iv = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
[iv setImage:image];
[iv setContentMode:UIViewContentModeScaleAspectFit];
[self.view addSubview:iv];
}
}
}
return iv;
}
I just want to make sure that not using a returned value doesn't have a negative impact on my application, and that over-using autorelease pools doesn't either.
Thanks for any advice.
Every time the run loop calls out, it creates an autorelease pool. So all you have to do is to not allocate too much memory while doing anything caused by the run loop. For example if a user presses a button, your code handling this shouldn't allocate 100 MB of memory, even if autoreleased. Otherwise, autoreleased is reasonably cheap.

Why does loading images asynchronously take forever?

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.

How to avoid twice adding UIView on not ended block?

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.

Delay On IPad When Calling From A Background Thread

I am converting my IPhone app to IPad and I'm having an issue with the conversion. I have a background thread that creates some thumbnails.
The problem seems to be that the app loops though the array and outputs all the items but only the last item seems to be loading with the UIButton text present. There is a 5-15 second delay before all the previous buttons have there text displayed.
UIImage *tmp = [self thumbnailImage:[effect objectAtIndex:0] thumbnailType:#"category"];
for (id effect in effectArray) {
UIImage *tmp = [self thumbnailImage:[effect objectAtIndex:0] thumbnailType:#"category"];
dispatch_async(dispatch_get_main_queue(), ^{
UIView *effectView = [[UIView alloc] initWithFrame:CGRectMake(item_x_axis, current_row_height, item_width, item_height)];
UIImageView *imagepreview = [[UIImageView alloc] init];
[imagepreview setFrame:CGRectMake(20, 20, 100, 100)];
[imagepreview setImage:tmp];
imagepreview.contentMode = UIViewContentModeScaleAspectFit;
imagepreview.layer.shadowColor = [UIColor blackColor].CGColor;
imagepreview.layer.shadowOffset = CGSizeMake(0, 1);
imagepreview.layer.shadowOpacity = 1;
imagepreview.layer.shadowRadius = 2.0;
imagepreview.clipsToBounds = NO;
[effectView addSubview:imagepreview];
if([[effect objectAtIndex:3] isEqualToString:#"iap"]){
UIImageView *buttonPlus = [[UIImageView alloc] init];
[buttonPlus setFrame:CGRectMake(54, 66, 23, 23)];
[buttonPlus setImage:[UIImage imageNamed:#"ButtonPlus.png"]];
[effectView addSubview:buttonPlus];
}
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:NSSelectorFromString([effect objectAtIndex:1]) forControlEvents:UIControlEventTouchDown];
[button setTitle:[effect objectAtIndex:2] forState:UIControlStateNormal];
button.frame = CGRectMake(0, 0, item_width, item_height);
button.titleLabel.font = [UIFont systemFontOfSize:11];
[button setTitleShadowColor:[UIColor blackColor] forState:UIControlStateNormal];
[button.titleLabel setShadowOffset:CGSizeMake(1.0f, 1.0f)];
[button setTitleEdgeInsets:UIEdgeInsetsMake(120, 0, 0.0, 0.0)];
[effectView addSubview:button];
[self.viewScrollCategories addSubview:effectView];
button = nil;
});
if(current_items_per_row < 3){
current_items_per_row++;
item_x_axis = item_x_axis + item_width;
}else {
current_row_height = current_row_height + item_height;
current_items_per_row = 1;
item_x_axis = 0;
}
}
Any idea how to solve this issue? it seemed to work fine on the IPhone.
EDIT
This is the code that is calling the background thread (shortned)
- (void)imagePickerController:( UIImagePickerController *)picker didFinishPickingMediaWithInfo: (NSDictionary *)info {
//[self generatingThumbnailMessageShow];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self loadAllEffects];
dispatch_async(dispatch_get_main_queue(), ^{
[self generatingThumbnailMessageHide];
});
});
}
Well, you are asking for a delay with dispatch_async(dispatch_get_main_queue().... That code means "Please do this whenever you have time, sometime after my current code finishes running." Once you say that, you are basically saying you don't care when this code runs. You hope it will be some time pretty soon, but you've left the runtime to take care of the details.
So perhaps it is this "hope" that is the issue. The architecture of the device processors is different and the threading model for the device might change the way the device responds to having a bunch of these delayed requests piling up.
I'm not at all clear on what you're trying to accomplish in this code (sorry, tl:dr) but the use of delayed performance in the middle of it and your complaint that there is a " 5-15 second delay" seem to go together somehow...
EDIT: OK, I see now that your code is running in the background and you are using dispatch_async to step out to the main thread in order to modify the interface. So perhaps the problem is not in the code you quote, but in the code you are using to manage your background threading in the first place.
ANOTHER EDIT: Just a wild and crazy idea here. If what takes time is the image processing, why don't you do all the image processing and then do your interface updating on the main thread once? What I mean is, you're doing this:
for (id effect in effectArray) {
UIImage *tmp = // ...
dispatch_async(dispatch_get_main_queue(), ^{
// ...
[imagepreview setImage:tmp];
So you're getting back on the main thread repeatedly each time through the loop. Why not try this:
dispatch_async(dispatch_get_main_queue(), ^{
for (id effect in effectArray) {
UIImage *tmp = // ...
// ...
[imagepreview setImage:tmp];
Now you're just getting back on the main thread once. There will be a delay while the images process, but then interface should just update, badda bing badda boom (technical programming term).

Load UIImage from file using grand central dispatch

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

Resources