I have an app with some posts placed at an UITableView. Each post have a favorite button and I need to change its image when the user clicks on it. Here are the code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *identifier = #"identifier";
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
if(cell == nil){
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"myNib" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
NSDictionary *post = [posts objectAtIndex:indexPath.row];
[cell.likeButton addTarget:self
action:#selector(clickedOnLike:)
forControlEvents:UIControlEventTouchUpInside];
cell.likeButton.tag = indexPath.row;
And on click handler:
-(IBAction)clickedOnLike:(id)sender
{
int tag = buttonSender.tag;
NSDictionary *post = [posts objectAtIndex:tag];
if( ![self likedAlready:post] ){
//set liked on this view...
//update view
NSLog(#"button: %#",buttonSender);
[sender setImage:[UIImage imageNamed:#"newImage.png"] forState:UIControlStateNormal];
//send like to server...
}
}
At this point, everything is going alright. The problem is, after click a button, update the view and scroll to other cells, the other buttons views I never clicked are updated too. For example, when I click a button at indexPath 1, the ones at 5 and 9 change their images automatically. This is a mistery to me, since I call the action sender directly and update only it. Thanks for help.
The reusable cell do it: dequeueReusableCellWithIdentifier.
U need to deal manually with things like this.
U can invoke manually to your clickedOnLike: method from the CellForRowAtIndexPath:
Try to maintain inside your modal (from your MVC development architecture), and access there from the CellForRowAtIndexPath:
That will solve your problem :)
Good Luck!
In your cellForRowAtIndexPath, set the default image of your button, then change it if its a favorite. The rows tend to get cached so always set your data to what you expect it to be.
As others have said, the problem is because the view is recycled for everyline, and in the recycled copy the button may have had its picture changed to the new one.
you need to keep somewhere in memory the status of the post, and on the cellForRowAtIndexpath set the correct image for the button every time the row is rendered.
Alternatively, if you have a very small number of posts, just eliminate recycling by doing
static NSString *identifier = nil;
Related
I am doing using some code that I have seen work before. Essentially a user answers yes or no on a post with some buttons. Pressing yes or no updates the database, which is working correctly, and it also updates the visible UI, which is not working. This UI updates the buttons so they one is selected, other is highlighted and both are disabled for user interaction. Also it makes changes to two UILabels. The method that these buttons calls needs to update the database and retrieve the buttons from the tableViewCell and update the changes I have the methods working in another ViewController so I can not understand the difference here. Here is my cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *simpleTableIdentifier = [NSString stringWithFormat:#"%ld,%ld",(long)indexPath.section,(long)indexPath.row];
NSLog(#" simple: %#",simpleTableIdentifier);
if (indexPath.row==0) {
ProfileFirstCell *cell = [self.tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
cell = [[ProfileFirstCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:simpleTableIdentifier];
}
cell = [self createProfileCell:cell];
return cell;
}else{
YesNoCell *cell =[self.tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell==nil) {
cell=[[YesNoCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
cell = [self createYesNoCell:cell:indexPath];
return cell;
}
}
Essentially what this does is create the users profile in the first cell, and load all the questions that user asks. This is the major difference I see between the old tableView and this tableView. In createYesNoCell I create the UIElements and create tags as follows
cell.yesVoteButton.tag=indexPath.row+ yesVoteButtonTag1;
cell.noVoteButton.tag=indexPath.row+ noVoteButtonTag1;
cell.yesCountLabel.tag=indexPath.row+ yesCountLabelTag1;
cell.noCountLabel.tag=indexPath.row+ noCountLabelTag1;
The buttons have the selector that initiates a number of things. It finds which button was pressed by the following.
NSInteger index;
if(sender.tag>=yesVoteButtonTag1){
NSLog(#"Yes button pressed");
votedYes=true;
index=sender.tag-yesVoteButtonTag1;
}else{
NSLog(#"No button Pressed");
votedYes=false;
index=sender.tag-noVoteButtonTag1;
}
UILabel *yesLabel = (UILabel*) [self.tableView viewWithTag:index+yesCountLabelTag1]; // you get your label reference here
UIButton *yesButton=(UIButton *)[self.tableView viewWithTag:index+1+yesVoteButtonTag1];
NSLog(#"Tag IN METHOD: %ld",index+yesVoteButtonTag1);
UILabel *noLabel = (UILabel*) [self.tableView viewWithTag:index+1+noCountLabelTag1]; // you get your label reference here
UIButton *noButton=(UIButton *)[self.tableView viewWithTag:index+noVoteButtonTag1];
These viewWithTag calls are nil when I look at them. The only difference that I can see from my earlier implementation is that the old one had sections and one row, while this one is all rows and one section. So replacing the indexPath.section with indexPath.row should account for that. Also I checked that the tag made in cellForRowAtIndexPath is the same as the row recovered in the yes/no vote method, because it is displaced by one because of the profile cell being created at indexPath.row==0. I tried passing the cell to the yes/no vote method and tried to recover the buttons and labels with contentView as some suggestions made on similar posts. However this didn't seem to solve my problem. Really would appreciate some insight on this.
have you call the '[tableView reload]' method to update the UITableView, it may helps.
Firstly, the table reuse identifier should be used for types of cells, not one for each cell. You have two types, so you should use two fixed reuse identifiers.
ProfileFirstCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"ProfileCell"];
if (cell == nil) {
cell = [[ProfileFirstCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"ProfileCell"];
}
and
YesNoCell *cell =[self.tableView dequeueReusableCellWithIdentifier:#"YesNoCell"];
if (cell==nil) {
cell=[[YesNoCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"YesNoCell"];
}
Secondly, rather than trying to get a reference to a cell after creating the table, which isn't working for you, you should initialize the cells completely when they are created. (TableView won't create cells unless they're visible, so you shouldn't rely on their existing at all.)
createProfileCell should really be called initializeProfileCell, because you're not creating the cell in it - you already did that in the line above, or recovered an old one.
Then your call to initializeProfileCell can take a flag specifying whether it is a Yes or No cell and set its properties accordingly.
cell = [self initializeProfileCell:cell isYes:(indexPath.section==0)];
Similarly with createYesNoCell --> initializeYesNoCell.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"YOURCELL_IDENTIFIER";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
UILabel *title = (UILabel*) [cell viewWithTag:5];
UILabel *vensu =(UILabel*) [cell viewWithTag:7];
vensu.text = #"YOUR TEXT";
title.text = #"YOUR TEXT";
return cell;
}
Before I post the question itself, I need to state this is a jailbreak app. This is why I'm writing in "bizarre" folders in the filesystem.
Let's continue.
Here is my cellForRowAtIndexPath method:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"pluginCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
}
if(indexPath.row == 0)
{
cell.textLabel.text = #"default";
}else
{
//Get the plugin's display name.
NSBundle *currentPlugin = [[NSBundle alloc] initWithPath:[NSString stringWithFormat:#"/Library/Cydeswitch/plugins/%#", [plugins objectAtIndex:indexPath.row - 1], nil]];
cell.textLabel.text = [[currentPlugin localizedInfoDictionary] objectForKey:#"CFBundleDisplayName"];
if(cell.textLabel.text == nil)
{
//No localized bundle, so let's get the global display name...
cell.textLabel.text = [[currentPlugin infoDictionary] objectForKey:#"CFBundleDisplayName"];
}
[currentPlugin release];
}
if([[[cell textLabel] text] isEqualToString:[settings objectForKey:#"pluginToExecute"]])
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
currentCell = [cell retain];
}
return cell;
}
Like you can see, this method uses a member called currentCell to point to the cell that is currently "selected". This is an options table and the user should be able to have only one cell with the Checkmark accessory icon at any time.
When the use selects another cell, he is changing an option and the Checkmark is supposed to disappear from the current cell and appear in the newly appeared cell. I do that like this:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
currentCell.accessoryType = UITableViewCellAccessoryNone;
[currentCell release];
currentCell = [[self tableView:tableView cellForRowAtIndexPath:indexPath] retain];
NSLog(#"CURRENT CELL %#", currentCell.textLabel.text);
currentCell.accessoryType = UITableViewCellAccessoryCheckmark;
}
But it doesn't work. The moment I tap another cell, the Checkmark correctly disappears from the old cell, but it never shows up in the new cell.
I know the selection work fine because that NSLog there prints the new cell's text just fine.
I have tried keeping track of the indexPath before, but it didn't work at all. When I tried using indexPaths instead of pointers to cells, when the user tapped the cell nothing happened at all (at least with my current approach the checkmark disappears from the old cell).
I think it has something to do with cellForRowAtIndexPath because if I keep pointing at the cells the checkmark disappears, but for some reason when trying to change the accessory type from a cell fetched with cellForRowAtIndexPath it doesn't seem to work at all.
Any help will be appreciated.
Typo? Try this:
currentCell = [[self.tableView cellForRowAtIndexPath:indexPath] retain];
You mustn't keep track of the last selected cell the way you are. Cell's get reused. Use an ivar to keep track of the indexPath or some other key appropriate to your data.
Then in the didSelect... method you get a reference to the old cell using the saved indexPath or key. In the cellForRow... method you need to set the proper accessoryType based on whether the current indexPath matches your saved indexPath.
Lastly, do not call your own delegate/data source method. When getting a reference to a cell, ask the table view for it directly.
BTW - you are over-retaining currentCell in your cellForRow... method. There is no need to retain it all in that method unless it is the first time you are making the assignment.
Let me get disclosure out of the way first: I'm just beginning iOS programming, furthermore I am a student and this is homework.
The project I am currently doing requires the creation of a Table View with custom cells in it. I've achieved that. However, it also requires putting the Table View into edit mode for deleting stuff (i.e. making the red icon thingies appear). That is something I haven't achieved. Here is the code directly used to change the editing mode. (view is the tv, it's wired to a UIButton)
-(IBAction)toggleEdit:(id)sender
{
if(isEditing)
{
NSLog(#"true");
isEditing = false;
[view setEditing:false];
}
else
{
NSLog(#"false");
isEditing = true;
[view setEditing:true];
}
}
Cell Allocation code:
-(UITableViewCell*)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* cellID = #"Cell";
ShipCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (!cell)
{
NSArray* views = [[NSBundle mainBundle] loadNibNamed:#"ShipCellView" owner:self options:nil];
cell = [views objectAtIndex:0];
}
cell.nameLabel.text = [dataController getNameAtIndex:indexPath.row];
cell.operatorLabel.text = [dataController getOperatorAtIndex:indexPath.row];
cell.flagImage.image = [dataController getFlagAtIndex:indexPath.row];
return cell;
}
Relevant UIBuilder screenshots:
I've been through my school-appointed iOS5 textbook (building for iOS6), as well as a lot of the school videos, which say my current code will work. (which it does in terms of displaying custom cells, but not for showing the editing icons.)
Full Source (of relevant files) for sake of completion:
http://pastebin.com/upLYXz4i
The controller for the cell nib is boilerplate.
The problem came from me using a pseudo-reserved keyword for one of my outlets; changing it resolved the problem.
To be specific, I named an outlet view, which isn't a great thing to do.
I am using the same code in two of my view controllers (they are implementing the same class what changes is the url they download) and in one occassion the image is displayed correclty while in the other I do see an empty cell.
Here is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier=#"MyCell";
//this is the identifier of the custom cell
MyCell *cell = (MyCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
tableView.backgroundColor=[UIColor clearColor];
tableView.opaque=NO;
tableView.backgroundView=nil;
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"MyCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
NSLog(#"Image url is:%#",[images_url objectAtIndex:indexPath.row]);
NSURL *url_image=[NSURL URLWithString:[images_url objectAtIndex:indexPath.row]];
cell.myimage.image=[UIImage imageWithData:[NSData dataWithContentsOfURL:url_image]];
return cell;
}
As i told you I have 2 view controllers implementing the same class. In the view did load the url is set depending on the value of a flag. If I open controller A, I see no image, if I open view B i can see the image. Both of the urls are correct as I can check it with the NSLog I have inserted.
What might be the problem?
Unfortunately calling "NSData dataWithContentsOfURL" is a blocking call. Execution of your program will stop until iOS is able to fetch all the data from the server or fails trying. This may often be "fast" if you're on LTE or WiFi; but can potentially take a LONG time.
Meanwhile, you're on the "main thread" in your app - so your app will appear to freeze-up, and the system's watchdog timer may kill your app. If anyone besides you will use this ap, you absolutely need to populate your tableview cell's image with local data that's retrieved immediately or use asynchronous methods.
Just google for "lazy load UIImage". This SO question has some good tips on the subject:
lazy-load-images-in-uitableview
Additionally, you should move these lines to some setup code. You don't need to perform them every time to update a cell:
tableView.backgroundColor=[UIColor clearColor];
tableView.opaque=NO;
tableView.backgroundView=nil;
Best of luck!
I am making a bookmarks page for my web browser and the problem is that everytime I add a new object, I have to set the properties since I created a custom cell (I must set the text of two labels) and so I need a way to only edit the newly added object...I'm familiar with indexes but not able to come up with any solutions to this problem...For example when I bookmark the first page its fine, but once I bookmark 2 pages the 2 cells are the exact same...Any Ideas?
Heres My Code:
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"CustomCell";
CustomCell *cell = (CustomCell*)
[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCell" owner:nil options:nil];
cell = (CustomCell *) [nib objectAtIndex:0];
}
// Set up the cell...
NSString *theTitle=[webView stringByEvaluatingJavaScriptFromString:#"document.title"];
NSString *currentURL = webView.request.URL.absoluteString;
cell.websiteTitle.text = theTitle;
cell.websiteURL.text = currentURL;
universalURL = currentURL;
return cell;
}
When setting up the cell I need to point to the newest cell!
Thank You In Advance!
Your approach cannot work. You cannot use the cells as the store for the titles and URLs, because cells are reused (and because it is bad design). A table view allocates only cells for the visible rows and reuses a cell for a different row when you scroll the table view.
Instead you should store the titles and URLs in a separated data source, for example an NSMutableArray *bookmarks where each item in the array is a NSDictionary with "title" and "URL" keys.
To add a bookmark to your table, you just append a new entry to the array and call reloadData on the table view.
The tableView:cellForRowAtIndexPath: method can then use the bookmarks array with the row number indexPath.row to fill all elements of the cell.