I'm having a problem with a table view and some custom cells. Here is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"cellForRowAtIndexPath");
static NSString *MyIdentifier = #"customCell";
EventTableCell *cell = (EventTableCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
//cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
[[NSBundle mainBundle] loadNibNamed:#"EventTableCell" owner:self options:nil];
cell = self.eventCell;
}
//Set up the cell
int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
[[cell eventNameLabel] setText:[[stories objectAtIndex: storyIndex] objectForKey: #"title"]];
[[cell eventDateLabel] setText:[[stories objectAtIndex: storyIndex] objectForKey: #"date"]];
NSString * storyLink = [[stories objectAtIndex: storyIndex] objectForKey: #"link"];
// clean up the link - get rid of spaces, returns, and tabs...
storyLink = [storyLink stringByReplacingOccurrencesOfString:#" " withString:#""];
storyLink = [storyLink stringByReplacingOccurrencesOfString:#"\n" withString:#""];
storyLink = [storyLink stringByReplacingOccurrencesOfString:#" " withString:#""];
//NSString *string = [[NSString alloc] stringWithString:[[stories objectAtIndex: storyIndex] objectForKey: #"link"]];
NSURL *url = [NSURL URLWithString:storyLink];
NSData *data = [[NSData alloc] initWithContentsOfURL: url];
UIImage *image = [UIImage imageWithData: data];
[[cell eventImage] setImage:image];
return cell;
}
My table has a lot of these custom cells in it, more than the view can show at once. So therefore I need to scroll up/down to see all the cells. My problem is this, when I scroll it lags a lot and in the debugger console it calls cellForRowAtIndexPath as each new cell comes into the view. Each cell holds an image which is downloaded from a URL and hence has to be converted to a UIImage. I believe this is whats causing the lag.
Can anyone direct me as to what I need to do in order to download the images and have them display in the cells without causing major lag in the app?
IE: Where would I put the download/conversion code so that the image is already stored as an image before the cell needs to be dispayed?
Thanks,
Jack
If you want to pre-download the images, you could do it at any point in your application and cache them for later. For example, you could start the downloads just after you download and parse the RSS feed. You can save them in the directory returned by [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]. It would be best to use asynchronous method to download them, and have just a few downloads active at a time for best performance.
If you want to load them as needed, set a placeholder image when you allocate the cell and use an asynchronous NSURLConnection or the like to do the download. Once the download completes, replace the placeholder with the real image (and cache it for reuse later, of course).
Related
I'm creating an app using the Twitter API and parsing with JSON and every time I load the image into the cells it's taking multiple images and everything runs slowly. How would I go on by getting the image once then put the same image into all cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"TweetCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSDictionary *tweet = [tweets objectAtIndex:indexPath.row];
NSString *text = [tweet objectForKey:#"text"];
NSString *time = [tweet objectForKey:#"created_at"];
time = [time stringByReplacingOccurrencesOfString:#" +0000 "withString:#"/"];
NSString *twitterImage = [[tweet objectForKey:#"user"] objectForKey:#"profile_image_url_https"];
NSString *completeImage = [NSString stringWithFormat:#"%#", twitterImage];
NSData * imageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: completeImage]];
imageLabel.image = [UIImage imageWithData: imageData];
cell.imageView.image = [UIImage imageWithData: imageData];
cell.textLabel.text = text;
cell.textLabel.numberOfLines = 3;
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#", time];
}
return cell;
}
Looks like this right now but really laggy when I scroll.
http://gyazo.com/8ab8325f3921fdb7e4f0ea0107d389ac.png
Looks to me like the problem is in these lines:
NSDictionary *tweet = [tweets objectAtIndex:indexPath.row];
NSString *twitterImage = [[tweet objectForKey:#"user"] objectForKey:#"profile_image_url_https"];
I believe that is getting a new copy of the image for each cell. Each indexPath.row is a new tweet, thus you are getting multiple twitterImage
You should use cache for images. I hope this link helps you:
http://khanlou.com/2012/08/asynchronous-downloaded-images-with-caching/
If it's always the same picture, load it before the table's loading in a class parameter (for example in the viewDidLoad) and always use this parameter.
If it's dynamic, load the image in the background using performSelectorInBackground.
The problem that you are facing is because you are downloading all the images on the main thread.
To solve this either :
Download images using a separate thread using NSOperationQueue.
A good tutorial on the same : http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues
Use this class 'AsyncImageView'. I have used this and it works fine. So, instead of UIImageView you will need to use the class AsyncImageView and this library will manage the downloading for you asynchronously.
https://github.com/nicklockwood/AsyncImageView
I have a table view that's scrolling slowly. Does anyone know why that might be?
There is an image for each row, but even after the images are loaded it still stutters and scrolls slowly.
thanks for any help
here's my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableCell";
SimpleTableCell *cell = (SimpleTableCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"SimpleTableCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
// Get item from tableData
NSDictionary *item = (NSDictionary *)[displayItems objectAtIndex:indexPath.row];
// display the youdeal deal image
photoString = [item objectForKey:#"image"];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:photoString]]];
cell.titleLabel.text = [item objectForKey:#"supercat"];
cell.descriptionLabel.text = [item objectForKey:#"title"];
NSString *convertedLeftCount = [NSString stringWithFormat:#"%#",[item objectForKey:#"left_count"]];
cell.amountLabel.text = convertedLeftCount;
cell.thumbnailImageView.image = image;
cell.priceLabel.text = [item objectForKey:#"cat"];
return cell;
}
It's due to the image loading mechanism you used.
You are loading the image from url in the main thread. That's why the UI is blocked for some time also the dataWithContentsOfURL: is a synchronous call. So the UI will respond after getting the image data.
Apple states that the time taking processes like webrequest,parsing huge data etc must be done on other threads rather than main thread.
And all UI related tasks must be done on main thread.
Solutions:
Request the image in background thread, not in main thread.
Cache the image once you get it
Source code and Third Party Libraries
Here are some links which will help you to understand the basic idea of loaing image using asynchronous methods
LazyTableImages
HJCache
SDWebImage
The images are getting loaded every time a cell is loaded, because the imageWithData: doesn't use any cache.
Edit: I saw a comment that suggests loading images asynchronously. You already have your custom class for each cell so it should be easy to do it. If it were an answer I'd vote it up
I think You are trying to say this.
NSURL* url = [NSURL URLWithString:#"http://www.YourImageUrl.com"];
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];
// Now workout with the image
}
}];
This will make the asynchronous call, and load the images after tableview loaded i.e when the image load complete the image will show but the table will be loaded when the table view is needed to load.
I have the following code: (Note: newsImageURL is an NSArray)
NSString *imagesURL = #"http://aud.edu/images/newsimage01.png,http://aud.edu/images/newsimage04.png,http://aud.edu/images/newsimage02.png,http://aud.edu/images/newsimage03.png,http://aud.edu/images/newsimage01.png,http://aud.edu/images/newsimage04.png,http://aud.edu/images/newsimage01.png,http://aud.edu/images/newsimage04.png,http://aud.edu/images/newsimage01.png,http://aud.edu/images/newsimage04.png,";
newsImageURL = [[NSArray alloc] initWithArray:[AllNewsHeadLine componentsSeparatedByString:#","]];
I am trying to load these images into a cell using the code below:
NSData* imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString: [newsImageURL objectAtIndex:indexPath.row]]];
cell.image = [UIImage imageWithData:imageData];
The image loads fine when I use this line instead:
cell.image = [UIImage imageNamed:#"imagename.png"];
What am I doing wrong?
You should use an existing framework which supports caching, default place holders and lazy loading of images.
https://github.com/rs/SDWebImage is a good and simple framework
#import "UIImageView+WebCache.h"
...
- (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];
}
// Here we use the new provided setImageWithURL: method to load the web image
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://aud.edu/images/newsimage01.png,http://aud.edu/images/newsimage04.png"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
cell.textLabel.text = #"My Text";
return cell;
}
You can load data this way:
NSData *data = [NSData dataWithContentsOfURL: [NSURL URLWithString: [newsImageURL objectAtIndex:indexPath.row]]];
And you can instantiate the array of URLs this way too:
NSArray *newsImageURL = [imagesURL componentsSeparatedByString:#","];
However, if someone scrolls around on the table a great deal, you may end up loading the images many times over as the cells are recycled.
Maybe you can have a try https://github.com/SpringOx/ALImageView.git.It is much simpler than SDWebImage.You only need two source files(ALImageView.h/ALImageView.m).You can reuse the image view to reload different urls in a tableview cell.
Support local and memory cache;
Support place holders;
Support tap touch(target-action);
Support corner for the image view;
You Can Easily load all the images with the Help of Following code ,Details Array is a Main Array
Details Array :- {
"item_code" = 709;
"item_desc" = Qweqweqwe;
"item_name" = AQA;
"item_photo" = "http://toshaya.com/webapp/snap&sell/api/img_items/709.png";
"item_price" = "0.00";
"item_till" = "20-25";
"item_type" = Orange;
latitude = "";
longitude = "";
}
With the Help of Following Code Retrieve The Photo-URL into String
NSString * result = [[DetailArray objectAtIndex:indexPath.row]objectForKey:#"item_photo"]; //componentsJoinedByString:#""];
NSLog(#"%#",result);
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:result]];
cell.icon.image = [UIImage imageWithData:imageData];
because i'm a newby at Stackoverflow i cannot comment someones anwser yet. (my reputation is 16..). I got a question about this anwser: How do I put this JSON data into my table view? Please help me, I'm living in a nightmare :)
Fulvio sais you have to use [eventNameList addObject:event]; and [eventNameList objectAtIndex:indexPath.row]; to store and get the event data but. addObject is an NSMutableSet method and objectAtIndex:indexPath.row is not. So i cannot use this method to get the data from the NSMutableSet.
Besides that, i can use the count methods neither.
Any Idea's ?
Assuming you have an NSDictionary, you could use the [dictionary allKeys] method to retrieve an array with all keys (lets call it keyArray for now). For the rowCount you could return the count of objects in this keyArray. To get the item that needs to be displayed in the cell you could use [dictionary objectForKey:[keyArray objectAtIndex:indexPath.row]]] to get the appropriate dictionary for the displayed cell.
In code:
// use the keyArray as a datasource ...
NSArray *keyArray = [jsonDictionary allKeys];
// ------------------------- //
// somewhere else in your code ...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [keyArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
// set some cell defaults here (mainly design) ...
}
NSString *key = [keyArray objectAtIndex:indexPath.row];
NSDictionary *dictionary = [jsonDictionary objectForKey:key];
// get values from the dictionary and set the values for the displayed cell ...
return cell;
}
#Tieme: apparantly the URL you use already returns an array, you don't really need to process a dictionary (you could just use the array as the dataSource), check out the following:
SBJSON *json = [[[SBJSON alloc] init] autorelease];
NSURL *url = [NSURL URLWithString:#"http://www.my-bjoeks.nl/competitions/fetchRoutes/25.json"];
NSString *string = [[[NSString alloc] initWithContentsOfURL:url] autorelease];
NSError *jsonError = nil;
id object = [json objectWithString:string error:&jsonError];
if (!jsonError) {
NSLog(#"%#", object);
NSLog(#"%#", [object class]); // seems an array is returned, NOT a dictionary ...
}
// if you need a mutableArray for the tableView, you can convert it.
NSMutableArray *dataArray = [NSMutableArray arrayWithArray:object]
eventNameList should be defined as an NSMutableArray, not an NSMutableSet. NSMutableArray responds to both -addObject (it puts the new object at the end of the array) and -objectAtIndex: and when you think about it, a table view is essentially an ordered list and so is an array whereas a set is not.
LUCKY:)
Assuming that you might be having nsmutablearray of nsdictionary.
In such case you can get data using:
[dictionary objectforkey:#"key"] objectAtIndex:indexpath.row]
I currently have a tableview with YouTube videos embedded inside of the custom cells.
I did this because from my research it seemed like the only way to allow the videos to load without leaving my application.
The problem is this
The thumbnails take a while to load. As I scroll down the list of videos, it keeps having to load the thumbnails. If I scroll back up, it tries to load the video thumbnails yet again.
Has anyone got any suggestions on either better ways of doing this, or ways of getting the table cells to keep the data and not replace it?
My code looks like this:
-(UITableViewCell *)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath{
static NSString *MyIdentifier = #"MyIdentifier";
YouTubeCell *cell = (YouTubeCell*)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if(cell ==nil){
cell = [[[YouTubeCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] autorelease];
}
NSDictionary *dic = [youTubeArr objectAtIndex:indexPath.row];
[cell updateCellData:dic];
return cell;
}
-(void)updateCellData:(NSDictionary*)dict
{
NSDictionary *tempDic = [dict objectForKey:#"video"];
self.titleLbl.text = [tempDic objectForKey:#"title"];
NSString *viewCountStr = [NSString stringWithFormat:#"%# views -",[tempDic objectForKey:#"viewCount"]];
viewCountLbl.text = viewCountStr;
uploadedDateLbl.text = [tempDic objectForKey:#"uploaded"];
NSDictionary *videoDic = [tempDic objectForKey:#"player"];
NSString *videoStr = [NSString stringWithFormat:#"%#",[videoDic objectForKey:#"default"]];
NSString *embedHTML =
#"<html><head>\
<body style=\"margin:0\">\
<embed id=\"yt\" src=\"%#\" type=\"application/x-shockwave-flash\" \
width=\"%0.0f\" height=\"%0.0f\"></embed>\
</body></html>";
// videoView = [[UIWebView alloc] initWithFrame:CGRectMake(3, 5, 100, 60)]; initialzed in ///initwithstyle of cell
NSString *html = [NSString stringWithFormat:embedHTML, videoStr, videoView.frame.size.width, videoView.frame.size.height];
[videoView loadHTMLString:html baseURL:nil];
}
You should cache your loaded images.
One approach could be to create for example a mutable dictionary, in which you store your images with keys unique to your UITableViewCells. In cellForRowAtIndexPath you retrieve the corresponding image by calling for example [dictionary objectForKey:uniquecellidentifier]. If it returns nil, you know the image has not yet been loaded and you should create a request to do so. As soon as the loading has finished, you store the image in the dictionary ([dictionary setObject:image forKey:uniquecellidentifier]
This should get you a more specific idea:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
NSString *cellid=[NSString stringWithFormat:#"Cell%i%i", indexPath.section, indexPath.row];
if([dictionary objectForKey:cellid]){
NSLog(#"Retrieving value for %#: %#", cellid, [dictionary objectForKey:cellid]);
cell.textLabel.text=[dictionary objectForKey:cellid];
//Show image from dictionary
}else{
NSLog(#"Now value set for %#.", cellid);
[dictionary setObject:[NSString stringWithFormat:#"Testvalue%#", cellid] forKey:cellid]; //Save image in dictionary
cell.textLabel.text=#"Loaded";
}
return cell;
}
Create an NSMutableDictionarynamed "dictionary" in your header file and initialize it in viewDidLoad:
dictionary = [[NSMutableDictionary alloc] init];
Header file:
NSMutableDictionary *dictionary;
This will result in the following behaviour:
The first time, your cell is displayed, it shows "Loaded". In all subsequent appearances it will display its set value.