UITableView with JSON data very slow and freezing - ios

Good morning,
Yesterday I finally made my first TableViewController with JSON parsing data from my database but now I have a problem with that:
The app is taking almost 9-10 second to load and show 3 entries.
When I navigate with scroll down the TableView it freezes when it displays a new image.
I'm going to show you my code because I don't know why is this happening, maybe it's because the images are really big (they are made with the iPhone camera and stored in my FTP).
Do you know why my App is so slow when loading the results and then navigation through them?
ViewDidLoad:
- (void)viewDidLoad
{
[super viewDidLoad];
[self fetchJson];
}
TableView:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"carTableCell";
CarTableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[CarTableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
// Configure the cell...
cell.makeLabel.text = [[_jsonArray objectAtIndex:indexPath.row] valueForKey:#"id"];
cell.likes.text = [[_jsonArray objectAtIndex:indexPath.row] valueForKey:#"likes"];
cell.comments.text = [[_jsonArray objectAtIndex:indexPath.row] valueForKey:#"comments"];
cell.username.text = [[_jsonArray objectAtIndex:indexPath.row] valueForKey:#"username"];
cell.refuser.text = [[_jsonArray objectAtIndex:indexPath.row] valueForKey:#"user_ref"];
cell.modelLabel.text = [[_jsonArray objectAtIndex:indexPath.row] valueForKey:#"user"];
NSURL * imageURL = [NSURL URLWithString:[[_jsonArray objectAtIndex:indexPath.row] valueForKey:#"imagen"]];
NSData * imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage * carPhoto = [UIImage imageWithData:imageData];
cell.carImage.image = carPhoto;
NSURL * imageURL2 = [NSURL URLWithString:[[_jsonArray objectAtIndex:indexPath.row] valueForKey:#"image"]];
NSData * imageData2 = [NSData dataWithContentsOfURL:imageURL2];
UIImage * carPhoto2 = [UIImage imageWithData:imageData2];
cell.profileImage.image = carPhoto2;
return cell;
}
FetchJSON:
-(void)fetchJson {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSString * urlString = [NSString stringWithFormat:#"http://website.com/service.php"];
NSURL * url = [NSURL URLWithString:urlString];
NSData * data = [NSData dataWithContentsOfURL:url];
self.carModels = [[NSMutableArray alloc] init];
self.carMakes = [[NSMutableArray alloc] init];
self.carImages = [[NSMutableArray alloc] init];
self.likes = [[NSMutableArray alloc] init];
self.comments = [[NSMutableArray alloc] init];
#try
{
NSError *error;
[_jsonArray removeAllObjects];
_jsonArray = [NSJSONSerialization
JSONObjectWithData:data
options:NSJSONReadingMutableContainers|NSJSONReadingMutableLeaves
error:&error];
for(int i=0;i<_jsonArray.count;i++)
{
NSDictionary * jsonObject = [_jsonArray objectAtIndex:i];
NSString* imagen = [jsonObject objectForKey:#"imagen"];
[_carImages addObject:imagen];
NSDictionary * jsonObject2 = [_jsonArray objectAtIndex:i];
NSString* user = [jsonObject2 objectForKey:#"user"];
[_carMakes addObject:user];
NSDictionary * jsonObject3 = [_jsonArray objectAtIndex:i];
NSString* date = [jsonObject3 objectForKey:#"date"];
[_carModels addObject:date];
}
}
#catch (NSException * e)
{
NSLog(#"Exception: %#", e);
}
#finally
{
[self.tableView reloadData];
}
}
);
}
And that's my JSON output file:
[{"id":"15","user":"1","imagen":"http:\/\/website.com\/juliaroberts.jpg","date":"2014-09-13","userID":"1","image":"http:\/\/m.c.lnkd.licdn.com\/mpr\/mpr\/shrink_200_200\/p\/3\/000\/25b\/1fe\/200e9f3.jpg","username":"*jordi","id_post":"15","likes":"5","comments":"2","post_id":"15","user_ref":"*juliaroberts"},{"id":"16","user":"1","imagen":"http:\/\/website.com\/onedirection.png","date":"2014-11-11","userID":"1","image":"http:\/\/m.c.lnkd.licdn.com\/mpr\/mpr\/shrink_200_200\/p\/3\/000\/25b\/1fe\/200e9f3.jpg","username":"*jordi","id_post":"16","likes":"1","comments":"0","post_id":"16","user_ref":"*onedirection"},{"id":"17","user":"1","imagen":"http:\/\/website.com\/onedirection.png","date":"2014-11-09","userID":"1","image":"http:\/\/m.c.lnkd.licdn.com\/mpr\/mpr\/shrink_200_200\/p\/3\/000\/25b\/1fe\/200e9f3.jpg","username":"*jordi","id_post":"17","likes":"3","comments":"3","post_id":"17","user_ref":"*onedirection"}]
Thanks in advance.

The problem is that your images are being downloaded synchronously and thay block main thread. So every time you see tableview cell, image will be downloaded.
This is the most expensive code:
NSData * imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage * carPhoto = [UIImage imageWithData:imageData];
You should always avoid heavy operations to be run on the same thread as UI runs (main thread).
The workaround for you is:
1). Move any images downloading code to the background threads.
2). Return downloaded UIImage to the main thread to show in the UI.
3). Cache the image once it downloaded to save traffic.
Basically you can use a lot of 3rd party libraries for these tasks. The most popular is SDWebImage:
https://github.com/rs/SDWebImage

You should try to measure the performance bottlenecks of your app using Instruments. Reading this post on raywenderlich.com is a simple introduction to profiling this sort of issues.
Based solely on the code you posted, it looks like you're fetching the images for the cars in the main thread which causes the performance hickups. The line of code that does this is:
NSData * imageData = [NSData dataWithContentsOfURL:imageURL];
and the reason is the fact that dataWithContentsOfURLis run synchronously in the current thread which in this case is the main thread. The Apple Documentation of dataWithContentsOfURL acknowledges this and recommends using dataTaskWithURL:completionHandler: instead.

Related

Store the image into cache for avoiding frequent loading from url in tableview

NSURL *url =[NSURL URLWithString:
#"http://api.kivaws.org/v1/loans/search.json?status=fundraising"];
Serialisation part is done below
(void)connectionDidFinishLoading{
NSDictionary *allDict = [NSJSONSerialization JSONObjectWithData:webData options:0 error:nil];
NSArray *loans = [allDict objectForKey:#"loans"];
countryArray = [[NSMutableArray alloc] init];
for (NSDictionary *diction in loans) {
Country *countryObj = [[Country alloc]init];
NSString *sector = [diction objectForKey:#"sector"];
countryObj.sector = sector;
NSDictionary *imageID = [diction objectForKey:#"image"];
NSString *img = [imageID objectForKey:#"id"];
countryObj.idValue=img;
NSString *activity = [diction objectForKey:#"activity"];
countryObj.activity = activity;
NSString *name = [diction objectForKey:#"name"];
countryObj.name = name;
NSDictionary *con = [diction objectForKey:#"location"];
NSString *world = [con objectForKey:#"country"];
countryObj.country= world;
NSString *towname = [con objectForKey:#"town"];
countryObj.down=towname;
[countryArray addObject:countryObj];
}
}
This contains both images and data. I need to store the image into cache.
Please use SDWebImage it contains everything you need. I am using this library for 2 years already.
Follow that tutorial which contains how to cache images inside a UIImageView with a simple one line of code and also a class that prefetches all the images in background, just create an NSArray with all the images you want to prefetch.
Hope that helps!

Xcode table view lag when scrolling

(void)bilgileriYukle
{
NSMutableArray *yemekIsimleri = [[NSMutableArray alloc] init];
NSMutableArray *resimIsimleri = [[NSMutableArray alloc] init];
NSURL *myURL = [[NSURL alloc]initWithString:#"http://www.sevgilezzeti.com/WebServices/tarifler.php"];
NSData *myData = [[NSData alloc]initWithContentsOfURL:myURL];
if (myData == nil)
{
NSLog(#"INTERNET YOK YADA VERI CEKEMEDI");
}
else
{
NSLog(#"INTERNET VAR VERI CEKILDI");
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:myData options:kNilOptions error:nil];
for (id element in jsonArray)
{
NSString *durum = [element objectForKey:#"durum"];
if([durum isEqualToString:#"1"])
{
[yemekIsimleri addObject:[element objectForKey:#"yemekadi"]];
NSString *gelenYemekAdi =[element objectForKey:#"kapakresmi"];
NSString *imagePathKapak = #"http://sevgilezzeti.com/Admin/yemekresimkapak/";
combined = [NSString stringWithFormat:#"%#%#", imagePathKapak, gelenYemekAdi];
[resimIsimleri addObject:combined];
}
}
}
_TarifAdi=yemekIsimleri;
_ResimAdi=resimIsimleri;
}
///////////////////////////////////////////////////////////////////////////////////////
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TableViewCell" forIndexPath:indexPath];
NSUInteger row = [indexPath row];
cell.TitleLabel.text = _TarifAdi[row];
NSCache *_imageCache;
UIImage *image = [_imageCache objectForKey:#"DenemeRes"];
if (image == nil) {
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:_ResimAdi[row]]];
image = [UIImage imageWithData:data];
[_imageCache setObject:image forKey:#"DenemeRes"];
}
cell.ThumbImage.image = image;
return cell;
}
I can get images from URL and can see images but when I scroll the table view, it's very slow and lagging. How can I fix this problem ?
It's pretty easy and straightforward to load images synchronously in table view cells, however it's evil, because it would cause the lag when you scroll it, because it's actually trying to load and display the images as you're scrolling.
Therefore, you must avoid loading images synchronously, but asynchronously instead, using a different thread other than the main thread, so that the scrolling and loading + displaying images can be done dependently, avoiding any kind of lag.
This is possibly a duplicate question, since it's already been asked and there are tutorials out there, but since I myself always had a problem with this on my first iOS app and found it very confusing, I'm posting the answer here, hoping it's helpful.
Try adding something like this to your - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath:
// Get the filename to load.
NSString *imageFilename = [imageArray objectAtIndex:[indexPath row]];
NSString *imagePath = [imageFolder stringByAppendingPathComponent:imageFilename];
[[cell textLabel] setText:imageFilename];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
dispatch_sync(dispatch_get_main_queue(), ^{
[[cell imageView] setImage:image];
[cell setNeedsLayout];
});
});
dataWithContentsOfURL is blocking your main thread, that'w why the table is acting like it is. You should load images in back thread so that the main thread can handle UI without interruptions
NSURL *url = [NSURL URLWithString:link];
NSURLRequest *urlRequest = [[NSURLRequest alloc] initWithURL:url cachePolicy:0 timeoutInterval:kTimeoutInterval];
[NSURLConnection sendAsynchronousRequest:urlRequest
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
UIImage *image = [[UIImage alloc] initWithData:self.activeDownload];
// set to ImageView
cell.ThumbImage.image = image;
}];
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TableViewCell" forIndexPath:indexPath];
cell.tag = indexPath.row;
cell.imageView.image = nil;
// Rounded Rect for cell image
CALayer *cellImageLayer = cell.imageView.layer;
[cellImageLayer setCornerRadius:35];
[cellImageLayer setMasksToBounds:YES];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_async(queue, ^(void) {
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:_ResimAdi[indexPath.row]]];
UIImage *image = [[UIImage alloc] initWithData:data];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
if (cell.tag == indexPath.row) {
CGSize itemSize = CGSizeMake(70, 70);
UIGraphicsBeginImageContext(itemSize);
CGRect imageRect = CGRectMake(0.0, 0.0, itemSize.width, itemSize.height);
[image drawInRect:imageRect];
cell.imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[cell setNeedsLayout];
}
});
}
});
cell.TitleLabel.text = _TarifAdi[indexPath.row];
return cell;
}

Very slowly scroll on UITableView images

Very slowly scroll on UITableView images.Help me please.
Did not find the right solution
My code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *const ImageCellId = #"ImageCell";
EXImageCell *cell = [tableView dequeueReusableCellWithIdentifier:ImageCellId];
RSSItem *item = [_data objectAtIndex:indexPath.row];
cell.cellTextLabel.text = item.title;
cell.cellTimeTextLabel.text = [NSDateFormatter localizedStringFromDate:item.pubDate dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterShortStyle];
cell.cellAuthor.text = [NSString stringWithFormat:#"Author: %#", item.author];
NSURL *tutorialsUrl = item.link;
NSData *tutorialsHtmlData = [NSData dataWithContentsOfURL:tutorialsUrl];
TFHpple *tutorialsParser = [TFHpple hppleWithHTMLData:tutorialsHtmlData];
NSString *tutorialsXpathQueryString = #"//div[#class='photo']/img";
NSArray *tutorialsNodes = [tutorialsParser searchWithXPathQuery:tutorialsXpathQueryString];
//NSMutableArray *newTutorials = [[NSMutableArray alloc] initWithCapacity:0];
for (TFHppleElement *element in tutorialsNodes) {
tutorial = [[Tutorial alloc] init];
//[newTutorials addObject:tutorial];
//tutorial.title = [[element firstChild] content];
tutorial.url = [element objectForKey:#"src"];
}
NSData *data = [NSData dataWithContentsOfURL : [NSURL URLWithString:tutorial.url]];
UIImage *imageMain = [UIImage imageWithData:data];
[cell.cellImageView setImage:imageMain];
return cell;
}
PS: dispatch_async tried to use, but to no avail

Cell text overlapping when scrolling in UITableView Xcode iOS

Everytime I start scrolling in the tableView the subtitleLabel text keeps overlapping each other in every row. I've tried for the last 4 hours every single search on the Internet for this problem I have clicked and tried.
Here is my ViewController.m:
#interface ANViewController()
{
NSMutableArray *TitleLabel;
NSMutableArray *SubtitleLabel;
NSMutableArray *AvatarImages;
}
#end
#implementation ANViewController
#synthesize thetableview;
- (void)viewDidLoad
{
[super viewDidLoad];
self.thetableview.delegate = self;
self.thetableview.dataSource = self;
TitleLabel = [NSMutableArray array];
SubtitleLabel = [NSMutableArray array];
AvatarImages = [NSMutableArray array];
__block NSArray *posts;
__block NSData *allNewsData = nil;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0ul);
dispatch_async(queue, ^{
allNewsData = [NSData dataWithContentsOfURL:[NSURL URLWithString: API_URL]];
NSError *error;
NSMutableDictionary *allNews = [NSJSONSerialization JSONObjectWithData:allNewsData options:NSJSONReadingMutableContainers error:&error];
if (error) {
NSLog(#"%#", [error localizedDescription]);
} else {
posts = allNews[#"data"];
for (NSDictionary *newsPost in posts) {
[TitleLabel addObject:newsPost[#"title"]];
[SubtitleLabel addObject:newsPost[#"post_message"]];
NSString *avatarUrl = AVATAR_URL;
NSString *avatarExt = #".jpg";
[AvatarImages addObject:[NSString stringWithFormat:#"%#%#%#", avatarUrl, newsPost[#"user_id"], avatarExt]];
}
}
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(#"THIS IS THE MAIN THREAD...");
[self.thetableview reloadData];
});
});
NSURL *url = [NSURL URLWithString: WEBSITE_URL];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
[webView loadRequest:urlRequest];
}
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
long count = TitleLabel.count;
if(count == 0){
count = 1;
}
return count;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier;
ANTableViewCell *Cell;
if(TitleLabel.count == 0){
NSLog(#"Did with no news");
CellIdentifier = #"Cell_NoNews";
Cell = [thetableview dequeueReusableCellWithIdentifier:CellIdentifier];
if (Cell == nil) {
Cell = [[ANTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Cell.noNewsLabel.text = #"No news to display!";
}else{
NSLog(#"Did with news");
CellIdentifier = #"Cell";
Cell = [thetableview dequeueReusableCellWithIdentifier:CellIdentifier];
if (Cell == nil) {
Cell = [[ANTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
Cell.TitleLabel.text = [TitleLabel objectAtIndex:indexPath.row];
Cell.SubtitleLabel.text = [SubtitleLabel objectAtIndex:indexPath.row];
NSURL *url = [NSURL URLWithString:[AvatarImages objectAtIndex:indexPath.row]];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *img = [[UIImage alloc] initWithData:data];
Cell.AvatarImage.image = img;
}
return Cell;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#end
Just change UITableViewCellStyleSubtitle for UITableViewCellStyleDefault, and you need an asynchronous connection instead of a synchronous like you're doing: NSData *allNewsData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString:API_URL]]; in your viewDidLoad method that is blocking your main thread.
You can adapt this code to your need, in order to download it on the background:
__block NSData *allNewsData = nil;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
allNewsData = [NSData dataWithContentsOfURL:[NSURL URLWithString: API_URL]];
NSURL *url = [NSURL URLWithString: WEBSITE_URL];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
[webView loadRequest:urlRequest];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(#"THIS IS THE MAIN THREAD...");
[self.thetableview reloadData];
});
});

App crashes if null image URL is loaded in JSON object

I am populating a tableView with objects from a MySQL table via JSON.
The source data are introduced by many users in a web site form, and sometimes they don't introduce an image URL into a field.
The downloaded JSON object are shown in the tableView which rows are configured to show text, detail text and image.
If the object doesn't have an image URL, the app crashes, and I want to show a default image instead, to avoid the crash.
This is how am I loading the JSON data for the object's image:
NSMutableString *logo = [[NSMutableString alloc]initWithString:#"http://mujercanariasigloxxi.appgestion.eu/logos/"];
NSString *imageURL = [[categorias objectAtIndex:indexPath.row] objectForKey:#"strImagen"];
[logo appendString:imageURL];
NSURL *logoURL = [NSURL URLWithString:logo];
NSData *logoData = [NSData dataWithContentsOfURL:logoURL];
cell.imageView.image = [UIImage imageWithData:logoData];
What should be the best way to avoid the crash and to show a default image if the web site user doesn't include an image URL in the web form?
Try use a if-else condition, like
NSMutableString *logo = [[NSMutableString alloc]initWithString:#"http://mujercanariasigloxxi.appgestion.eu/logos/"];
NSData *logoData;
if([categorias objectAtIndex:indexPath.row] && [[categorias objectAtIndex:indexPath.row] objectForKey:#"strImagen"]){
NSString *imageURL = [[categorias objectAtIndex:indexPath.row] objectForKey:#"strImagen"];
[logo appendString:imageURL];
NSURL *logoURL = [NSURL URLWithString:logo];
logoData = [NSData dataWithContentsOfURL:logoURL];
}
else {
//load your default image here
logoData = //default logo data
}
cell.imageView.image = [UIImage imageWithData:logoData];
//set an activity indicator
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSString *imageURL = [[categorias objectAtIndex:indexPath.row] objectForKey:#"strImagen"];
if (imageURL.count>0){
[logo appendString:imageURL];
//declare logodata as nsdata
logoData=[NSData dataWithContentsOfURL:[NSURL URLWithString:logo]];
}
dispatch_sync(dispatch_get_main_queue(), ^{
//turn the activity indicator off
if(logoData!=nil)
cell.imageView.image = [UIImage imageWithData:logoData];
else
//set your default image.
});
});
Hope this might help you.
You may try checking whether imageurl is nil or in some cases it will come as null.
NSMutableString *logo = [[NSMutableString alloc]initWithString:#"http://mujercanariasigloxxi.appgestion.eu/logos/"];
NSString *imageURL = [[categorias objectAtIndex:indexPath.row] objectForKey:#"strImagen"];
if(imageURL != nil && ![imageURL isEqual:[NSNull null]])
{
[logo appendString:imageURL];
NSURL *logoURL = [NSURL URLWithString:logo];
NSData *logoData = [NSData dataWithContentsOfURL:logoURL];
cell.imageView.image = [UIImage imageWithData:logoData];
}
else{
cell.imageView.image = [UIImage imageNamed:#"Default_image"];
}

Resources