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.
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
UITableView shows extra blank space on the left, after I inserted array of parsed data. So, my question is how to trim whitespace in the tableView. I guess I have the blank space because of href hyperlink to the text I parsed which is shown as blank in the cell.
This is the part of HTML I parsed:
<h3 style="clear:left"><a class="c4" href="http://www.website.ge/article-22624.html">
Facebook - the text I have parsed </a></h3>
This is the code I used:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSError *error = nil;
NSURL *url=[[NSURL alloc] initWithString:#"http://www.website.ge/news"];
NSString *strin=[[NSString alloc] initWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil];
HTMLParser *parser = [[HTMLParser alloc] initWithString:strin error:&error];
if (error) {
NSLog(#"Error: %#", error);
return;
}
listData =[[NSMutableArray alloc] init];
HTMLNode *bodyNode = [parser body];
NSArray *dateNodes = [bodyNode findChildTags:#"span"];
for (HTMLNode *inputNode in dateNodes) {
if ([[inputNode getAttributeNamed:#"class"] isEqualToString:#"date"]) {
//NSLog(#"%#", [inputNode contents]);
//[listData addObject:[inputNode contents]];
}
}
NSArray *divNodes = [bodyNode findChildrenOfClass:#"c4"];
for (HTMLNode *inputNode in divNodes) {
[listData addObject:[inputNode contents]];
}
}
In the table view I see blank space at the beginning of the parsed data. It must be the hyperlink which is translated into blank space.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
//here you check for PreCreated cell.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
//Fill the cells...
cell.textLabel.textAlignment=UITextAlignmentLeft; // attempt to move it to the left
cell.textLabel.text = [listData objectAtIndex: indexPath.row];
//yourMutableArray is Array
return cell;
}
I'm assuming, based on your description, that you have extra whitespace in the text which you are displaying in a label.
If this is the case, take the string that you have created, and before setting the label use this:
theStringToDisplay = [theStringToDisplay stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
theLabel.text = theStringToDisplay;
EDIT now that you have supplied your code:
In your case, I would use stringByTrimmingCharactersInSet when you are first setting the strings in your array:
NSArray *divNodes = [bodyNode findChildrenOfClass:#"c4"];
for (HTMLNode *inputNode in divNodes) {
[listData addObject:[[inputNode contents] stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]];
}
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).
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 got same warning here “local declaration hides instance variable” warning
but I got more problems...
Here is my code
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.someaddress.php"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
// create the connection with the request
// and start loading the data
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
NSLog(#"\n\nCONNECTION: %#", theConnection);
NSData *returnData = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:nil error:nil];
NSString *listFile = [[NSString alloc] initWithData:returnData encoding:NSASCIIStringEncoding];
NSMutableArray *plist = [[NSMutableArray alloc] init];
plist = [listFile propertyList];
NSLog( #"\n 1111 plist is \n%#", plist );
//I can get a plist format data here,But nothing in 2222
NSLog(#"Now you see me tableView Row Count");
NSLog(#"TOTAL PLIST ROW COUNT IS = %i", [plist count]);
// Return the number of rows in the section.
return [plist count];
}
and I got Warning here"Local declaration of 'plist' hides instance variable"
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"LightCell";
LightCell0 *cell =(LightCell0 *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[LightCell0 alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
// Set up the cell…
NSLog(#"Now you see me Load Data %i", indexPath.row);
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
//I try to get list data here But RETURN NULL
NSLog( #"\n 2222 plist is \n %#", plist);
switch (indexPath.row) {
case 0:
if ([plist valueForKey:#"nodeStatus"] == 0){
cell.lightImageView.image = [UIImage imageNamed:#"lightOff.png"];
NSLog(#"value for key Node Status : %#" ,[self.plists Valuefokey:#"nodeStatus"]);
//also return NULL !!
}
else if([self valueForKey:#"nodeStatus"] == 1){
cell.lightImageView.image = [UIImage imageNamed:#"lightOn.png"];
}
break;
case 1:
cell.lightLocation.text =[plist valueForKey:#"nodeName"] ;
if ([plist valueForKey:#"nodeStatus"] == 0){
cell.lightImageView.image = [UIImage imageNamed:#"lightOff.png"];
}
else if([plist valueForKey:#"nodeStatus"] == 1){
cell.lightImageView.image = [UIImage imageNamed:#"lightOn.png"];
};
break;
default:
break;
}
return cell;
}
This is the tow items I create in a plist
{
category = Light;
nodeID = 1;
nodeName = "Living Room";
nodeStatus = 0;
nodeTrigger = 0;
nodeType = "light_sw";
},
{
category = Light;
nodeID = 2;
nodeName = Kitchen;
nodeStatus = 0;
nodeTrigger = 0;
nodeType = "light_sw";
}
So that's my question ,Why can't I pass "plist" from
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
...
}
to
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
...
}
and I use NSMutableArray *plist = [[NSMutableArray alloc] init];
But still appear "Local declaration of 'plist' hides instance variable"
???
hope someone can figure out this problem
Best Regards !
and I got Warning here"Local declaration of 'plist' hides instance variable"
Well, then, you should fix that.
The warning is telling you that you've declared two variables named plist: One local to this instance method, and the other an instance variable. The local variable, having a narrower scope, hides the instance variable, so that when you refer to plist in the method, you are referring to the local variable. This means that you cannot access anything stored in the instance variable by another method, nor store anything in it for another method to retrieve.
The solution is either to kill off or to rename the local variable. If the latter is what you want, use Xcode's “Edit All in Scope” feature.
Also:
NSMutableArray *plist = [[NSMutableArray alloc] init];
plist = [listFile propertyList];
Creating the array on the first of those lines is redundant, because you immediately replace your pointer to that array with the pointer to another array, returned by propertyList. Thus, you never use and you leak the first array. You should at least cut out the creation of the first array, and you should probably cut out the entire first line (thereby cutting out both the first array and the local variable).
Here is the code I fix the warning ,the program can build without any warning
it also can display the result after reading the plist in tableview
1.Load the plist:
- (void)viewDidLoad {
NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www. someaddress.php"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];
NSData *returnData = [NSURLConnection sendSynchronousRequest:theRequest returningResponse:nil error:nil];
NSString *listFile = [[NSString alloc] initWithData:returnData encoding:NSASCIIStringEncoding];
plist = [listFile propertyList];
}
2.return the number to rows
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [plist count];
}
3.read the plist data to show result in cells
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"LightCell0";
LightCell0 *cell =(LightCell0 *) [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[LightCell0 alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
// Set up the cell…
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
int i;
for (i=0; i<[plist count]; i++) {
//Get nodeName
if(indexPath.row == i)
{
cell.lightLocation.text = [[[plist objectAtIndex:i] valueForKey: #"nodeName"]description];
//Get Light Status to show the image
if ([[[plist objectAtIndex:i] valueForKey: #"nodeStatus"] intValue] == 0){
cell.lightImageView.image = [UIImage imageNamed:#"lightOff.png"];
}
else if([[[plist objectAtIndex:i] valueForKey: #"nodeStatus"] intValue] == 1){
cell.lightImageView.image = [UIImage imageNamed:#"lightOn.png"];
cell.lightSwitch.on=YES;
}
}
}
return cell;
}
It can get the right data ,and display the correct result in the tableview cells
BUTTTTTTT
If you scroll up the tableview,it's ok,when you on the top it will scroll down Automatically
When you "scroll down" the tableview,program crash ???
WHY ??? did I write anything wrong ???
plist = [listFile propertyList];=====>self.plist = [listFile propertyList];
THAT IS CORRECT