UICollectionViewCell not setting frames of labels when image is nil - ios

What I am trying to do, I am shifting the UILabel Frames and UITextView upwards when there is no image.
It was working correctly when i do this thing locally.But now as the images are downloading from server and when i check for nil image, it is giving me wrong output.
I am trying horizontal scrolling
Here is the code what I tried
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *myCell=nil;
myCell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell"
forIndexPath:indexPath];
UIImageView *ImageView=(UIImageView*)[myCell viewWithTag:9];
UILabel *questionLabel=(UILabel*)[myCell viewWithTag:567];
UITextView *answerTextView=(UITextView*)[myCell viewWithTag:6];
questionLabel.text=[_questionsArray objectAtIndex:indexPath.row];
answerTextView.text=[_answerArray objectAtIndex:indexPath.row];
[ImageView setImageWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"http:/%#",[[_arrayOfDictionary objectAtIndex:indexPath.row] objectForKey:#"image"]]] ];
if (ImageView.image==nil) {
//Always Satisfying this condition
//Set Frame
return myCell;
}else{
return myCell;
}
}
Please provide me with solution.And also the reason Why it is satisfying if statement every-time.
(Autolayout is unchecked)

Add the AFNetworking for asynchronous solution of your image view update like
if (userBasicInfo.userImage == nil) {
__weak LGMessageBoxCell *weakCell = cell;
[cell.userImage setImageWithURLRequest:[[NSURLRequest alloc] initWithURL:[NSURL URLWithString:userBasicInfo.imageUrl]]
placeholderImage:[UIImage imageNamed:#"facebook-no-user.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image){
weakCell.userImage.image = image;
[weakCell setNeedsLayout];
// reload your tableView current cell here
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error){
}];
}

Related

Loading images from web and displaying it in a collectionview

I am using UICollectionView to display a set of images. The images are loaded from web. I get the image URLs from an online JSON file. The images are in different sizes and i use a flowlayout to put the images together. The challenge here is that you won't know the exact size of the images until they are loaded. I use SDWebImage to download and cache the images. I use a boolean value to check which images are loaded. After each image is loaded, i tell the collectionview to reload the image at its corresponding indexpath. Now the problem is that the images do load properly but the cells are all messed up when i scroll the collectionview. Here is some code:
This is where i calculate the size of each cell:
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(NHBalancedFlowLayout *)collectionViewLayout preferredSizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
if (didLoadImage == YES) {
return [loadedImage size];
}
else
{
return CGSizeMake(300, 140);
}
}
And here is where the images are loaded and set:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
GalleryCell *customCell = [collectionView
dequeueReusableCellWithReuseIdentifier:#"cell"
forIndexPath:indexPath];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[galleryLargeLinks objectAtIndex:indexPath.row]]];
customCell.backgroundColor = [UIColor blackColor];
[customCell.imageView setImageWithURLRequest:request placeholderImage:[UIImage imageNamed:#"placeholder"] success:^(NSURLRequest *request, NSHTTPURLResponse *response,UIImage *image)
{
loadedImage = image;
didLoadImage = YES;
[customCell.imageView setImage:image];
[collectionView reloadItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error)
{
NSLog(#"fail");
[activityIndicator stopAnimating];
}];
return customCell;
}
UICollectionViewCell's are reused whenever they are scrolled off screen. And so since you are using an asynchronous block to load them, the cell may likely be assigned to another indexPath by the time it is ready to go. You can continue using the setImageWithURLRequest: if you'd like, however, you should add another check to see if the indexPath is still what you expect by the time it loads. Something like this:
[customCell setIndexPath:indexPath];
__weak GalleryCell *weakCell = cell;
__weak CollectionView *weakCollectionView = collectionView;
[customCell.imageView setImageWithURLRequest:request placeholderImage:[UIImage imageNamed:#"placeholder"] success:^(NSURLRequest *request, NSHTTPURLResponse *response,UIImage *image)
{
if([weakCell.indexpath isEqual:indexPath]){
[weakCell.imageView setImage:image];
[weakCollectionView reloadItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
}
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error)
{
[activityIndicator stopAnimating];
}];
As for the collectionView:layout:preferredSizeForItemAtIndexPath:, you need to go about that check completely differently. One option could be to make your cached images key off your datasource, so you can just check the value in the datasource array and see if it's in the cache. If it is, grab the image, otherwise return a default value. That's too much code to write out, but basically you should be checking your datasource/cache and not just two random BOOL and UIImage variables that can change at anytime.

iOS UITableViewCell imageView not working

I initialize a tableviewcell with a small image while the large image is downloading. when the large image is downloaded I notify the tableView cell imageView to update the image but nothing happens.
This is my table view cell creation method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellID = #"EventTableViewCellID";
EventTableViewCell *cell = [self.eventsTableView dequeueReusableCellWithIdentifier:cellID forIndexPath:indexPath];
Event *e = [_events objectAtIndex:indexPath.row];
[cell.eventTitle setText:e.eventTitle];
if (e.largeEventImage) {
[cell.eventImageView setImage:[e largeEventImage]];
} else {
[cell.eventImageView setImage:[e thumbImage]];
}
cell.event = e;
cell.delegate = self;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
NSString *dateString = [_formatter stringFromDate:e.eventDate];
[cell.eventDate setText:dateString];
return cell;
}
This method (in the tableView cell) is called when a image is fetched and it is supposed to update the cell image view
- (void)imagesDownloaded {
if (self.event.largeEventImage) {
[self.eventImageView setImage:_event.largeEventImage];
} else {
[self.eventImageView setImage:_event.thumbImage];
}
}
you can use AFNetworking library for that.
there is a class AFNetworking + UIImageView
this is the method for load and display the image...
[imageView setImageWithURLRequest:request
placeholderImage:#"Your thumbImage"
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
yourImageView.image = image;
}
failure:nil];
hope this helps you to done your work.

In a UICollectionView how can I preload data outside of the cellForItemAtIndexPath to use within it?

Figured out slow loading images were the behind the choppy slow effect of my collectionView.
I've been reading different Q&A's all day and various forum posts. It looks like the best way to solve this issue is to have the data pre-loaded available for the cellForItemAtIndexPath to be able to take what it needs.
I'm not sure how I can do this. I'm using parse as my backend, but sure if given a rough example I'd be able to figure out how to do it. From what I've seen so far I need a separate method to grab the data.
Here is the code:
- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView
{
return 1;
}
-(NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return [[self objects] count];
}
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
NSArray *people = [self objects];
static NSString *CellIdentifier = #"Cell";
VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: CellIdentifier forIndexPath:indexPath];
PFObject *current;
current = [people objectAtIndex:indexPath.item];
PFFile *userImageFile = current[#"image"];
[userImageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
UIImage *image = [UIImage imageWithData:imageData];
[[cell contentView] setContentMode: UIViewContentModeScaleAspectFit];
[[cell imageView] setImage:image];
}];
[[cell title] setText:[current valueForKey:#"title"]];
[[cell price] setText:[NSString stringWithFormat: #"£%#", [current valueForKey:#"price"]]];
return cell;
}
So maybe the cellForItemAtIndexPath needs to call that method and take what it needs. Because the data would already be available it won't need to be loaded in the cellForItemAtIndexPath method and the cells will be populated immediately.
Please give suggestions and examples.
I was told a good way to do this would be to check for the image, if non existent provide a placeholder, if it does exist set it. Here are the changes to the above code.
Updates:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
NSArray *people = [self objects];
static NSString *CellIdentifier = #"Cell";
VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: CellIdentifier forIndexPath:indexPath];
PFObject *current;
current = [people objectAtIndex:indexPath.item];
PFFile *userImageFile = current[#"image"];
[userImageFile getDataInBackgroundWithBlock:^(NSData *imageData, NSError *error) {
if (!error)
{
if (!image) {
[[cell imageView] setImage:[UIImage imageNamed:#"placeholder.png"]];
} else {
image = [UIImage imageWithData:imageData];
//resize image
CGSize destinationSize = CGSizeMake(158,187);
UIGraphicsBeginImageContext(destinationSize);
[image drawInRect:CGRectMake(0,0,destinationSize.width, destinationSize.height)];
//New image
UIImage*newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//Optimise image
NSData *imageDataCompressed = UIImageJPEGRepresentation(newImage, 0.4f);
// NSLog(#"Image Size %#", NSStringFromCGSize(newImage.size));//log size of image
NSLog(#"%#", [current valueForKey:#"title"]);
[[cell imageView] setImage:[UIImage imageWithData:imageDataCompressed]];
}
}
}];
[[cell title] setText:[current valueForKey:#"title"]];
[[cell price] setText:[NSString stringWithFormat: #"£%#", [current valueForKey:#"price"]]];
return cell;
}
Place holder shows fine but remains, how do I know when the image has been loaded so I can make my cells reflect that?
Thanks for your time.
Kind regards.
Update:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
NSArray *people = [self objects];
static NSString *CellIdentifier = #"Cell";
VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: CellIdentifier forIndexPath:indexPath];
[cell.activityIndicator startAnimating];
PFObject *current;
current = [people objectAtIndex:indexPath.item];
PFFile *userImageFile = current[#"image"];
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:userImageFile.url, indexPath.item]];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataDontLoad
timeoutInterval:6.0];
[cell.imageView setImageWithURLRequest:urlRequest
placeholderImage:[UIImage imageNamed:#"placeholder.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
//resize image
CGSize destinationSize = CGSizeMake(158,187);
UIGraphicsBeginImageContext(destinationSize);
[image drawInRect:CGRectMake(0,0,destinationSize.width, destinationSize.height)];
//New image
UIImage*newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//Optimise image
NSData *imageDataCompressed = UIImageJPEGRepresentation(newImage, 0.4f);
cell.imageView.image = [UIImage imageWithData:imageDataCompressed];
NSLog(#"Image Size %#", NSStringFromCGSize(newImage.size));//log size of image
[cell.activityIndicator stopAnimating];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"Failed to download image: %#", error);
}];
return cell;
}
Latest Update:
Set up a method that gets data from parse.com and stores in an NSMutableDictionary then in a mutable array. I store the title, price and URL to image of the garment.
- (void)grabDataFromCloud
{
self.model = [NSMutableArray array];
for (PFObject *object in [self objects]) {
PFFile *imageFile = [object valueForKey:#"image"];
NSURL *url = [NSURL URLWithString:imageFile.url];
NSMutableDictionary *newObject = [NSMutableDictionary dictionaryWithDictionary:#{#"title": [object valueForKey:#"title"], #"price": [object valueForKey:#"price"], #"imageUrl": url}];
[[self model] addObject:newObject];
}
}
This gets called in my cellForItemsAtIndexPath method.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath object:(PFObject *)object
{
[self grabDataFromCloud];
static NSString *CellIdentifier = #"Cell";
VAGGarmentCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier: CellIdentifier forIndexPath:indexPath];
[cell.activityIndicator setHidden:YES];
NSMutableDictionary* d = [self.model objectAtIndex:indexPath.item];
cell.title.text = d[#"title"];
cell.price.text = [NSString stringWithFormat:#"£%#", d[#"price"]];
if (d[#"image"]) {
cell.imageView.image = d[#"image"];
} else { // if not, download it
cell.imageView.image = nil;
dispatch_queue_t backgroundQueue = dispatch_queue_create("test", 0);
dispatch_async(backgroundQueue, ^{
NSData* data = [NSData dataWithContentsOfURL:d[#"imageUrl"]];
UIImage* img = [UIImage imageWithData:data];
d[#"image"] = img;
dispatch_async(dispatch_get_main_queue(), ^{
//causes crash Assertion failure in -[UICollectionView _endItemAnimations],
// /SourceCache/UIKit/UIKit-2935.137/UICollectionView.m:3687
// [self.collectionView reloadItemsAtIndexPaths:#[indexPath]];
});
});
}
return cell;
}
I'd suggest you to use AFNetworking's UIImageView+AFNetworking category. It will handle the placeholder etc automatically, and will do everything in a background thread, ensuring that the main thread doesn't get blocked. Specifically, this is the method you'd want to call:
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholderImage;
It is up to you to supply a placeholder image (or nil) when the image is first needed and to start downloading it, and then to hang on to the image once it has been downloaded so that ever after that you can supply it instantly. This example is for a table view, but the principle is exactly the same; the key thing is that my data model is a bunch of NSMutableDictionary objects, and each dictionary in not only the url for the picture we are supposed to have but also a place for keeping the image once it has been downloaded:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
NSMutableDictionary* d = (self.model)[indexPath.row];
cell.textLabel.text = d[#"text"];
if (d[#"im"]) { // if we have a picture, supply it
cell.imageView.image = d[#"im"];
} else if (!d[#"task"]) { // if not, download it
cell.imageView.image = nil;
NSURLSessionTask* task = [self.downloader download:d[#"picurl"]
completionHandler:^(NSURL* url){
if (!url)
return;
NSData* data = [NSData dataWithContentsOfURL:url];
UIImage* im = [UIImage imageWithData:data];
d[#"im"] = im;
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
});
}];
}
return cell;
}
I suggest a different approach.
Google for - or use the search on SO - Asynchrnous loading. Nearly every app programmer faces this issue earlier or later. Consequentially there are tons of tutorials out there.
This is one of them.
http://www.markj.net/iphone-asynchronous-table-image/
I think it is older than the UICollectionView and therfore explains it for UITableView. Both data source delegates are so close to each other that you can easily adopt the solution to your collection.
There are smarter ways of acomplishing your goal. But I think tht this way is a good starting point. You may later want to refactor the solution once you got comforatble with the approach in general.
After several days the issue was my images were far too large. I had to resize them and this instantly solved my issue.
I literally narrowed things down and checked my images to find they were not being resized by the method I thought was resizing them. This is why I need to get myself used to testing.
I learnt a lot about GCD and caching in the past few days but this issue could have been solved much earlier.

Set an image to many UIImageViews

Say I have 60-70 UIImageViews and I want to dynamically load the same image into all of them at the same time (so to speak).
For example, if I were working on a web page with 60-70 divs and I wanted to give them all a background image I would give them all a class of myDivs and call `$('.myDivs').css('background-image', 'url(' + imageUrl + ')');
I know how to set a UIImageView's image but is there an easy way to set a bunch at once in Objective C?
I did try searching but I was flooded with a ton of stuff that is really unrelated.
It depends on the way you wish to display the imageView(s).
If you are using a UITableView or a UICollectionView, in the cellForRowAtIndexPath: method you can dynamically update an imageView placed in a cell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
// Configure the cell...
[self setCell:cell forIndexPAth:indexPath];
return cell;
}
- (void)setCell:(UITableViewCell *)cell forIndexPAth:(NSIndexPath *)indexPath
{
__weak UITableViewCell *weakCell = cell;
// 5. add picture with AFNetworking
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"www.facebook.com/profileImageLocation"]];
[cell.profileImage setImageWithURLRequest:request
placeholderImage:nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
weakCell.profileImage
.image = image;
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"bad url? %#", [[request URL] absoluteString]);
}];
}
Another option will be using a for loop like this:
- (void)addImagesToAllMyImageViews:(NSArray *)images
{
for (id obj in images) {
if ([obj isKindOfClass:[UIImageView class]]) {
UIImageView *imageView = (UIImageView *)obj;
imageView.image = [UIImage imageNamed:#"someImage.png"];
}
}
}
I think you can do with tag property, select all ImageView and give them a teg like 777 and
for(id* subview in self.view.subview) {
if([subview isKindaOfclass:[UIImageView class]] && (subview.tag == 777)) {
UIImageView* imageView = (UIImageView*)subview;
imageVIew.image = youImage;
}
}
hope this helps you

Table view scroll lagging when using SDWebImage

I am loading some images from the internet in a table view inside cellForRowAtIndexPath. Here is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"ArticleCell";
ArticleCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
Article *article = [parser items][indexPath.row];
cell.title.text = article.title;
cell.newsDescription.text = article.description;
[cell.image setImageWithURL:[NSURL URLWithString:article.image]];
return cell;
}
My problem is that even if I use SDWebImage, when I scroll down, my app still lags. Here is some screenshots from Instruments:
It looks like even though the download of the image is performed in a background thread, the work with the image data is done in the main thread, thus it blocks your application. You could try the asynchronous image downloader provided by SDWebImage.
[SDWebImageDownloader.sharedDownloader downloadImageWithURL:imageURL
options:0
progress:^(NSUInteger receivedSize, long long expectedSize)
{
// progression tracking code
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished)
{
if (image && finished)
{
// do something with image
}
}
];
In your method it should look like:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"ArticleCell";
ArticleCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
Article *article = [parser items][indexPath.row];
cell.title.text = article.title;
cell.tag = indexPath.row;
cell.newsDescription.text = article.description;
[SDWebImageDownloader.sharedDownloader downloadImageWithURL:imageURL
options:0
progress:nil
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished)
{
if (cell.tag == indexPath.row && image && finished)
{
dispatch_async(dispatch_get_main_queue(), ^(){
cell.image = image;
});
}
}];
return cell;
}
Download the image on a separate thread, like so:
NSURLRequest *request = [NSURLRequest requestWithURL:yourURL];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( data )
{
UIImage *image = [[UIImage alloc] initWithData:data];
[cell.image setImage:image];
}
}];
This probably has less to do with network activity and more to do with your image sizes. Resizing images, especially for non-integer multipliers, is expensive. If your images are significantly larger than the view you are putting them into, you need to resize and cache them in a background thread, and reference the cached copy from your view.
To confirm if this is your issue, manually download all the images and reference the local copy. If I am correct, your UITableView will still lag the same way.
You have to load asynchronously from server so your tableView will scrolling smoothly.
Please check below answer you will be able to solve your problem.
https://stackoverflow.com/a/15331306/1713478
Check you images, check the types and size and more important: Is one or more image broken by its file format in any way? Filesize too big, irregular (to large) bounds?
Try to open and re-save suspiciously image files with an image editor, to be sure the internal file format is OK.

Resources