UITextField artifacts - iphone

I have an iPhone app which has a Table View-based data input screen with a toggle, which when on shows all rows in another section of the table.
Sometimes, when the app is first loaded, and usually when it has been fully deleted from the phone, the UITextFields from the original section of the table are displayed in addition to the new rows, as below (main table section is above)
:
THe strangest thing about this is that this behaviour only occurs the first time this screen is displayed - it seems fine after that. Oh, and it only seems to occur on the phone, not the simulator.
Given the random nature of this, could it have something to do with other apps? I did have apps with similar namespaces running at the same time. The problem seemed to go away after I closed the apps down / deleted from phone.
I have included the code block that is run when the switch is changed below:
- (void)accountSwitchChanged {
[customerDetails saveField:#"addToAccount" WithBool:addToAccountSwitch.on];
NSArray *indexes = [NSArray arrayWithObjects:[NSIndexPath indexPathForRow:1 inSection:1], [NSIndexPath indexPathForRow:2 inSection:1], [NSIndexPath indexPathForRow:3 inSection:1],nil];
if (addToAccountSwitch.on)
[detailsTable insertRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationFade];
else
[detailsTable deleteRowsAtIndexPaths:indexes withRowAnimation:UITableViewRowAnimationFade];
}
Any ideas?
--EDIT
cellForRowAtIndexPath code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// get rid of grey background for iPad app
[tableView setBackgroundView:nil];
UITableViewCell *cell = nil;
NSDictionary *cellData = [[dataSourceArray objectAtIndex: indexPath.section] objectAtIndex:indexPath.row];
id cellControl = [cellData objectForKey:kCellControlKey];
static NSString *kCellControl = #"CellControl";
cell = [tableView dequeueReusableCellWithIdentifier:kCellControl];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:kCellControl] autorelease];
cell.textLabel.textColor = [[[ConfigManager sharedInstance].skin valueForKey:#"tableCellHeadingTextColour"] toUIColor];
cell.backgroundColor = [[[ConfigManager sharedInstance].skin valueForKey:#"tableCellBgColour"] toUIColor];
cell.textLabel.font = [UIFont boldSystemFontOfSize:17];
} else {
// a cell is being recycled, remove the old control (if it contains one of our tagged edit fields)
UIView *viewToCheck = nil;
viewToCheck = [cell.contentView viewWithTag:kDetailsViewTag];
if (!viewToCheck)
[viewToCheck removeFromSuperview];
}
// if control is not a text field, make it a button
if (![cellControl isKindOfClass:[UITextField class]])
cell.selectionStyle = UITableViewCellSelectionStyleGray;
else
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.text = [cellData objectForKey:kCellTitleKey];
[cell.contentView addSubview:cellControl];
return cell;
}

Something to check and eliminate If it is only occurring the first time view is displayed, it's always worth checking whether you are assuming the view is loaded before you are doing work on it. This doesn't come up very often, but when it does it can be very hard to track down. To check whether this might be your issue, add a [viewController view] call just after you create the view for the first time. This forces the view to be loaded. If the issue goes away when you add this, you've tracked the source of the issue. And you can even leave the [viewController view] call in as a fix, or work through your code to allow for lazy instantiation.
All this said, far more likely to be something funny in your tableView:cellForRowAtIndexPath: code. If you want good stack overflow, post all or relevant fragment of the that code (or the relevant code it calls).
Follow up on posted code:
(1) if (!viewToCheck) [viewToCheck removeFromSuperview] won't do anything. It'll only send a removeFromSuperview message when viewToCheck is nil and that won't do anything. Not sure if this will be key issue, but it's something to put right.
(2) [cell.contentView addSubview:cellControl]; - this looks problematic - it's usually best to only add subviews when you create the cell. You can hide or show in the main body of tableView:cellForRowAtIndexPath: - that would, I suspect, work for you here. With this code there's a risk here that you'll either add multiple subviews when you only want one or (which may be what's going on here) that you end up not removing a subview when a cell is recycled. If the viewToCheck code is supposed to be removing the added cellControl then, as (1), it won't do that just now because your if condition is wrong.

Related

Removing a checkmark accessory that is placed by default in my TableView

Brand new poster here on StackOverflow. I've been reading and learning here for some time, but I need to start asking a few questions and interacting, so any help with a few of my issues would be greatly appreciated.
I basically generate a list in a tableView of default sounds from a plist in cellForRowAtIndexPath. This generates me a list of say "x" sounds and one of those sounds has a default checkmark accessory placed on its cell. I have a plist file that stores the 'active' sound file's index value, this is how I put the original checkmark in the generated tableView. So, in the code below, as you can see, I load in the plist's value for the 'active' sounds index, however, when a user interacts with my tableView and they select a new sound, I need the default checkmark placed to be removed from view. Everything else is working fine. I can't seem to crack this simple problem, Im sure its a simple syntax issue, i just don't know how to get it done. Im sure the solution is a single line of code or two and its right under my nose. Thank you for help in advance.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *dictionarySound = [NSDictionary dictionaryWithContentsOfFile:[self ActDataFilePath]];
NSNumber *defaultSoundIndex = [dictionarySound valueForKey:#"soundIndex"];
int theIntVal = [defaultSoundIndex integerValue];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
//Everything below works just fine, checkmarks are removed and placed accordingly, sounds are played just fine. I just need help above in removing the default checkmark
if(self.checkedPath)
{
UITableViewCell *uncheckCell = [tableView cellForRowAtIndexPath:self.checkedPath];
uncheckCell.accessoryType = UITableViewCellAccessoryNone;
}
cell.accessoryType = UITableViewCellAccessoryCheckmark;
self.checkedPath = indexPath;
....... other code below the plays my sounds on click and stores the newly active sound name and index value in the plist.
The usual way of dealing with this is to use didSelectRowAtIndexPath: to record the fact that the selection has changed and tell the table view to reload data, either all or just for the affected cells, then do the accessoryType configuration in cellAtRowForIndexPath:.
Other ways tend to fail if scrolling of the table is possible.
Alright boys and girls, here is the solution to my own question.
The checkmark is determined and placed in cellForRowAtIndexPath initially when the table is built based on my plist file. Here is the code for my cellForRowAtIndexPath that grabs my plist data, builds my table and sets the default selection with a checkmark.
NSDictionary *dicSound = [NSDictionary dictionaryWithContentsOfFile:[self ActDataFilePath]];
NSNumber *defaultSoundIndex = [dicSound valueForKey:#"soundIndex"];
//conversion of NSNumber to integer for comparison below to indexPath.row, both need to be int's
int theIntVal = [defaultSoundIndex integerValue];
static NSString *CheckMarkCellIdentifier = #"CheckMarkCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CheckMarkCellIdentifier];
cell.textLabel.text = [[MyArray objectAtIndex:indexPath.row] objectForKey:#"name"];
if(indexPath.row == theIntVal)
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
When the user interacts with the tableView by clicking on another cell and didSelectRowAtIndexPath kicks in, this is how Im placing the new checkmark and how Im setting the old cell to have no accessory checkmark.
//This is a dictionary file that contains a number of values I need to grab
NSDictionary *dictionarySound = [NSDictionary dictionaryWithContentsOfFile:[self ActDataFilePath]];
NSNumber *defaultSoundIndex = [dictionarySound valueForKey:#"soundIndex"];
//conversion of NSNumber to integer for comparison below to indexPath.row, both need to be int's
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if(self.checkedPath)
{
UITableViewCell *uncheckCell = [tableView cellForRowAtIndexPath:self.checkedPath];
uncheckCell.accessoryType = UITableViewCellAccessoryNone;
}
cell.accessoryType = UITableViewCellAccessoryCheckmark;
self.checkedPath = indexPath;
//THE FIX
[tableView reloadData];
The key here is the [tableView reloadData]; line as it reloads the table with the new options checked and removes the original checkmark. Obviously, Im reloading data which means that I have persistence built in in a plist file. I basically update the local dictionary's value for 'soundIndex' EVERY TIME a user clicks on a cell and then write that to the persistent plist file.
Hope this helps anyone else who is having this issue.
Try following code
cell.accessoryType = UITableViewCellAccessoryNone;

When an UITableView is empty, show an UIImage

This is related to another question of mine which wasn't answered in a helpful way (message when a UITableView is empty).
I'm trying to show an UIImage graphic that says You haven't saved any bookmarks over an UITableView when it's empty. I have NSNotification set-up so that when bookmarks are added or deleted, a message is sent so that the UITableView can be updated.
I've been trying to do it with this code. Why won't this work?
- (void)bookmarksChanged:(NSNotification*)notification
{
[self.tableView reloadData];
UIImageView* emptyBookmarks = [[UIImageView alloc] initWithFrame:CGRectMake(75, 100, 160, 57)];
emptyBookmarks.alpha = 1;
emptyBookmarks.image = [UIImage imageNamed:#"emptyBookmark.png"];
[self.view addSubview:emptyBookmarks];
[emptyBookmarks release];
if ([self.dataModel bookmarksCount] == 0)
{
emptyBookmarks.alpha = 1;
}
else
{
emptyBookmarks.alpha = 0;
}
}
I'm probably approaching this the wrong way... But if salvageable, what am I doing wrong?
When I initially have an empty bookmarks tableview, there's no image displayed. After I add a bookmark and then delete it, the image shows. Grrh.
Another way (and IMO the correct way) to do this is to manipulate the backgroundView property on the UITableView.
While making a single cell with a custom image cell would certainly works, I think it overly complicates the logic of your UITableViewController's data source. It feels like a kludge.
According to UITableView documentation:
A table view’s background view is automatically resized to match the
size of the table view. This view is placed as a subview of the table
view behind all cells , header views, and footer views.
Assigning an opaque view to this property obscures the background color
set on the table view itself.
While you probably don't want to just set it to your UIImageView, it is very easy to make a UIView that contains the UIImageView that you want.
Well first off if you were going to do it that way, you would need to reload the tableView after updating the image or model etc. and not before.
But you are probably making things more complicated than they need to be!
Why not just check to see if the data for section 0 and indexPath.row 0 are empty and if so in cellForRowAtIndexPath display a text message accordingly.
// First make sure there is always one row returned even if the dataModel is empty.
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger numRows = 0;
if ([self.dataModel lastObject]) {
// Return the number of rows in the section.
numRows = [self.dataModel count]; // etc.
}
if (numRows < 1) numRows = 1;
return numRows;
}
// Then display the data if there is some, otherwise a message if empty.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
if ([self.dataModel lastObject]) {
// setup the cell the normal way here.
} else { // the datasource is empty - print a message
cell.textLabel.text = nil;
cell.detailTextLabel.text = NSLocalizedString(#"You haven't saved any bookmarks", #"");
cell.detailTextLabel.textColor = [UIColor colorWithRed:0/255.0 green:0/255.0 blue:0/255.0 alpha:0.7];
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
Are you sure [self.dataModel bookmarksCount] is equal to 0 ?
While I agree that you are probably going about this the wrong way,
your image is allocated and added in your bookmark changed, your notification does not trigger when there are no bookmarks initially. Hence you don't see the image. Call the bookmar changed when your table view inits or appears.
Probably the best way to achieve this is to perform a check in your numberOfRowsInSection method to return 1 if your data source is empty. Then in cellForRowAtIndexPath check if your data source is empty and if it is, create a custom cell that contains whatever you want. In heightForRowAtIndexPath you need to return your custom cell height if your datasource is empty, but only if you want the cell larger than the default. At least that is how I would approach it.
when bookmarks count is nil add one to your row method:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
int c;
c = bookmarks.count;
if(c == 0){
c = 1;
}
return c;
}
and then the same check again in your cellforrowatindexpath.
Another thing to be aware of in this situation is that if you're using core data and you're datasource is feeding off an entity, you will want to make sure your model matches. You can get some weird side-effect behavior in certain situations. This is especially true if you allow editing and core data has an empty model but you're tableview is still showing a cell.

iPhone : friend list, how did Facebook accomplished that?

I wonder if anyone can speculate or better yet provide a piece of code as for the implementation of the lengthy friends list in the Facebook iPhone app.
when you open the app and go strait to the friends list, you get the list almost in an instant, at least for me with ~500 friends.
when I try it in my own app it takes lots of precious seconds to populate the table view with the same data, so how does Facebook accomplished such a quick response time ?
upon looking at the tableview in the facebook app you notice there is no scroll bar usually found in such tableview, could that be one sign of the neat trick facebook is utilizing to achieve this rapid rows insert ? could it be they implemented some sort of a virtual tableview with only holds a few dozen rows but rotates them ?
any thoughts ?
the UITableView will let you do this. There are a number of examples on the internet with UITableView and Custom Cell's
Essentially, you load your images in the background, and you reuse the Cells that are in the tableview
EDIT Added example code to demonstrate how this is accomplished.
IMPORTANT NOTE
This code was not tested and may or may not actually function as is.
It was pasted with some editing for length. I did a lot more then this in my app, but in the interest of keeping with the example requested I omitted a lot.
On with the example:
Here is where I get the cell, load it with the items that are readily available. And send it to the background thread to load the rest.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"OfferCell";
static NSString *CellNib = #"OfferItem";
OfferCell* cell = (OfferCell*)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:CellNib owner:self options:nil];
cell = (OfferCell*)[nib objectAtIndex:0];
}
NSDictionary* couponPackage = [self.jsonOfferData valueForKey:#"result"];
NSArray *couponList = [couponPackage valueForKey:#"offers"];
if ([couponList count] >= indexPath.row )
{
NSDictionary* couponData = [couponList objectAtIndex:indexPath.row];
Coupon *coupon = [[Coupon alloc] initWithDictionary:couponData];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:cell,#"cell",coupon,#"coupon", nil];
//Right here you would try to load any cached imaged from disk.
//Then send a Thread to the background to load the image.
[self performSelectorInBackground:#selector(loadTableViewCellData:) withObject:params];
//Load up the rest of the custom info into the custom cell.
[cell.captionLabel setText:coupon.name];
[cell.subTextLabel setText:coupon.subText];
[cell setAccessoryType:UITableViewCellAccessoryDetailDisclosureButton];
[cell setCommand:coupon.command];
[cell setParameter:coupon.commandArgs];
[cell setImageURL:coupon.imageURL];
[cell setImageAltURL:coupon.imageAltURL];
[cell setRegistrationCode:coupon.registrationCode];
[coupon release];
}
return cell;
}
as you can see, i call a background thread before i even load the custom content in the cell.
- (void) loadTableViewCellData:(NSDictionary*) objectData
{
OfferCell *cell = [objectData objectForKey:#"cell"];
Coupon *coupon = [objectData objectForKey:#"coupon"];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[coupon iconURL]]]];
[objectData setValue:image forKey:#"image"];
self performSelectorOnMainThread:#selector(setImageOnMainThread:) withObject:objectData
}
after downloading the image, i send a Main thread request to update the Image that is in the cell object.
- (void) setImageOnMainThread:(NSDictionary*) objectData
{
OfferCell *cell = [objectData objectForKey:#"cell"];
Coupon *coupon = [objectData objectForKey:#"coupon"];
UIImage *image = [objectData objectForKey:#"image"];
cell.icon.image = image;
}
##AGAIN This May not Actually Function. ##
I did not copy all of my code for this. this is a hammer out so you can get the idea.
play with the code and test it. but the fundamentals are.
Dequeue the cell that will fit your needs (Reuse Identifier)
Use the cell if it can be dequeue'd or create a new one with a reuse identifier (my example uses a xib file named OfferItem.xib)
Send a thread to the background that will load the image data from disk or url (a combination of both is recommended)
Send a thread back to the UI when you are ready to load the image into the View (Updating the UI must be done on the main thread)
if you can do that, then your friends list (or in this case offers) will be loaded up as fast as possible. and the Images will pop on the screen as soon as they download.
Also if you use a Caching technique it will be faster for subsequent loads because in the the first method {tableView:cellForRowAtIndexPath:} you would load up the cached image immediately.
Aside from that, this should load your cell's pretty fast.
They obviously load the data from a local resource (plist, ManagedObject, ...)
Have a look at some sample code to draw a TableView:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *kCellIdentifier = #"MyCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:kCellIdentifier] autorelease];
}
return cell;
}
The dequeueReusableCellWithIdentifier: thing is one reason why TableViews in iOS can draw quickly. It works somehow like this:
1)You provide an identifier for a cell you're creating.
2)Cells that are visible at first get alloced (with identifier)
3)When a Cell is moved off the screen it gets put on a pile MyCellIdentifier
4)Whenever the system needs to draw a cell of identifier:MyCellIdentifier it first looks whether there are any cells currently unused on the MyCellIdentifier pile. If that's the case it picks one off the pile and thus doesn't have to alloc a new one. That way expensive allocing can be kept at a minimum.
I hope this answers your question :)

All UITableCells rendered at once... why?

I'm extremely confused by the proper behavior of UITableView cell rendering. Here's the situation:
I have a list of 250 items that are loading into a table view, each with an image. To optimize the image download, I followed along with Apple's LazyTableImages sample code... pretty much following it exactly. Really good system... for reference, here's the cell renderer within the Apple sample code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// customize the appearance of table view cells
//
static NSString *CellIdentifier = #"LazyTableCell";
static NSString *PlaceholderCellIdentifier = #"PlaceholderCell";
// add a placeholder cell while waiting on table data
int nodeCount = [self.entries count];
if (nodeCount == 0 && indexPath.row == 0)
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:PlaceholderCellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:PlaceholderCellIdentifier] autorelease];
cell.detailTextLabel.textAlignment = UITextAlignmentCenter;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
cell.detailTextLabel.text = #"Loading…";
return cell;
}
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
// Leave cells empty if there's no data yet
if (nodeCount > 0)
{
// Set up the cell...
AppRecord *appRecord = [self.entries objectAtIndex:indexPath.row];
cell.textLabel.text = appRecord.appName;
cell.detailTextLabel.text = appRecord.artist;
// Only load cached images; defer new downloads until scrolling ends
if (!appRecord.appIcon)
{
if (self.tableView.dragging == NO && self.tableView.decelerating == NO)
{
[self startIconDownload:appRecord forIndexPath:indexPath];
}
// if a download is deferred or in progress, return a placeholder image
cell.imageView.image = [UIImage imageNamed:#"Placeholder.png"];
}
else
{
cell.imageView.image = appRecord.appIcon;
}
}
return cell;
}
So – my implementation of Apple's LazyTableImages system has one crucial flaw: it starts all downloads for all images immediately. Now, if I remove this line:
//[self startIconDownload:appRecord forIndexPath:indexPath];
Then the system behaves exactly like you would expect: new images load as their placeholders scroll into view. However, the initial view cells do not automatically load their images without that prompt in the cell renderer. So, I have a problem: with the prompt in the cell renderer, all images load at once. Without the prompt, the initial view doesn't load. Now, this works fine in Apple sample code, which got me wondering what was going on with mine. It's almost like it was building all cells up front rather than just the 8 or so that would appear within the display. So, I got looking into it, and this is indeed the case... my table is building 250 unique cells! I didn't think the UITableView worked like this, I guess I thought it only built as many items as were needed to populate the table. Is this the case, or is it correct that it would build all 250 cells up front?
Also – related question: I've tried to compare my implementation against the Apple LazyTableImages sample, but have discovered that NSLog appears to be disabled within the Apple sample code (which makes direct behavior comparisons extremely difficult). Is that just a simple publish setting somewhere, or has Apple somehow locked down their samples so that you can't log output at runtime?
Thanks!
You certainly should not have an actual UITableViewCell instance for every row in the table. You should only see a few more instances than are visible in the UI. That is where your problem is. It doesn't have anything to do with the loading of images.
The only time I've seen a large number of cells instantiated when the cells where dequeued was when a coder had altered the frame of the tableview to make it much larger than the screen. The tableview retains enough cells from being dequeued to carpet its own frame regardless of what is visible. If the frame is to big then you get a lot of cells in queue.
NSLog does work in Apple examples so if you can't get NSLog output you've got something weird going on with the dev tools themselves.
You might want to shutdown Xcode and the simulator and restart and see if that clears up the odd behavior.
Oh my... mustISignUp is absolutely correct in saying "you definitely have a deeper underlying problem". I have variable-height table rows, and I was doing all height calculation and data storage on the rows themselves rather than on the data model that populated them. As a result, ALL cells were being created and populated by my heightForRowAtIndexPath method which was reading cell height from the cell objects. SO – lesson learned.
Thanks mustISignUp, and I love your username.
NSLog definitely isn't disabled within the Apple sample code. I don't know why you can't see it but you definitely have a deeper underlying problem.
Anyway, for your comparison:- if you have 6 rows on screen -tableView: cellForRowAtIndexPath: is called 6 times with index [0, 0] - [0, 5].
So, is that what you are seeing? How many times is -cellForRowAtIndexPath being called?

iPhone: Cell imageViews repeating when loaded from url

I'm developing a demo RSS reader for iPhone. I obviously have a tableview to display the feeds, and then a detailed view. Some of this feeds have a thumbnail that I want to display on cell.imageview of the table, and some don't.
The problem is that when scrolling the table, loaded thumbnails start repeating on other cells, and I end up with a thumbnail on every cell.
Here's a piece of my code. I may upload screenshots later
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier: #"rssItemCell"];
if(nil == cell){
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"rssItemCell"]autorelease];
}
BlogRss *item = [[[self rssParser]rssItems]objectAtIndex:indexPath.row];
cell.textLabel.text = [item title];
cell.detailTextLabel.text = [item description];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// Thumbnail if exists
if(noticia.imagePath != nil){
NSData* imageData;
#try {
imageData = [[NSData alloc]initWithContentsOfURL:[NSURL URLWithString:item.imagePath]];
}
#catch (NSException * e) {
//Some error while downloading data
}
#finally {
item.image = [[UIImage alloc] initWithData:imageData];
[imageData release];
}
}
if(item.image != nil){
[[cell imageView] setImage:item.image];
}
return cell;
}
Any help will be very appreciated.
Easy one.
Cells are reused. Just ensure you clear the cell.imageView.image each time you fill the cell. You may just need to remove the if(item.image!=nil) line.
For a production application you probably also want to fetch the images in the background and implement a simple cache. There are plenty of examples of how to do that knocking around.
EDIT
RickiG makes a lot of good points : cellForRowAtIndexPath should be displaying the data from the model and as little as possible else!
The concept of just supplying a view-ready model is kind of good (I do it with ASP.NET MVC), but needs to be balanced against the JustInTime memory minimization techniques of iPhone and lets face it, you are not committing the real sin of trying to read back data from the controls on your tableview - that really doesn't work!
The worst thing you are doing is reading web data on cellForRowAtIndexPath as that will block the UK. Instead you should display a blank or placeholder image and trigger a background fetch that will update the model with the data, and then trigger a reload - preferably of the specific cell.
The UITableView is made to reflect a "model", so never try to
change data/views on the cell is self after you build it or reference the cell based on an index number or the like, make an array of your data, I usually build a separate CellViewEntity object that holds all the data I need on the cell, like title, detailtext etc. but also behavior stuff like, is it expanded, has it got special view visible etc.
I then build a UIView CellView that I populate with the graphics and methods that I need, e.g. - (void) shouldDisplayCheckmark:(BOOL) value and so on.
I set the tag of the CellVIew object - [cellView setTag:15] and release the CellView. Now I can reference it later without retaining it and let the UITableVIew decide what should be released/retained.
cellForRowAtIndexPath is called constantly by the SDK, when scrolling when updating, when a cell enter or leave the screen. So don't put heavy instantiations, web call etc. into this function.
In this if block.
if(nil == cell){
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"rssItemCell"]autorelease];
}
I instantiate a cell and a CellView and add the CellView object to the cells view.
(with the tag)
outside the if(cell == nil) I only update the CellVIew with the data from the model array.
This means that if the cell exists it will be reused and have its properties updated from the array, if not it is created and attached to the cell.
Outside the if statement I reference my CellView like this:
[(CellView*)[cell viewWithTag:15] updateValuesAccordingToModelArray:[array objectAtIndes:indexPath.row]]; //this will be the only code executed for a already existing cell.
I guess it might seem a bit over the limit, but you can not reference cells like this [uitableView cellAtRow:14] in a consistent way, because the UITableView caches cell that are off screen and changes the indexPath accordingly. This approach you can do with your array.
Will stop rambling now :) ... separate all data in a nice tableView friendly package and feed it to the table view - one way only.