iOS: UITableView lagging with image data - ios

I have a UITableview that has a list of images like so. But it is very laggy for some reason when I scroll up and down. Any way to stop this? The images dont need to be reloaded. It needs to stay static.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier];
}
ContentModel *contentModel = [self.tableArray objectAtIndex:indexPath.row];
NSURL *url = [NSURL URLWithString:contentModel.txtImages];
NSData *data = [NSData dataWithContentsOfURL:url];
cell.imageView.image = [[UIImage alloc] initWithData:data];
return cell;
}

You may better use AFNetworking UIImageView Methods:
[imageView setImageWithURL:[NSURL URLWithString:#"http://image.com/image.png"]];
Even with a PlaceHolder:
[self.image setImageWithURL:[NSURL URLWithString:#"http://image.com/image.png"] placeholderImage: [UIImage imageNamed:#"logo"]];
https://github.com/AFNetworking/AFNetworking
This will dramatically reduce the lag on your tableview.

We have very similar requirements but my code works perfectly, I am guessing that
these commands slow down your routine:
NSURL *url = [NSURL URLWithString:contentModel.txtImages];
NSData *data = [NSData dataWithContentsOfURL:url];
cell.imageView.image = [[UIImage alloc] initWithData:data];
instead I use the following:
NSString *icon = CurrentQRCode.parsed_icon;
NSString *filePath = [[NSBundle mainBundle] pathForResource:icon ofType:#"png"];
UIImage *image_file = [UIImage imageWithContentsOfFile:filePath];
cell.imageView.image = image_file;
the whole routine is below:
QRTypeCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[QRTypeCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];}
CurrentQRCode = (QRCode *)[fetchedResultsController objectAtIndexPath:indexPath];
NSString *icon = CurrentQRCode.parsed_icon;
NSString *string = CurrentQRCode.parsed_string;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
NSString *filePath = [[NSBundle mainBundle] pathForResource:icon ofType:#"png"];
UIImage *image_file = [UIImage imageWithContentsOfFile:filePath];
cell.imageView.image = image_file;
cell.labelview.text = string;
I believe I originally tried handling UIImages using initWithData but found them a lot slower than the other methods.

Never ever ever ever load data from a network on the main thread. Use GCD or a framework like AFNetworking or restkit. The worst thing you could ever do to you app is lock up your main thread with things that can be computed off the main thread. A good rule of thumb is to ask is this something that is updating the UI, if not think about moving it to another thread.
I would also look at some of the WWDC videos about multithreading and anything related to moving off the main thread if you really want to understand the reasons why.
AFNetworking
RestKit
GCD

Related

Images are loading in Table View in simulator but not in Device

I'm parsing images from JSON data and displaying in Table View. My code to display images in table view is -
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellidentifier=#"MyCell";
OnlineCell *cell = [tableView dequeueReusableCellWithIdentifier:cellidentifier];
if (cell == nil)
{
cell = [[OnlineCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellidentifier];
}
cell.userLabel.text = [self.allusername objectAtIndex:indexPath.row];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
imageURL = [self.alluserphoto objectAtIndex:indexPath.row];
img = [UIImage imageWithData:[NSData dataWithContentsOfURL: [NSURL URLWithString:imageURL]]];
dispatch_async(dispatch_get_main_queue(), ^{
cell.userIMage.image = img;
[indicator stopAnimating];
});
});
return cell;
}
Simulator Output -
Device OutPut -
Please tell me the how could i get images in device.
As i can see two screenshot one is Simulator and one is Device that two image common Load at both end simulator and device. So i think issue in to you image that comes from web side. Because you code is correct if there is issue in code then that not load two image as well in device.
Please test with change the image with First loaded image from your back End side and check once that have to load. put first loaded image for all response instead of goggles image that not load in device. i am dame sure issue is in image not in code.
Debugging Suggestion 1: On the device, make sure that the image is accessible. You can try opening the image in Safari. If safari is opening the image, it means that image is accessible.
Debugging Suggestion 2: If the image is accessible, try loading the image synchronously and debug if you're getting the required data (on the device):
NSURL *url = [NSURL URLWithString:self.photoImagePath]; // Is URL valid?
NSData *imageData = [NSData dataWithContentsOfURL:url]; // Is data nil?
UIImage *image = [UIImage imageWithData:imageData]; // Is image nil?
Then, try loading the image asynchronously, like this:
__weak UIImageView *weakImgageView = cell.userIMage;
imageURL = [self.alluserphoto objectAtIndex:indexPath.row];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL: [NSURL URLWithString:imageURL]]];
dispatch_async(dispatch_get_main_queue(), ^{
weakImgageView.image = image;
[indicator stopAnimating];
});
});
Hope this helps.
The default image is not coming properly. This is what I suspect. Please check if you are giving the name properly or not. This could be the issue.
Set default image until you fetch image asynchronously from JSON.
UIImage *img = [UIImage imageNamed:#"default.png"];
When you fetch image in asynchronously method means it will fetch depend on image size & internet speed. So you just keep a default image until it fetch completely from server
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MyCell";
OnlineCell *cell = (OnlineCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSArray *nib = nil;
if (cell == nil)
{
nib = [[NSBundle mainBundle] loadNibNamed:#"YourCustomCellNibName" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
// Your code
cell.userLabel.text = [self.allusername objectAtIndex:indexPath.row];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//NSData *imagedata = [NSData dataWithContentsOfURL:[NSURL URLWithString:[self.alluserphoto objectAtIndex:indexPath.row]]];
NSURL* url = [NSURL URLWithString:[self.alluserphoto objectAtIndex:indexPath.row]];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
UIImage* image = [[UIImage alloc] initWithData:data];
// do whatever you want with image
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
OnlineCell *updateCell = (id)[tableView cellForRowAtIndexPath:indexPath];
if (updateCell)
cell.userIMage.image = image;
});
}
}
}];
});
}

