PDA

View Full Version : NSMutableArrays and UILabels




macprogrammer80
Apr 22, 2009, 04:01 AM
I have a NSMutableArray which is getting data parsed from an RSS feed. The only issue is that I want to display my data in a UITableView, for which I am using UILabels. How can I get the data from the NSMutableArray to go into the UILabel, or what other alternatives do I have?

Extra question:
What would be the best way to count and update on the screen the amount of characters in a UILabel?



johnnyjibbs
Apr 22, 2009, 04:24 AM
UILabels have a text property which takes an NSString object. Therefore, you'll need to convert the objects within your NSMutableArray into NSString objects (if they aren't already) and then pass them to the labels that way.

e.g.

// label for cell at current index path...
label.text = [myMutableArray objectAtIndex:indexPath.row];


You can get the number of characters in the label by using the length property of the text

e.g.

int numberOfCharacters = label.text.length;


You can then print to another label the number of characters in the first label's text:


secondLabel.text = [NSString stringWithFormat:@"The first label contains %i characters",numberOfCharacters];

macprogrammer80
Apr 22, 2009, 04:40 AM
Thanks for the label length, I remember it from my C programming days and didn't realise it was that easy.

I tried your NSMutableArray code, but the console said “EXC_BAD_ACCESS” with no further details.

The code I used was the following:

myLabel.text = [stories objectAtIndex:indexPath.row];;


With Stories being my NSMutableArray holding my RSS feed headlines.

Any ideas?

johnnyjibbs
Apr 22, 2009, 04:49 AM
That error message normally means you're trying to access a free'd object. Insert the following NSLog command prior to the setting of the label's text and see if you get the same error:


NSLog(@"Adding to label: %@",[stories objectAtIndex:indexPath.row]);


The object at that index path must be an NSString, otherwise the label will not be able to accept it as its text property. If it is an NSString, you should see it print whatever you want it to say on the label in the console window (using the view menu in Xcode to bring up the console).

If the NSLog fails, then check that you have the appropriate retain on your object. If you are using an instance variable to store your stories array, then make sure you have set up the property as retain in the header file i.e.

@property (nonatomic, retain) NSMutableArray *stories;


Otherwise, make sure that your array isn't being released or autoreleased before you are accessing it.

I am assuming that the stories array is the datasource for your UITableView.

macprogrammer80
Apr 22, 2009, 05:39 AM
I am still getting this error, but the NSMutableArray in question is not being released and it is being retained. If it helps, the way it's getting its data is from a series of strings combined together. The one that gives it its story is "currentTitle". The only issue when I get the label to output it is that rather than have 20 cells with different headlines, they all display the same headline (so 20 of the same thing).

Any ideas?

johnnyjibbs
Apr 22, 2009, 06:03 AM
Just as a check the general flow of your application view controller should be as follows.

The NSMutableArray (stories) should be an array of 20 objects, each containing an NSString of the RSS feed content (or headline). Later, you can expand each object to become a dictionary with various keys containing other information such as time last modified, etc.

Your UITableView data should be based on the NSMutableArray - i.e. have 1 section and 20 rows. The index path for each row (numbered 0 to 19) should correspond to the index of each object in your array (again numbered 0 to 19).

Therefore, your number of sections in table view method should return 1 and your number of rows in section should return [stories count].

In your cellForRowAtIndexPath method, you should be dealing with your code to populate the label (well, you may have a custom cell subclass or something more complicated but for simplicity I will just go through the motions here). Assuming you have one label per cell, you would have defined one label when creating the cell and added it to the content view of the cell.

Then, for each cell, your call to populate the text as previously described should just locate the object at the index of your array that corresponds to the index path of the row (because of that 1:1 relationship between your array and your table view row index paths as mentioned above):

label.text = [stories objectAtIndexPath:indexPath.row];


Thus what this code is doing is, for each cell, populating the text of the label for this particular cell with the object in the corresponding point of your array. I can't see how the above code would not work!

If you are getting 20 of the same thing then either you are not accessing the correct label or your array contains 20 of the same object (you can try an NSLog of the stories array and read the output to check).

If there is any error in this it must be that you object is not a valid NSString or that it has already been released. I'm not very familiar with coding RSS but essentially what you are getting is a string. What method are you using to convert the parsed RSS xml into a string? You could try NSObject's isKindOfClass: method to test that you've got a key-value-coding compliant NSString and output the results to an NSLog or check the debugger.

macprogrammer80
Apr 22, 2009, 06:20 AM
I think it would just be easier if I post the code that I use:


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil) {
cell = [self tableviewCellWithReuseIdentifier:CellIdentifier];
}

[self configureCell:cell forIndexPath: [indexPath length] -1];
return cell;
}


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat result;
result = 100;
return result;

}

- (UITableViewCell *)tableviewCellWithReuseIdentifier:(NSString *)identifier {
CGRect rect;

rect = CGRectMake(0.0, 0.0, 320.0, 150);

UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:rect reuseIdentifier:identifier] autorelease];

#define TWEET_TAG 1
#define TIME_TAG 2
#define ALIAS_TAG 3

UILabel *label;
rect = CGRectMake(67, 36, 248, 57);
label = [[UILabel alloc] initWithFrame:rect];
label.tag = TWEET_TAG;
label.font = [UIFont systemFontOfSize:13.5];
label.adjustsFontSizeToFitWidth = YES;
label.numberOfLines = 6;
[cell.contentView addSubview:label];
label.highlightedTextColor = [UIColor whiteColor];
[label release];

