I have created collection view programmatically where i have created UIImageview programmatically within collectionViewCell and images are displayed in imageview are downloaded from server asynchronously.
Code below is written in cellForItemAtIndexPath: method -
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSURL *imageURL = [NSURL URLWithString:[arrmImgPaths objectAtIndex:indexPath.row]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
// [imgvMainView removeFromSuperview];
// Now the image will have been loaded and decoded and is ready to rock for the main thread
dispatch_sync(dispatch_get_main_queue(), ^{
imgvMainView =[[UIImageView alloc] initWithFrame:CGRectMake(34,41,177,124)];
[cell addSubview:imgvMainView];
[imgvMainView setImage:image];
imgvMainView.contentMode = UIViewContentModeScaleAspectFit;
});
});
Problem is, when i scroll collection view some images are overlapped on one another. Please tell me solution to avoid it.
Thanks in advance.
Please use below line I hope this would work what I am assuming problem of duplicate cell.
UIImageView* imgvMainView =[[UIImageView alloc] initWithFrame:CGRectMake(34,41,177,124)];
[cell.contentView addSubview:imgvMainView];
This is most likely happening because you are reusing cells (correctly) and that the previous use of the cell is still calling the previous image. You must cancel the async task when the cell goes out of view. By the way, it will make you life much easier to use AFNetworking+UIImageView to do this.
One other option is to check that there is no UIimageView in your cell before you create one.
dispatch_sync(dispatch_get_main_queue(), ^{
UIView *viewToRemove;
for (UIView *view in cell.subViews) {
if (view isKingOfClass:UIImageView){
viewToRemove = view;
}
}
[viewToRemove removeFromSuperView];
imgvMainView =[[UIImageView alloc] initWithFrame:CGRectMake(34,41,177,124)];
[cell addSubview:imgvMainView];
[imgvMainView setImage:image];
imgvMainView.contentMode = UIViewContentModeScaleAspectFit;
});
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];
I have a question about table view cells.
In cell I have an UIImageView. This view loads from web service with getter:
- (UIImageView *)imageView
{
if (!_ imageView)
{
_imageView = [[TurnipImageView alloc] init];
_imageView.backgroundColor = [UIColor clearColor];
}
[_imageView loadImageViewFromWebService];
return _imageView;
}
When I add [_imageView loadImageViewFromWebService]; call inside of if (!_ imageView) statement, image view is not loaded correctly.
When I scroll table view, cells are reloading as well as image views and causing lags in scrolling.
Maybe anyone knows how to optimise this process?
You should try lazy loading of the table view :
here is the sample : https://developer.apple.com/library/ios/samplecode/LazyTableImages/Introduction/Intro.html
also have a look at this :
Lazy loading UITableView with multiple images in each cell
Try this
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
//perform download on background thread
dispatch_async(dispatch_get_main_queue(), ^{
//assign image to imageview on main thread, UI operations should be carried on main thread
});
});
- (UIImageView *)imageView
{
if (!_ imageView)
{
_imageView = [[TurnipImageView alloc] init];
_imageView.backgroundColor = [UIColor clearColor];
}
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
//perform download on background thread
[_imageView loadImageViewFromWebService];
});
return _imageView;
}
Hope this helps
I have added this UICollectionViewDelegate with my implementation:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
SAPCollectionViewCell *collectionViewCell = (SAPCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:#"SAPCollectionViewCell" forIndexPath:indexPath];
NSInteger index = indexPath.item + indexPath.section * 2;
NSString *imagePath = [_images objectAtIndex:index];
collectionViewCell.index = index;
[collectionViewCell setImageWithPath:imagePath];
collectionViewCell.delegateCell = self;
return collectionViewCell;
}
In my custom cell I have this method:
- (void)setImageWithPath:(NSString *)imagePath
{
if (!self.imageView)
{
CGRect rect = self.contentView.frame;
self.imageView = [[UIImageView alloc] initWithFrame:rect];
[self.contentView addSubview:self.imageView];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageFromPath:imagePath];
UIImage *resizedImage = [UIImage imageWithImage:image scaledToWidth:rect.size.width];
dispatch_async(dispatch_get_main_queue(), ^{
[self.imageView setImage:resizedImage];
});
});
}
}
So as you can see if the cell does not have UIImageView then I start to create it and in background I get image from local path and resize it to cell content view width, then in main queue I set image to UIImageView.
This part work good but when I try to scroll my UICollectionView e.g. with 10 images I noticed that for example if first 2 top images were disappeared when I scroll down and then when I scroll back to the top, the images are changed placed.
This is state of images before scrolling down:
And this state after I scroll UIColectionView back to the top:
So as you can see first to item changed theirs location. As I have set if (!self.imageView) in my code above it should means that the image never will be created twice or more times.
I opened debugger and was checking this method:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
So the images paths returned one by one as it worked when UICollectionView just created cells at first time.
At first time first UICollectionViewCell has 0x8fbc1d0
and second has 0x8d0a4c0 address
But when I scroll down and then scroll up the debugger shown me second address at first 0x8d0a4c0 and then shown me 0x8fbc1d0
But I can't understand why items change theirs order. Or maybe here is another issue?
Also I don't have any method in the code that make for example for me reorder for cell. just few UICollection view methods delegate that configure count of cells and create them.
Also if I remove if (!self.imageView) seems everything works good, but then my code every time invoke dispatch that's no good for me, because when I resize image to the cell size I don't need do it twice.
When you scroll the two items off screen, your CollectionView puts the associated cells back onto the 'queue'. When you scroll back up again, the dequeueResuableCellwithReuseIdentifier in cellForItemAtIndexPath retrieves them from that queue, but (as you've seen) not necessarily in the same order. Thus the cell which was item 0 has been used for item 1 and vice versa. As you've seen, the if (!self.imageView) actually stops the correct image from being loaded. If you want to avoid retrieving and resizing them each time, then I would store the (resized) imageViews in an array.
- (void)setImageWithPath:(NSString *)imagePath
{
if (!self.imageView)
{
CGRect rect = self.contentView.frame;
self.imageView = [[UIImageView alloc] initWithFrame:rect];
[self.contentView addSubview:self.imageView];
}
if (![self.imagePath isEqualsToString:imagePath] && self.imageView.image == nil)
{
self.imagePath = imagePath;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageFromPath:imagePath];
UIImage *resizedImage = [UIImage imageWithImage:image scaledToWidth:rect.size.width];
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.imagePath isEqualsToString:imagePath])
{
[self.imageView setImage:resizedImage];
}
});
});
}
}
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