Slow UITableView Scrolling from JSON

My UITableView scrolls really slowly when I load from a local JSON. The images are being loaded from a external URL. I first try loading the JSON in my viewWillAppear method:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"HomePage" ofType:#"json"];
NSError *error = nil;
NSData *data = [NSData dataWithContentsOfFile:filePath];
self.titleLabels = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
});
And in my tableView (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath, I have the following:
HomeTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if(cell == nil) {
[tableView registerNib:[UINib nibWithNibName:#"HomeCell" bundle:nil] forCellReuseIdentifier:#"Cell"];
cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
}
NSDictionary *titleLabels = [self.titleLabels objectAtIndex:indexPath.row];
NSString *label = [titleLabels objectForKey:#"Heading"];
NSURL *imageURL = [NSURL URLWithString:[titleLabels objectForKey:#"Image"]];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]];
cell.label.text = label;
cell.imageView.image = image;
cell.cardView.frame = CGRectMake(10, 5, 300, 395);
return cell;
I am just wondering why the table view is scrolling very slowly. If anyone could shed some light on this, it would be greatly appreciated.
Thanks!
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]]
This is what is causing the slow scrolling in your app. You are making a network call for the cell.imageView.image property. Since you are calling the [UIImage imageWithData:[NSData dataWithContentsOfURL:imageURL]]; ON THE MAIN THREAD, the app needs to wait for the network call to finish, only then it can scroll farther.
The keypoint here is, you are doing all the networking calls on the main thread, and all touches will be delayed untill all the cells have been completely loaded.
This is not recommended, at all.
A better approach :
(1). is to make the networking call, only for the VISIBLE CELLS on the screen.
Follow this tutorial -- > How to Make Faster UITableViewCell Scrolling by RayWenderLich
(2) Apple's Sample Code, which shows this in action
(3) Load all the images beforehand and store it in an array, after viewDidLoad

Load only visible cells in UITableView

So I have an application that reads records from a database and basically fills out a UITableView with the information from the DB. Some of the information includes an image, which it brings from an online server I have. Everything works fine, but the program is a little slow and scrolling is a little laggy/ jumpy, as its bringing all the images at once, instead of bringing them as I scroll. Is there a way to change it so that as I scroll it brings the next few visible records?
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
static NSString *CellIdentifier = #"VersionCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier];
}
STSneakerInfo *info = [_versionInfo objectAtIndex:indexPath.row];
cell.textLabel.font = [UIFont boldSystemFontOfSize:14.1];
[[cell textLabel] setNumberOfLines:2];
cell.textLabel.text = [[_uniqueBrand stringByAppendingString:#" "] stringByAppendingString: info.version];
cell.detailTextLabel.textColor = [UIColor grayColor];
cell.detailTextLabel.font = [UIFont boldSystemFontOfSize:14.1];
cell.detailTextLabel.text = info.initialReleaseDate;
NSString *brandVersion = [[_uniqueBrand stringByAppendingString:#" "] stringByAppendingString:info.version];
NSString *underscoredBrandVersion = [brandVersion stringByReplacingOccurrencesOfString:#" " withString:#"_"];
NSString *underscoredBrandName = [_uniqueBrand stringByReplacingOccurrencesOfString:#" " withString:#"_"];
NSData *imageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: ([[[[[#"http://www.abc/img/" stringByAppendingString:underscoredBrandName] stringByAppendingString:#"/"] stringByAppendingString:underscoredBrandVersion] stringByAppendingString:#"/"] stringByAppendingString:#"default.jpg"])]];
cell.imageView.image = [UIImage imageWithData: imageData];
return cell;
}
you can use (https://github.com/rs/SDWebImage) to download image async. its easy and fast.
i prefered this library because it will handle your cache.
just write below code
[cell.imageView sd_setImageWithURL: [NSURL URLWithString: ([[[[[#"http://www.abc/img/" stringByAppendingString:underscoredBrandName] stringByAppendingString:#"/"] stringByAppendingString:underscoredBrandVersion] stringByAppendingString:#"/"] stringByAppendingString:#"default.jpg"])]];
you can also download image in background thread as per wenchenHuang answer above. using below code.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString: ([[[[[#"http://www.abc/img/" stringByAppendingString:underscoredBrandName] stringByAppendingString:#"/"] stringByAppendingString:underscoredBrandVersion] stringByAppendingString:#"/"] stringByAppendingString:#"default.jpg"])];
if (data)
{
UIImage *img = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{
if (img)
cell.imageView.image = img;
});
}
});
Maybe this will help you.
The UITableView class never loads cells until they're about to appear onscreen.
I suggest you to use GCD to download image background.When finished,notice UI to change.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//Download images
dispatch_async(dispatch_get_main_queue(), ^{
//Notice UI to change
});
});
Here's a full, real-world example with DLImageLoader
https://github.com/AndreyLunevich/DLImageLoader-iOS/tree/master/DLImageLoader
DLImageLoader is incredibly well-written, maintained constantly, is super-lightweight, and it can properly handle skimming.
It's difficult to beat and is used in many apps with vast numbers of users.
It is really an amazingly well maintained library - and on top of that the new Swift version is the pinnacle of excellence in Swift programming, it's a model for how to do it.
PFObject *aFacebookUser = [self.fbFriends objectAtIndex:thisRow];
NSString *facebookImageURL = [NSString stringWithFormat:
#"http://graph.facebook.com/%#/picture?type=large",
[aFacebookUser objectForKey:#"id"] ];
__weak UIImageView *loadMe = self.cellImage;
[DLImageLoader loadImageFromURL:facebookImageURL
completed:^(NSError *error, NSData *imgData)
{
if ( loadMe == nil ) return;
if (error == nil)
{
UIImage *image = [UIImage imageWithData:imgData];
image = [image ourImageScaler];
loadMe.image = image;
}
else
{
// an error when loading the image from the net
}
}];
another real world example,
-(UICollectionViewCell *)collectionView:(UICollectionView *)cv
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
NSInteger thisRow = indexPath.row;
BooksCell *cell;
cell = [cv dequeueReusableCellWithReuseIdentifier:
#"CellBooksNormal" forIndexPath:indexPath];
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
// set text items...
cell.title = #"blah;
// set image items using DLImageLoader...
__weak UIBookView *loadMe = cell.anImage;
[DLImageLoader loadImageFromURL:imUrl
completed:^(NSError *error, NSData *imgData)
{
[loadMe use:[UIImage imageWithData:imgData]];
}];
return cell;
}
Reason for your sluggish scroll is that you are downloading image on the main thread.
NSData *imageData = [[NSData alloc] initWithContentsOfURL:
That piece of code which is running on main thread, will make a network call and start downloading contents from server. And the execution halts there until its completed. Which means you wont be be able to scroll down anymore till image is loaded.
There are many workarounds for this. However the logic is same for all. Download image in a separate thread and load it once its completed.
If you are using AFNetworking in your project you can use setImageWithURL: on your UIImageView object. You need to include UIImageView+AFNetworking.h. It has a inbuilt caching mechanism and it will cache the images downloaded for that session.

Loading images for UITableViewCell from URL (need to load them asynchronously)

I have a custom class that parses through an XML and gets URL strings for images (which I store in an array). Then I want to retrieve those strings to load images and display each in a UITableViewCell. Here is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
UILabel *labelText = (UILabel *)[cell viewWithTag:1000];
labelText.text = [[self.listOfPlaceDetails objectAtIndex:indexPath.row] objectForKey:#"name"];
NSURL *imageURL = [NSURL URLWithString:[[self.listOfPlaceDetails objectAtIndex:indexPath.row]objectForKey:#"imageCell"]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
cell.imageView.image = image;
return cell;
}
Obviously, when I scroll down the table, the app loads more images, and when the images are loading, the user can't do anything. It looks like the app froze. The images I want to load are not very large (about 8kb). Maybe there is a way I could load and store them in the background? I also want to give more freedom to the user, like scrolling down and viewing cells with text but without an image. Then force the app to load the images for the cells the user is currently viewing.
Is there any way to modify my code or any other solution for proper loading of images from URL strings?
Any advice would be appreciated, thanks.
Try to load the images asynchronous, so the main thread doesn't block.
Your code would look as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
UILabel *labelText = (UILabel *)[cell viewWithTag:1000];
labelText.text = [[self.listOfPlaceDetails objectAtIndex:indexPath.row] objectForKey:#"name"];
NSURL *imageURL = [NSURL URLWithString:[[self.listOfPlaceDetails objectAtIndex:indexPath.row]objectForKey:#"imageCell"]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = image;
});
});
return cell;
}
You can use the SDWebImage framework to load images asynchronously.
Please follow the link:
https://github.com/rs/SDWebImage
Just add the SDWebImage framework to your project and set the other linker flag '-ObjC', to use the framework.
Download link : https://github.com/rs/SDWebImage/releases
Use this for asynchronus way of loading imageview
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
UILabel *labelText = (UILabel *)[cell viewWithTag:1000];
labelText.text = [[self.listOfPlaceDetails objectAtIndex:indexPath.row] objectForKey:#"name"];
//get a dispatch queue
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this will start the image loading in bg
dispatch_async(concurrentQueue, ^{
NSURL *imageURL = [NSURL URLWithString:[[self.listOfPlaceDetails objectAtIndex:indexPath.row]objectForKey:#"imageCell"]];
NSData *image = [[NSData alloc] initWithContentsOfURL:imageURL];
//this will set the image when loading is finished
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = [UIImage imageWithData:image];
});
});
return cell;
}
Download this project and use asynimageveiw in your cell's imageview.. link
Use NSOperation and NSOperationQueue.A perfect solution for your problem.Use below link to see how it works.
http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues
Or
Use AFNetworking library It also provide a category over UIImageview that download image asynchronously.

How make parse image from JSON

I have some project where i must paste image from JSON. I try to find any tutorial about this, try to see any video from this theme but nothing. So my problem have:
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSDictionary *allDataDictionary = [NSJSONSerialization JSONObjectWithData:webdata options:0 error:nil];
NSDictionary *playlist =[allDataDictionary objectForKey:#"playlist_data"];
for (NSDictionary *diction in playlist) {
NSString *name = [diction objectForKey:#"text1"];
NSString *namesong = [diction objectForKey:#"text2"];
NSString *images = [diction objectForKey:#"image"];
NSLog(#"%#", images);
[array addObject:text1];
[array2 addObject:text2];
}
[[self tableTrack]reloadData];
}
I added text 1 to cell also text 2 its work perfect but how to add image to to array 3(image in cell in my tableView)?
I tried also to added for image but its not work for me:
NSURL *imageURL = [NSURL URLWithString:[appsdict objectForKey:#"image"]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *imageLoad = [[UIImage alloc] initWithData:imageData];
cell.imageView.image = imageLoad;
Please help with my problem or maybe give some tutorial with parse image from JSON, also youtube haven't perfect tutorial from JSON parse. Thanks!
Don't keep multiple data source like array1, array2, array3 etc. It is not good coding and will confuse while debugging/fixing issues. Instead maintain single array with information for displaying in individual cell of the table view.
for (NSDictionary *dict in playlist) {
// array is single data source
[array addObject:diction];
}
Then while assigning data to the table view cell use,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier] autorelease];
}
cell.text1 = [[array objectAtIndex:indexPath.row] objectForKey:#"text1"];
cell.text2 = [[array objectAtIndex:indexPath.row] objectForKey:#"text2"];
//For displaying image by lazy loading technique use SDWebImage.
[cell.imageView setImageWithURL:[NSURL URLWithString:[[array objectAtIndex:indexPath.row] objectForKey:#"image"]] placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
// If no place holder image, use this way
// [cell.imageView setImageWithURL:[NSURL URLWithString:[[array objectAtIndex:indexPath.row] objectForKey:#"image"]] placeholderImage:nil];
}
Like I mentioned in comments for lazy loading images from the URLs in JSON response, use SDWebImage. Usage is simple like I have shown above. Alternately you can implement lazy loading yourself by studying this sample code from Apple: LazyTableImages
Hope that helps!
According to https://developer.apple.com/library/mac/documentation/Foundation/Reference/NSJSONSerialization_Class/Reference/Reference.html and http://www.json.org/ you can't keep binary data in JSON (unless it is in base64).
It seems there's a problem with your webdata which is not a valid JSON. Did you check what is set under #"image" key in that dictionary while debugging? You can use [NSJSONSerialization isValidJSONObject:webdata] as well to see if your data is ok.
//prepare your method here..
-(void)hitimageurl{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:replacephotostring]];
//set your image on main thread.
dispatch_async(dispatch_get_main_queue(), ^{
if (_photosArray==nil) {
_showImageView.image = [UIImage imageNamed:#"noimg.png"];
} else {
[_showImageView setImage:[UIImage imageWithData:data]];
}
});
});

Resources