issue with UIButton image in UITableCellView - iphone

I am making a simple timer app. I have a uitable with multiple rows. In that table I have put a UIButton. Everything works and so far so good i.e. I see the button appear in each row. What I needed to do next was to set an image for the UIButton e.g. if the timer was running I wanted to show stop_button_image in the UIButton and if the timer had started I wanted to set the UIImage on the button to start_button_image
The problem is that if the table gets refreshed then I get a nasty traceback depending upon where I had put the UIButton setimage code. Here is my code for reference.
//WORKING CODE: (NOT THAT I WANT)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//TESTING - Button
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
// Try to retrieve from the table view a now-unused cell with the given identifier.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// If no cell is available, create a new one using the given identifier.
if (cell == nil)
{
// Use the default cell style.
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
//TESTING - Button
[button addTarget:self
action:#selector(timerStopStart:)
forControlEvents:UIControlEventTouchDown];
[button setTitle:#"Start/Stop" forState:UIControlStateNormal];
button.frame = CGRectMake(5.0f, 5.0f, 72.0f, 77.0f);
button.tag = indexPath.row;
[cell addSubview:button];
//THIS WORKS IF I PUT THIS HERE
[button setImage:[UIImage imageNamed:#"button_pause.png"] forState:UIControlStateNormal];
}
else
{
//NSLog(#"went here 2");
button = (UIButton *) [cell viewWithTag:indexPath.row];
}
//some other logic
}
//CODE BREAKS HERE
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//TESTING - Button
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
// Try to retrieve from the table view a now-unused cell with the given identifier.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// If no cell is available, create a new one using the given identifier.
if (cell == nil)
{
// Use the default cell style.
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
//TESTING - Button
[button addTarget:self
action:#selector(timerStopStart:)
forControlEvents:UIControlEventTouchDown];
[button setTitle:#"Start/Stop" forState:UIControlStateNormal];
button.frame = CGRectMake(5.0f, 5.0f, 72.0f, 77.0f);
button.tag = indexPath.row;
[cell addSubview:button];
}
else
{
//NSLog(#"went here 2");
button = (UIButton *) [cell viewWithTag:indexPath.row];
}
//CODE BREAKS HERE
[button setImage:[UIImage imageNamed:#"button_pause.png"] forState:UIControlStateNormal];
//some other logic
}
The problem is that when the table gets refreshed it calls the section with code button = (UIButton *) [cell viewWithTag:indexPath.row]; which I know is just a reference.
Here is the error I get in iOS simulator. I really want to put that image code outside the cell==null check. Any clues how this can be done?
Here is the error that I get in the simulator
2012-06-14 12:39:27.246 Timers[5381:10a03] -[UITableViewCell setImage:forState:]: unrecognized selector sent to instance 0x73706e0
2012-06-14 12:39:27.247 Timers[5381:10a03] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UITableViewCell setImage:forState:]: unrecognized selector sent to instance 0x73706e0'
*** First throw call stack:
(0x150b052 0x16bbd0a 0x150cced 0x1471f00 0x1471ce2 0x136c6 0x7b5e0f 0x7b6589 0x7a1dfd 0x7b0851 0x75b322

The problem is probably when the indexPath.row is zero. In that case, you call viewWithTag:0 but as the tag of any UIView is 0 by default, there are chances that a lot of your cell's subview have a tag with the value 0, not only your UIButton.
To avoid this issue, try adding a constant (like 100) to your indexPath.row before affecting it to the tag, so that your button at row 0 will have the tag 100 and not 0 (and button at row 1 will have the tag 101, and so on…), avoiding it to be confused with any other subview with tag 0.

Yeah, this will fail for row 0.
button = (UIButton *) [cell viewWithTag:indexPath.row];
will return cell when indexPath.row is 0, because cell.tag is 0.
As a general note, this approach will not work. The first time you reuse a cell the button will have the wrong tag. For example, if you reuse the cell from row 2 in row 18, then the tag on the button will be 2, but the tag you look for is 18. It will return nil.
why not define a constant #define kMyStopStartButtonTag 12345 and use that constant the whole time.
if (cell == nil)
{
…
button.tag = kMyStopStartButtonTag;
[cell addSubview:button];
…
} else {
button = (UIButton *) [cell viewWithTag:kMyStopStartButtonTag];
}

Related

uitabelview custom button tag issue

I am creating a custom button in my uitable and giving the button a tag number as shown in code below.
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//NSLog(#"Inside cellForRowAtIndexPath");
static NSString *CellIdentifier = #"Cell";
// Try to retrieve from the table view a now-unused cell with the given identifier.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// If no cell is available, create a new one using the given identifier.
if (cell == nil)
{
// Use the default cell style.
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self
action:#selector(selectPicOnBtnTouch:)
forControlEvents:UIControlEventTouchDown];
[button setTitle:#"btn1" forState:UIControlStateNormal];
button.frame = CGRectMake(6.0f, 0.0f, 97.0f, 110.0f);
button.tag = (indexPath.row)+100;
[cell addSubview:button];
[button setImage:[UIImage imageNamed:#"sample_pic.png"] forState:UIControlStateNormal];
}
else
{
//NSLog(#"went here 2");
button = (UIButton *) [cell viewWithTag:((indexPath.row)+100)];
}
//Get an arrow next to the name
cell.accessoryType = UITableViewCellAccessoryNone;
return cell;
}
- (IBAction) selectPicOnBtnTouch:(id)sender
{
button = (UIButton *)sender;
NSLog(#"[button tag]: %d ...", [button tag]);
}
If I have a table with 10 rows and when I touch buttons in rows 1-5 I get the following in my log
[button tag]: 100 ...
[button tag]: 101 ...
[button tag]: 102 ...
[button tag]: 103 ...
[button tag]: 104 ...
This is fine. The problem is that when I touch row 6-10 I am expecting 105 - 109 tags but what I get back is same tag numbers starting over
[button tag]: 100 ...
[button tag]: 101 ...
[button tag]: 102 ...
[button tag]: 103 ...
[button tag]: 104 ...
The reason I want to keep the tag numbers unique (and not repeating) is so that I know which button corresponds to what row. How can I do that?
Before You follow my Answer i wnat to tell you that following code is bad for memory management because it will create new cell for each rows of UITableView, so be careful for it.
But it is better to use, When UITableView Have Limited rows (about 50-100 may be ) then following code is helpful in your case, use it if is it suitable for you.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = [NSString stringWithFormat:#"S%1dR%1d",indexPath.section,indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
/// Put your code here
}
return cell;
}
Don't set the button tag inside the if block where you create the cell, because cells will be reused and you are not updating the tag in the scenario when the cell is reused.
EDIT :
For the first 5 cells, if block is being executed and new cells are
being created. 6th cell onwards the cells are being reused and hence
count is not incremened.
Call your
button.tag = (indexPath.row)+100;
below your if (cell == nil) block, like this:
if (cell == nil)
{
//...
}
else
{
///...
button = (UIButton*)[[cell subviews] lastObject]; //this assumes your button is the last view in the subview array (double check)
}
button.tag = (indexPath.row)+100;
You need to do smth like that:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
self.rNewsCell = [self.tabelView dequeueReusableCellWithIdentifier:allNewsCellIdentifier forIndexPath:indexPath];
if (self.news.count != 0)
{
if(indexPath.row <[self.news count])
{
RNewsModel *news = (self.news)[indexPath.row];
self.rNewsCell.newsImage.contentMode = UIViewContentModeScaleAspectFill;
self.rNewsCell.newsName.text = news.newsNameModel;
self.rNewsCell.newsData.text = news.newsDataModel;
self.rNewsCell.newsWatches.text = news.newsWatchesModel;
self.rNewsCell.newsAddress.text = news.newsAddressModel;
self.rNewsCell.newsTime.text = news.newsTimeModel;
self.rNewsCell.playFrame.image = [UIImage imageNamed: news.newsPlayImage];
self.rNewsCell.playButton.tag = indexPath.row;
self.rNewsCell.showOnMap.tag = indexPath.row;
self.rNewsCell.rightUtilityButtons = [self rightButtons];
self.rNewsCell.delegate = self;
}
}
return self.rNewsCell;
}
and after that catch the button actions:
- (IBAction)showNewsOnTheMap:(id)sender
{
UIButton *button=(UIButton *) sender;
self.showNewsOnMap = [NSIndexPath indexPathForRow:button.tag inSection:0];
RNewsModel *news = (self.news)[self.showNewsOnMap.row];
NSLog(#"didSelect NEWSOnMap = %#",news.newsIDModel);
[self performSegueWithIdentifier:SegueShowNewsOnTheMap sender:self];
}

how to delete row in uitableview when click on button

hi i am implementing a table in which i am getting request i have list of students to which i want to add as my room mate i have two button accept and reject the problem is here when i click on accept button i want that row will be deleted from the array and also i want to shift the deleted row value into the table of nextview controller in case of accept request . and in case of reject button cliked only want to delete the row not to move the value of cell into next view controller i am implementing this project by using storyboard can anybody have idea about that plase share me this is the code for cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"DefaultCell"];
if(cell == nil) {
cell =[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:#"DefaultCell"];
}
acceptreq = [UIButton buttonWithType:UIButtonTypeRoundedRect];
acceptreq.frame =CGRectMake(170, 10, 60, 30);
rejectreq = [UIButton buttonWithType:UIButtonTypeRoundedRect];
rejectreq.frame =CGRectMake(240, 10, 60, 30);
[acceptreq setTitle:#"Accept" forState:UIControlStateNormal];
[rejectreq setTitle:#"Reject" forState:UIControlStateNormal];
[acceptreq addTarget:self action:#selector(Acceptrequest:) forControlEvents:UIControlEventTouchUpInside];
[rejectreq addTarget:self action:#selector(Rejectrequest:) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:acceptreq];
[cell addSubview:rejectreq];
[acceptreq setTag:indexPath.row];
cell.textLabel.text = [myarray objectAtIndex:indexPath.row];
return cell;
}
In short you can delete selected Row with clicking UIbutton like bellow:-
-(IBAction)Acceptrequest:(id)sender
{
UIButton *btn =(UIButton*)sender;
[myarray removeObjectsAtIndexes:btn.tag];
[tableView deleteRowsAtIndexPaths:myarray withRowAnimation:UITableViewRowAnimationAutomatic];
[tableVIew reloadData];
}
You need to implement this method:
[tableView deleteRowsAtIndexPaths:arrayOfIndexPathsToDelete withRowAnimation:UITableViewRowAnimationAutomatic];

UITableView reload data issues

I have had couple of encounters of this and what I am trying to do is basically calling tableView reloadData however not all the values in the cell is getting updated.The top 5 rows is always not updated... I'd have to scroll to the bottom and then up again to get it updated. Why is this happening?
Here's my code:
- (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];
[cell.textLabel setFont:[UIFont fontWithName:#"Arial" size:16]];
}
//add a button to set to accessory view
UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
//I missed setting the frame yesterday!
//[button setFrame:CGRectMake(200, 20, 20, 20)];
[button setImage:[UIImage imageNamed:#"addsource.png"] forState:UIControlStateNormal];
[button addTarget:self action:#selector(changeButton:) forControlEvents:UIControlEventTouchUpInside];
[button setBackgroundColor:[UIColor clearColor]];
cell.accessoryView = button;
if ([[self.content objectAtIndex:indexPath.row] isKindOfClass:[Source class]]){
Source * source = [self.content objectAtIndex:indexPath.row];
if (![source.type isEqualToString:#"featured"]){
[cell.textLabel setText:source.domain];
NSURL* URL = [NSURL URLWithString:source.imageUrl];
[cell.imageView setImageWithURL:URL
placeholderImage:[UIImage imageNamed:#"placeholder.jpg"]];
}
}
return cell;
}
and this is called everytime I refresh the whole data set:
[self.content removeAllObjects];
[self.content addObjectsFromArray:objects];
[self.tableView reloadData];
try debugging the following line:
if (![source.type isEqualToString:#"featured"]){
obviously your cell won't get updated in the case where your source type is "featured". double check all values in your content array
I've not dug into your code, but this sounds like a symptom you might see when cells are being reused without being fully reset from their previous appearance.
When a cell gets dequeued, it doesn't get reset, it just gets removed from the table view. So unless you do reset it when it next happens to be allocated, it carry previous config and settings.
So, when you configure a cell, make sure you restore all aspects of the cell in all cases, not just those settings for a particular variant of the cell you happen to be returning this time.
Hope that helps.

UITableView with multiple items in each cell

I have a tableview in which I am trying to place a button with an image and a label. I want to change the image of the button once clicked.
Here's the code:
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
checkedImg = [UIImage imageNamed:#"buttonUnChecked1.png"];
UITableViewCell *cell = [_tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
//Set up the cell...
NSString *cellValue = [suggestions objectAtIndex:indexPath.row];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
check = [UIButton buttonWithType:UIButtonTypeCustom];
check.frame=CGRectMake(0,35,20,20);
[check setImage:checkedImg forState:UIControlStateNormal];
[check addTarget:self action:#selector(checkClicked:) forControlEvents: UIControlEventTouchUpInside];
[cell.contentView addSubview:check];
cellContent = [[UILabel alloc]initWithFrame:CGRectMake(40,32,500,25)];
cellContent.text = cellValue;
[cell.contentView addSubview:cellContent];
return cell;
}
-(void)checkClicked:(UIButton *)b
{
checkedImg = [UIImage imageNamed:#"buttonChecked1.png"];
[check setImage:checkedImg forState:UIControlStateNormal];
}
By doing this, the image of the buttons are getting changed but only the last one and not the one clicked. I know the reason behind it, but I don't know how to achieve what I want.
A structured way to get the result you're looking for:
Make a UIView subclass that for your table cells (containing a button and label). You instantiate these custom views and set them as your contentView for each table cell in cellForRowAtIndexPath.
Each of your custom views listens for its own button being pressed. When it was pressed, it toggles its state and tells the main viewcontroller (via a delegate method) that it was toggled. The main view controller calls reloadData on the cell in question to cause it to be reloaded with the correct appearance.
Note that this approach requires you to tell each of the custom views which index path it is rendering for in the table -- that way it can inform the main view controller's delegate method -- this info is needed for triggering a reload of the appropriate cell.
Btw, I presume you want to look at the state of the buttons in your table when the user is done with editing, and your current approach doesn't capture the state stuff very explicitly -- you'd have to iterate over your buttons, or add selected items to a mutable array, or something.
The easy answer to your problem is to change your checkClicked: method to this:
-(void)checkClicked:(UIButton *)b
{
[b setImage:[UIImage imageNamed:#"buttonChecked1.png"] forState:UIControlStateNormal];
}
But you should also adjust your tableView:cellForRowAtIndexPath: method to avoid creating the button repeatedly and to clean up some memory issues like this:
- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [_tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
UIButton *checkBtn = [UIButton buttonWithType:UIButtonTypeCustom];
checkBtn.frame = CGRectMake(0,35,20,20);
[checkBtn setImage:[UIImage imageNamed:#"buttonUnChecked1.png"]; forState:UIControlStateNormal];
[checkBtn addTarget:self action:#selector(checkClicked:) forControlEvents: UIControlEventTouchUpInside];
[cell.contentView addSubview:checkBtn];
UILabel *cellContentLbl = [[UILabel alloc]initWithFrame:CGRectMake(40,32,500,25)];
cellContentLbl.tag = 1;
[cell.contentView addSubview:cellContentLbl];
[cellContentLbl release];
}
//Set up the cell...
NSString *cellValue = [suggestions objectAtIndex:indexPath.row];
cellContent = (UILabel *)[cell viewWithTag:1];
cellContent.text = cellValue;
return cell;
}

Cant display button on specific rows of UITableView properly for iPhone

I am trying to have a button for selected rows of my table.
Here is the example code I am using:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *ControlRowIdentifier = #"ControlRowIdentifier";
UITableViewCell *cell = [tableView
dequeueReusableCellWithIdentifier:ControlRowIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:ControlRowIdentifier] autorelease];
}
if ([indexPath row] > 5) {
UIImage *buttonUpImage = [UIImage imageNamed:#"button_up.png"];
UIImage *buttonDownImage = [UIImage imageNamed:#"button_down.png"];
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0.0, 0.0, buttonUpImage.size.width, buttonUpImage.size.height);
[button setBackgroundImage:buttonUpImage forState:UIControlStateNormal];
[button setBackgroundImage:buttonDownImage forState:UIControlStateHighlighted];
[button setTitle:#"Tap" forState:UIControlStateNormal];
[button addTarget:self action:#selector(buttonTapped:) forControlEvents:UIControlEventTouchUpInside];
cell.accessoryView = button;
}
NSUInteger row = [indexPath row];
NSString *rowTitle = [list objectAtIndex:row];
cell.textLabel.text = rowTitle;
return cell;
}
This code works absolutely fine when loaded for 1st time. So, as per the logic it shows 'Tap' button for all rows greater than 5.
Problem occurs when I scroll up and down. Once I do that it just starts putting that button at any random row. I dont understand why it does that and it'll be really helpful if someone can give some tips on this.
Thanks.
The problem is reusing cell's identifier. In your case, the cell with an index of less than 6 must be with one identifier, and the rest from other.
Table view cells is reusable objects and you must do some clean work with it. Try use next:
if ([indexPath row] > 5) {
...
} else {
cell.accessoryView = nil;
}