How select one item at a time ios sdk - ios

I have a list of Option values displayed in a UITableView.
Now I want user to select one item once. But currently the user can select all the option.
What I want :
Suppose I Have 5 radio boxes : 1 2 3 4 5 at a time user can only select one . If he choses another one then Previous one must get deselected.
What is Happening Now:
Currently all of the boxes are get selected.
I am using this code in my didSelectRowAtIndex method:
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UIButton *btnRadhio = (UIButton *)[cell viewWithTag:1];
for(int i =0;i<[arrDistanceList count];i++)
{
[btnRadhio setBackgroundImage:[UIImage imageNamed:#"radio_unchecked"] forState:UIControlStateNormal];
}
[btnRadhio setBackgroundImage:[UIImage imageNamed:#"radio_checked"] forState:UIControlStateNormal];

You are changing the UIButton for same index path:
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; //From Your Code
You have to get new indexPath each time. Try this one
for(int i =0;i<[arrDistanceList count];i++)
{
NSIndexPath *indexPathI=[NSIndexPath indexPathForRow:i inSection:0]; //i supposed you have 0 section
UITableViewCell *cellI=[tableView cellForRowAtIndexPath:indexPathI];
UIButton *btnRadhio = (UIButton *)[cellI viewWithTag:1];
if(i==indexPath.row)
{
[btnRadhio setBackgroundImage:[UIImage imageNamed:#"radio_checked"] forState:UIControlStateNormal];
}
else
{
[btnRadhio setBackgroundImage:[UIImage imageNamed:#"radio_unchecked"] forState:UIControlStateNormal];
}
}
You can check here also

You need to have one int class variable and store selected indexpath.row into it and reload tableview and in cellForRowAtIndexPath check this variable and check row that you have selected.

I will assume arrDistanceList is an array of objects, each of which represents a row of data in your table.
It is good practice that your UI is driven by your model. So let's say each object in your array has information such as 'title', 'background image' etc, consider a simple boolean flag, e.g. 'selected', which cellForRow consults.
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UIButton *btnRadhio = (UIButton *)[cell viewWithTag:1];
id object = [arrDistanceList objectAtIndex:indexPath.row];
if (object /* .selected */) {
[btnRadhio setBackgroundImage:[UIImage imageNamed:#"radio_checked"] forState:UIControlStateNormal];
} else {
[btnRadhio setBackgroundImage:[UIImage imageNamed:#"radio_unchecked"] forState:UIControlStateNormal];
}
When making a selection in didSelect, flip the boolean for the selected object and turn off all other selections by searching with NSPredicate.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"selected == YES"];
NSArray *filteredArray = [arrDistanceList filteredArrayUsingPredicate:predicate];
All you will need to do now is update any visible cells on screen. You can do this using tableView.visibleCells array.
It is fine updating the visible cells with a for loop on the main thread, because the number of visible cells at any one time will be relatively small. However, the arrDistanceList array may have a lot of objets within it, so you may eventually considering updating this array on a background thread one day.

CellforRowAtIndexPath:
cell.btnRadhio.tag = (indexpath.row+1)*100;
DisselectRowAtIndexPath:
for(int i =0;i<[arrDistanceList count];i++)
{
UIButton *btnRadhio = (UIButton *)[self.view viewWithTag:(i+1)*100];
if(i==indexpath.row)
{
[btnRadhio setBackgroundImage:[UIImage imageNamed:#"radio_checked"] forState:UIControlStateNormal];
}
else
{
[btnRadhio setBackgroundImage:[UIImage imageNamed:#"radio_unchecked"] forState:UIControlStateNormal];
}
}
Hope this will help you.

Related

Objective C - Problems With TableView Check Box Using Sections

I'm implementing a contact check-box feature in my UITableView by using the following code.
The problem I'm having is that, when checking off a couple of users and then scrolling up/down the table, users who should not be checked off are showing as checked, and vice versa.
I'm assuming it has something to do with an error in my cell creation, but I'm not quite sure what.
- (void)viewDidLoad {
...
self.checkedUsers = [[NSMutableArray alloc] init];
for (int i=0; i<self.allContacts.count; i++) {
[self.checkedUsers insertObject:#"FALSE" atIndex:i];
}
...
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *simpleTableIdentifier = #"SocietyContactCell";
SocietyContactCell *cell = (SocietyContactCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"SocietyContactCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
BOOL checked = [[self.checkedUsers objectAtIndex:indexPath.row] boolValue];
UIImage *image = (checked) ? [UIImage imageNamed:#"checkedContact.png"] : [UIImage imageNamed:#"uncheckedContact.png"];
[cell.checkButton setBackgroundImage:image forState:UIControlStateNormal];
[cell.checkButton addTarget:self action:#selector(buttonPressedAction:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)buttonPressedAction:(id)sender
{
UIButton *button = (UIButton *)sender;
int row = button.tag;
BOOL checked = [[self.checkedUsers objectAtIndex:row] boolValue];
[self.checkedUsers removeObjectAtIndex:row];
[self.checkedUsers insertObject:(checked) ? #"FALSE":#"TRUE" atIndex:row];
UIImage *newImage = (checked) ? [UIImage imageNamed:#"uncheckedContact.png"] : [UIImage imageNamed:#"checkedContact.png"];
[button setBackgroundImage:newImage forState:UIControlStateNormal];
}
EDIT: I now realize the issue is likely coming from my use of sections in this TableView. The contacts are sorted into alphabetized sections, which is messing up my tags.
How would I tackle this problem using sections?
The problem here is retention of the status of checkmark in a cell. Since the table cells are reused on scrolling, the cellForRowAtIndexPath method is called again and the value of the check mark is not set again there.
You will have to make a global array which will store the status of the checkmark for each indexPath. Then you can set the status of the checkmark button checked/unchecked in cellForRowAtIndexPath method.
Add the following code snippet along with the code show:
[cell.checkButton addTarget:self action:#selector(buttonPressedAction:) forControlEvents:UIControlEventTouchUpInside];
[cell.checkButton setTag:indexPath.row];
Also, replace these lines:
[self.checkedUsers removeObjectAtIndex:row];
[self.checkedUsers insertObject:(checked) ? #"FALSE":#"TRUE" atIndex:row];
with this:
[self.checkedUsers replaceObjectAtIndex:row withObject:checked ? #"TRUE" : #"FALSE"];
In your SocietyContactCell Class add the following Method:
- (void)prepareForReuse {
[super prepareForReuse];
[self.checkButton setBackgroundImage:nil forState:UIControlStateNormal];
}
I don't see where you are setting button.tag every time in cellForRowAtIndexPath:, so the action method might be updating the wrong row in your self.checkedUsers array, although the action method independently updates the cell's image correctly at the moment. When you scroll up and down however, the display will go back to the state in the self.checkedUsers array.
You probably need a line like:
cell.checkButton.tag = indexPath.row;
in your cellForRowAtIndexPath: method.
Secondly, your action method is getting the state on self.itsToDoChecked, and then updating a self.checkedUsers array, when it appears it should be acting on the same array (simply reversing the state of the given row index). Not sure if that is just an example code typo or something significant.
Edit: It's also possible there is state in the cell subclass causing issues; hard to be sure. You want to implement -prepareForReuse in that class (and always call super) to reset any state to initial values.

Strange behavior when adding UIButton to UICollectionViewCell

I'm trying to add aUIButton to aUICollectionViewCell. The main purpose of the button is to let users to select their favourites.
When they click on theUIButton, the UIButton will display a glowing star image, showing that the item has been picked as favourite.
Click on the UIButton again, and the button return to display the original image with an empty star, meaning deselection.
The way I realize this is as below:
1 - In the storyboard, put an UIButton into theUICollectionViewCell, ctrl-drag the button to the cell's .h file to setup an action called
-(IBAction)favoriteBtnClick:(id)sender;
2 - In the cell's .m file, edit the action as follows:
-(IBAction)favoriteBtnClick:(id)sender {
if ([self.favoriteChecked isEqualToString:#"NO"]) {
UIImage *image = [UIImage imageNamed:#"FavoriteSelected.PNG"];
[sender setBackgroundImage:image forState:UIControlStateNormal];
self.favoriteChecked = #"YES";
} else if ([self.favoriteChecked isEqualToString:#"YES"]) {
UIImage *image = [UIImage imageNamed:#"FavoriteBlank.PNG"];
[sender setBackgroundImage:image forState:UIControlStateNormal];
}
}
However, when I try to run the UICollectionView, when I clicked the button on one cell, "strange" behaviors occurred:
1 - Click on one cell to turn one button to favorite glowing star, buttons on some other cells below also turn selected, after your scroll down the list.
2 - The switching only functions for one cycle. After you finish the first round of select and deselect, there is no longer reactions upon further clicking.
Can anybody help me understand the reasons for this and probably give me some hints on how to solve it?
Thank you!
Regards,
Paul
Add this code in your button action
CGPoint ButtonPoint = [sender convertPoint:CGPointZero toView:self.collectionView];
NSIndexPath *ButtonIndex = [self.collectionView indexPathForItemAtPoint:ButtonPoint];
buttonindexpath = ButtonIndex.row
[collectionView reloadData];
Now in your cellForItemAtIndexPath, check if indexPath.row == buttonindexpath, if yes give the code you gave in button action....
ie....
if (indexPath.row == buttonindexpath)
{
if ([self.favoriteChecked isEqualToString:#"NO"])
{
UIImage *image = [UIImage imageNamed:#"FavoriteSelected.PNG"];
[sender setBackgroundImage:image forState:UIControlStateNormal];
self.favoriteChecked = #"YES";
}
else if ([self.favoriteChecked isEqualToString:#"YES"])
{
UIImage *image = [UIImage imageNamed:#"FavoriteBlank.PNG"];
[sender setBackgroundImage:image forState:UIControlStateNormal];
}
}
Hope this works fine
This is happening because of cell reuse. since self.favoriteChecked is a property inside the custom collection cell class, and its value is updated only inside that class causes this issue.
Solution to this problem is, if you are using custom object model to populate collection cells from viewcontroller, then on favourite button click update the favourite status on that custom object and in cellForItemAtIndexPath method check the value of favourite status and update the favoriteChecked status of custom collectionviewcell class.
If you are not using custom object, you need to keep a list of favourite items separately either indexpaths of favourite item or unique identifier of fav item. Then in cellForItemAtIndexPath method check the value of favourite status and update the favoriteChecked status of custom collectionviewcell class.
This is no no a strange behaviour. You have load the cells from the model data objects. You have to track & keep the state change of button clicked data in an array & then load the cell data values.
Here is a sample code : which aligns with solution to your problem.
NSMutableArray *favoriteStatusArray = [[NSMutableArray alloc]init];
// assume favoriteStatusArray is initialized with all BOOL as NO.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
SomeCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
cell.favoriteButton.tag = indexPath.row;
[cell.favoriteButton addTarget:self action:#selector(peformFavoriteChange:) forControlEvents:UIControlEventTouchUpInside];
if([[favoriteStatusArray objectAtIndex:indexPath.row] boolValue] == NO)
UIImage *image = [UIImage imageNamed:#"FavoriteSelected.PNG"];
[cell.favoriteButton setBackgroundImage:image forState:UIControlStateNormal];
}else {
UIImage *image = [UIImage imageNamed:#"FavoriteBlank.PNG"];
[cell.favoriteButton setBackgroundImage:image forState:UIControlStateNormal];
}
return cell;
}
-(void)peformFavoriteChange:(UIButton *)sender
{
BOOL oldValue = [[favoriteStatusArray objectAtIndex:sender.tag] boolValue];
[favoriteStatusArray replaceObjectAtIndex:sender.tag withObject:[NSNumber numberWithBool:!oldValue]];
[myCollectionView reloadData];
}
Hope you got the idea of doing this.

Maintain state of custom button in UITableview cell when scrolls

I know this question is very similar to other questions, but i doesn't resolve the problem using that approach.i follow most of the available solution but it doesn't work for me..
I know that when tableview scrolls it reuse cell but i doesn't know the approach of maintaining the button state . i'll try with following link
How to use Reusable Cells in uitableview for IOS
IOS: Maintaining button state in uitableviewcell
How to Handle Favourite button clicks in custom Tableview cells iOS?
I done all the things.use tags, use touch points and all but nothing seems to work for me.so help me out..and here is my sample code
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
cell = (subcategoryCell *)[tableView dequeueReusableCellWithIdentifier:NSStringFromClass([subcategoryCell class]) forIndexPath:indexPath];
/*
* Set button for cell to subscribe with it
*/
cell.btnsubscribe = (UIButton *)[cell.contentView viewWithTag:303];
cell.btnsubscribe.tag = indexPath.row ;
[cell.btnsubscribe addTarget:self action:#selector(clickBtnSubscribe:) forControlEvents:UIControlEventTouchUpInside];
if (![_arraybtnState containsObject:listid] )
{
[cell.btnsubscribe setImage:[UIImage imageNamed:#"follow"] forState:UIControlStateNormal];
[cell.btnsubscribe setSelected:YES];
}
else {
[cell.btnsubscribe setImage:[UIImage imageNamed:#"following"] forState:UIControlStateNormal];
[cell.btnsubscribe setSelected:NO];
}
return cell;
}
_arrbtnstate contains the ids that user follows.
and listid contains unique id comes from database too
And the click event method...
- (IBAction)clickBtnSubscribe:(id)sender {
UIButton *button = (UIButton*)sender;
NSLog(#"selected button tag %li", (long)button.tag);
NSNumber *tagnum = [NSNumber numberWithLong:(long)button.tag];
if (button.selected) {
[self.arraybtnState addObject:tagnum];
[button setImage:[UIImage imageNamed:#"following"] forState:UIControlStateNormal];
NSLog(#"Subscribe");
[self subscribeButton:button.tag];
[button setSelected:NO];
}
else
{
[self.arraybtnState removeObject:tagnum];
[button setImage:[UIImage imageNamed:#"follow"] forState:UIControlStateNormal];
NSLog(#"unsubscribe");
[self unsubscribeButton:button.tag];
[button setSelected:YES];
}
}
Note: In this code, button is created in storyboard.,but i'll also tried without storyboard too.
First of all this line cell.btnsubscribe = (UIButton *)[cell.contentView viewWithTag:303]; is to be changed. Follow the below steps and try
in custom subcategoryCell connect IBOutlet to btnsubscribe.
in storyboard, you can set both selected and not normal images to a UIButton, here it is btnsubscribe. If you find it difficult follow this
in cellForRowAtIndexPath: remove below lines
[cell.btnsubscribe setImage:[UIImage imageNamed:#"follow"] forState:UIControlStateNormal];
[cell.btnsubscribe setImage:[UIImage imageNamed:#"following"] forState:UIControlStateNormal];
cell.btnsubscribe = (UIButton *)[cell.contentView viewWithTag:303];
update the following lines
if (![_arraybtnState containsObject:listid] )
{
[cell.btnsubscribe setSelected:YES];
}
else {
[cell.btnsubscribe setSelected:NO];
}
or simply [cell.btnsubscribe setSelected:(![_arraybtnState containsObject:listid] )];
Keep the addTarget in cell for row method (since you have custom cell class, it is better to move the button action to cell class and pass the result to viewcontroller by callback or delegate. not discussing that now but recomments) and update the clickBtnSubscribe: by removing
[button setImage:[UIImage imageNamed:#"following"] forState:UIControlStateNormal];
[button setImage:[UIImage imageNamed:#"follow"] forState:UIControlStateNormal];
these lines. assumes other parts are working properly.
You are setting Selected and unselected image for button's Normal state in both cases i.e UIControlStateNormal.
When you are using selected method you do not require to setImage again. Just set Image for Normal and selected state for button properly in cellForRowAtIndexpath or in xib. You are done.
First remove the setting image in clickBtnSubscribe:() method.
Now in your storyboard set images for Normal and selected state.
Also remove set Image in cell for row at indexpath as you have already done in view.
You can maintain the state of the buttons as-
// initialize your array only once
_arraybtnState = [[NSMutableArray alloc] init];
// cell for row at index path would be
//initially each cell will have follow image
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
cell = (subcategoryCell *)[tableView dequeueReusableCellWithIdentifier:NSStringFromClass([subcategoryCell class]) forIndexPath:indexPath];
[cell.btnsubscribe setTag: indexPath.row];
[cell.btnsubscribe addTarget:self action:#selector(clickBtnSubscribe:) forControlEvents:UIControlEventTouchUpInside];
if (![_arraybtnState containsObject:[NSNumber numberWithInt:indexPath.row]] )
{
[cell.btnsubscribe setImage:[UIImage imageNamed:#"follow"] forState:UIControlStateNormal];
[cell.btnsubscribe setSelected:YES];
}
else
{
[cell.btnsubscribe setImage:[UIImage imageNamed:#"following"] forState:UIControlStateNormal];
[cell.btnsubscribe setSelected:NO];
}
return cell;
}
- (IBAction)clickBtnSubscribe:(id)sender {
UIButton *button = (UIButton*)sender;
NSLog(#"selected button tag %li", (long)button.tag);
NSNumber *tagnum = [NSNumber numberWithLong:(long)button.tag];
if (button.selected) {
[self.arraybtnState addObject:tagnum];
NSLog(#"Subscribe");
[self subscribeButton:button.tag];
[button setSelected:NO];
}
else
{
[self.arraybtnState removeObject:tagnum];
NSLog(#"unsubscribe");
[self unsubscribeButton:button.tag];
[button setSelected:YES];
}
//after doing modification update the respective row as
UIButton *button = (UIButton *)sender;
// Find Point in Superview
CGPoint pointInSuperview = [button.superview convertPoint:button.center toView:self.tableView];
// Infer Index Path
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:pointInSuperview];
//relaod the row
NSArray* indexPathModel = [NSArray arrayWithObjects:indexPath, nil];
[self.tableView reloadRowsAtIndexPaths: indexPathModel withRowAnimation:UITableViewRowAnimationNone];
}
reloading row will reflect the changes done in the modal to the UI.
I think you can put this line in viewDidLoad or after you load what all have subscribed .
_arraybtnState = [NSMutableArray arrayWithArray:[self.strSubscribe componentsSeparatedByString:#","]];
Then the cellForRowAtIndexPath will loook like below
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
cell = (subcategoryCell *)[tableView dequeueReusableCellWithIdentifier:NSStringFromClass([subcategoryCell class]) forIndexPath:indexPath];
/*
* Set button for cell to subscribe with it
*/
cell.btnsubscribe = (UIButton *)[cell.contentView viewWithTag:303];
cell.btnsubscribe.tag = indexPath.row ;
[cell.btnsubscribe addTarget:self action:#selector(clickBtnSubscribe:) forControlEvents:UIControlEventTouchUpInside];
if (![_arraybtnState containsObject:[NSNumber numberWithInt:indexPath.row]] ) {
[cell.btnsubscribe setImage:[UIImage imageNamed:#"follow"] forState:UIControlStateNormal];
}
else {
[cell.btnsubscribe setImage:[UIImage imageNamed:#"following"] forState:UIControlStateNormal];
}
return cell;
}
and then the btn click method as
- (IBAction)clickBtnSubscribe:(id)sender {
UIButton *button = (UIButton*)sender;
NSLog(#"selected button tag %li", (long)button.tag);
NSNumber *tagnum = [NSNumber numberWithLong:(long)button.tag];
if (![_arraybtnState containsObject:tagnum] )
{
[_arraybtnState addObject:tagnum];
[button setImage:[UIImage imageNamed:#"following"] forState:UIControlStateNormal];
NSLog(#"Subscribe");
[self subscribeButton:button.tag];
}
else {
[self.arraybtnState removeObject:tagnum];
[button setImage:[UIImage imageNamed:#"follow"] forState:UIControlStateNormal];
NSLog(#"unsubscribe");
[self unsubscribeButton:button.tag];
}
}
Two easy solutions and quite fast to implement :
Keep track of the state in an array/dictionary
You can store your states in an array of states, i'll make an example.
(Note: i'm coding this without compiling, I might mispel some stuff, feel free to edit my post)
in your viewDidLoad
arrState = [[NSMutableArray alloc]init];
for (yourObject *object in dataArray){
//For every object that you use to load your tableview, this can be strings or anything really
[arrState addObject:#NO];
}
In your cellForRow
...
//This will either put YES or NO depending on the element in the state array.
[cell.button setSelected:[arrState objectAtIndex:indexPath.row]];
...
Boom, done.
You can also keep track of this in a database or anything persistent, and then you'd simply load whatever your field contains for a selected record. This is more relevant if we're talking about, for example, something like favourites or something that will be there until the user decides otherwise. If it's just to keep track of your button states during the lifetime of your view, then ignore this part of the answer.
If you need to keep it alive forever (even when the app closes), then you'd most probably need a database :)

How to insert objects into an NSMutableArray based on cells selected in UITableView in iOS?

I have a UITableViewController that has a UITableView whose cells have a button attached to them via the accessoryView. These buttons can be selected/deselected when the user clicks on them. The table itself is made up from an NSMutableArray of objects that contain multiple NSString parameters, with the "name" parameter being on display in the table.
What I am trying to do now is when the button is selected, to add the corresponding OBJECT that links to the respective "name" parameter to an NSMutableArray. The user is not selecting the cell, but rather, they are clicking on the button that is attached to the cell.
Here is my method that selects/deselects the button in each cell:
-(void)buttonTouched:(id)sender
{
UIButton *btn = (UIButton *)sender;
if( [[btn imageForState:UIControlStateNormal] isEqual:[UIImage imageNamed:#"CheckBox1.png"]])
{
[btn setImage:[UIImage imageNamed:#"CheckBox2.png"] forState:UIControlStateNormal];
// statement to add object to NSMutableArray
}
else
{
[btn setImage:[UIImage imageNamed:#"CheckBox1.png"] forState:UIControlStateNormal];
// statement to remove object from NSMutableArray
}
}
//Here is my code that shows how I initialize my button and cell in my UITableView
- (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];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
testButton = [UIButton buttonWithType:UIButtonTypeCustom];
[testButton setFrame:CGRectMake(280, 57, 25, 25)];
[testButton setImage:[UIImage imageNamed:#"CheckBox1.png"] forState:UIControlStateSelected];
[testButton setImage:[UIImage imageNamed:#"CheckBox2.png"] forState:UIControlStateNormal];
[testButton setUserInteractionEnabled:YES];
[testButton addTarget:self action:#selector(buttonTouched:) forControlEvents:UIControlEventTouchUpInside];
[testButton setTag:cell.tag];
[cell setAccessoryView:testButton];
//[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
}
// Configure the cell...
TestObject *tObject = [[DataModel sharedInstance].testList objectAtIndex:indexPath.row];
cell.textLabel.text = tObject.testTitle;
return cell;
}
My issue is not so much the actual adding of an object to the array, but deriving the respective cell from the button, and then deriving the correct object from that cell.
You need a way to link your cell to your button when you do it like this, you have a few options
1- Set tags on the button to be the corresponding index of the element in your array, that way you can get a hold of the cells through the tag property, this can have issues if you are deleting cells as you have to update your button tags.
2- Use a dictionary to map your buttons to your cells
So if you use the tag to mark the index you can use cellForRowAtIndexPath of UITableViewCell to get a hold of the cell , the data object should be in your datasourcce array with that index too..
Daniel
You can add a view.tag to each button: e.g assign 101 to the first button, 102, to the second etc.
This tag then corresponds to a position in array, where you find your further info.
One thing I've done in the past is set the tag of the button to the row of the cell. This only works if:
you only have one section in your table
you ALWAYS set the tag for the button in tableView:cellForRowAtIndexPath: (because of dequeuing)
your table is read only (meaning you don't allow cells to be deleted or moved)
For more complicated tables, you'll likely want a mapping from buttons to your objects.
Maybe instead of using an NSMutableArray, you could use an NSMutableDictionary and assign a key to the new item so it can be ordered more simply.

IOS: Maintaining button state in uitableviewcell

I have an iPhone app problem that's been bugging me for a few days and it really doesn't seem like it should be this difficult so I'm sure I'm missing something obvious. I have researched plenty of forum discussions on "similar" topics but nothing that actually addresses this issue, specifically.
To be clear, if there is some piece of documentation or some other source that I should research, please point me in the right direction.
Here goes...
I have a list of items that I display to the user within a table (uitableview). The cell (uitableviewcell) for each item is custom and contains an image and a Like button (uibutton). As expected, for each item in the the table, the user can click the Like button or ignore it. The like button calls a separate process to update the server. Simple, right?
So, here is the issue:
When the Like button is clicked on a particular cell, the Selected state works fine but the moment you scroll the cell out of view, other random cells in the table show the Like button as Selected even though they were never touched. Because the cells are reused, for obvious performance reasons, I understand why this could happen. What I don't understand is why my approach (see code below) would not override or reset the button's state the way I think it should. For brevity, I am only including the relevant code here (hopefully formatted properly):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MyCustomCell";
MyCustomViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[MyCustomViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
}
NSString *myRating = [[self.dataArray objectAtIndex:indexPath.row] valueForKey:#"my_rating"];
// Create the Like button
UIButton *likeButton = [[UIButton alloc] initWithFrame:CGRectMake(260, 68, 40, 40)];
[likeButton setImage:[UIImage imageNamed:#"thumbsUp"] forState:UIControlStateNormal];
[likeButton setImage:[UIImage imageNamed:#"thumbsUpSelected"] forState:UIControlStateSelected];
if (myRating == #"9") {
[likeButton setSelected:YES];
}
[likeButton setTitle:#"9" forState:UIControlStateNormal];
[likeButton setTag:indexPath.row];
[cell.contentView addSubview:likeButton];
[likeButton addTarget:self action:#selector(likeButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
- (void)likeButtonPressed:(UIButton *)sender {
// Changed the Selected state on the button
UIButton *button = (UIButton *)sender;
button.selected = !button.selected;
// Create a new object with the user's rating and then replace it in the dataArray
NSString *ratingText = sender.titleLabel.text;
NSMutableArray *myMutableArray = [[self.dataArray objectAtIndex:row] mutableCopy];
[myMutableArray setValue:ratingText forKey:#"my_rating"];
[self.dataArray replaceObjectAtIndex:row withObject:myMutableArray];
}
So, I've been through many iterations of this but I can't seem to get the state of the button to show the Selected image for those items that are Liked and keep the normal image for those items that have not been Liked.
Any help or advice would be greatly appreciated.
there is a simple way out of this. you can trick the button to keep being in the latest selected state.
make a mutable array, for the purpose of keeping the selected state of the button
selectedButton = [[NSMutableArray alloc]init];//i've already defined the array at the .h file
for (int i = 0; i<yourTableSize; i++) //yourTableSize = how many rows u got
{
[selectedButton addObject:#"NO"];
}
at the tableviewcell method, declare your button and set it so that it refers to the mutablearray to set it's selected state
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
}
UIImage *img = [UIImage imageNamed:#"btn_unselected.png"];
UIButton *toggleButton = [[UIButton alloc]initWithFrame:CGRectMake(0, 0, img.size.width, img.size.height)];
[toggleButton setImage:img forState:UIControlStateNormal];
img = [UIImage imageNamed:#"btn_selected.png"];
[toggleButton setImage:img forState:UIControlStateSelected];
[toggleButton setTag:indexPath.row+100];//set the tag whichever way you wanted it, i set it this way so that the button will have tags that is corresponding with the table's indexpath.row
[toggleButton addTarget:self action:#selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:toggleButton];
//and now we set the button's selected state, everytime the table reuse/redraw the cell the button will set it's selected state according to the array
if([[selectedButton objectAtIndex:indexPath.row]isEqualToString:#"NO"])
{
[toggleButton setSelected:NO];
}
else
{
[toggleButton setSelected:YES];
}
return cell;
and finally, create the method which the button triggered when the button is pressed, to change it's selected state
-(void)buttonPressed:(UIButton*)sender
{
int x = sender.tag - 100; //get the table's row
if([sender isSelected]) //if the button is selected, deselect it, and then replace the "YES" in the array with "NO"
{
[selectedButton replaceObjectAtIndex:x withObject:#"NO"];
[sender setSelected:NO];
}
else if (![sender isSelected]) //if the button is unselected, select it, and then replace the "NO" in the array with "YES"
{
[selectedButton replaceObjectAtIndex:x withObject:#"YES"];
[sender setSelected:YES];
}
}
The problem is that every time you create or reuse a cell you're giving it a new like button, so when you reuse a cell where the like button has been activated, you're giving it a deactivated like button but the old, activated like button is still there as well.
Instead of creating a like button every time you need a cell, you should just be setting the state of an existing like button. See the answers to this question for some possible ways of handling that.
This is not valid (At least not if you are trying to compare strings my contents instead of addresses):
if (myRating == #"9")
Try this:
if ([myRating isEqualToString:#"9"])
And +1 to yuji for noticing the multiple button creation.

Resources