rect = CGRectMake(53, 25.5, 192, 10);
label = [[UILabel alloc] initWithFrame:rect];
label.tag = TIME_TAG;
label.font = [UIFont systemFontOfSize:10];
label.textAlignment = UITextAlignmentRight;
[cell.contentView addSubview:label];
label.highlightedTextColor = [UIColor whiteColor];
[label release];

//The user's name/alias
rect = CGRectMake(53, 5);
label = [[UILabel alloc] initWithFrame:rect];
label.tag = ALIAS_TAG;
label.font = [UIFont boldSystemFontOfSize:15.5];
label.textAlignment = UITextAlignmentRight;
[cell.contentView addSubview:label];
label.highlightedTextColor = [UIColor whiteColor];
[label release];

return cell;
}


- (void)configureCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath {

UILabel *label;

label = (UILabel *)[cell viewWithTag:TWEET_TAG];
label.text = currentTitle; <-- this is where I was putting the output of stories

label = (UILabel *)[cell viewWithTag:TIME_TAG];
label.text = currentDate;

label = (UILabel *)[cell viewWithTag:ALIAS_TAG];
label.text = @"Alias";

}


(Sorry about my bad spacing and bad naming of variables; I'm always getting yelled at for that).

Thanks for all of your help, by the way!

johnnyjibbs
Apr 22, 2009, 08:23 AM
Ok, firstly this piece of code here where you call your configureCell looks wrong:


[self configureCell:cell forIndexPath: [indexPath length] -1];

For this you should just pass the index path you get given as a parameter from the method. It should read:


[self configureCell:cell forIndexPath:indexPath];


This will ensure that you are configuring the cell at the same index path as you are creating the cell.

Nextly, it appears that you're storing the title in an instance variable called currentTitle but you don't declare it anywhere in these methods. This explains why all your titles are saying the same thing because they are using the same static instance variable. Remember, your configureCell method will be called for each and every (visible) cell in your list. In your configureCell method you should insert the following or something similar at the start of the method:


currentTitle = [stories objectAtIndex:indexPath.row];


This will set the variable to be the correct object for that particular cell. You don't really need an instance variable so you could just make currentTitle a local string in that method, e.g.:


NSString *currentTitle;
currentTitle = [stories objectAtIndex:indexPath.row];


(If you do this, make sure you remove the decalaration of currentTitle as an instance variable in the interface file etc).

macprogrammer80
Apr 22, 2009, 06:20 PM
It seems I made a mistake when I said what currentTitle is, and I missed some code. Here's the part where I declare the value of currentTitle:


- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{
currentElement = [elementName copy];
if ([elementName isEqualToString:@"item"]) {
item = [[NSMutableDictionary alloc] init];
currentTitle = [[NSMutableString alloc] init];
currentDate = [[NSMutableString alloc] init];
currentSummary = [[NSMutableString alloc] init];
currentLink = [[NSMutableString alloc] init];
currentAvatar = [[NSMutableString alloc] init];
}
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{
//NSLog(@"ended element: %@", elementName);
if ([elementName isEqualToString:@"item"]) {
// save values to an item, then store that item into the array...
[item setObject:currentTitle forKey:@"title"];
[item setObject:currentLink forKey:@"description"];
[item setObject:currentSummary forKey:@"summary"];
[item setObject:currentDate forKey:@"date"];
[item setObject:currentAvatar forKey:@"user"];

}
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{
if ([currentElement isEqualToString:@"title"]) {
[currentTitle appendString:string];
} else if ([currentElement isEqualToString:@"hello!"]) {
[currentLink appendString:string];
} else if ([currentElement isEqualToString:@"text"]) {
[currentSummary appendString:string];
} else if ([currentElement isEqualToString:@"pubDate"]) {
[currentDate appendString:string];
} else if ([currentElement isEqualToString:@"avatar"]) {
[currentAvatar appendString:string];
}

}

macprogrammer80
Apr 22, 2009, 09:26 PM
Just as some extra info, I output the value of stories in the console, and it does grab the stories list and output 20 different stories. Could the issue be that it's not refreshing the table after it adds each thing?

Also, the main file I have uploaded and can be downloaded from here (http://m2software.net/MacRumors.m), as I realised that I missed more code.

johnnyjibbs
Apr 23, 2009, 04:51 AM
Just as some extra info, I output the value of stories in the console, and it does grab the stories list and output 20 different stories. Could the issue be that it's not refreshing the table after it adds each thing?

Also, the main file I have uploaded and can be downloaded from here (http://m2software.net/MacRumors.m), as I realised that I missed more code.
Any time that you want to update the table view without scrolling through, you would need to call the [tableView reloadData] method. Only call it when you have to though as it is quite expensive to use from a processing perspective.

However, you can actually just go through each visible cell using a for {} loop and reset each of the label texts to the new text, plus set each label for redrawing using [label setNeedsLayout]. Something like:


// Call this whenever you have updated the RSS feed and want to live update the table
- (void)updateVisibleCells {

// Iterate through all visible cells
NSArray *visibleCellsArray = [tableView indexPathsForVisibleRows];
int it;
for (it = 0; it < [visibleCellsArray count]; it ++) {
// Get hold of the index path and the cell for the current row
NSIndexPath *indexPath = [visibleCellsArray objectAtIndex:it];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
// Now get hold of the label and set for update
UILabel *label = (UILabel *)[cell viewForTag:CELL_LABEL];
label.text = [stories objectAtIndex:indexPath];
[label setNeedsLayout];
}

}