Wrong indexPath.row after reloadData - ios

I'm trying to get the indexPath.row of a button clicked inside a tableView row.
When the user clicks this button I get the index.row corresponding to the button very well, but when I add more objects to the source array to create more cells by calling reloadData, the rowButtonClicked in each cell it's no longer giving me the correct indexPath.row in example I press the index 20 and now the printed indexPath.row is 9.
In cellForRowAtIndexPath to add the event to the button:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
lBtnWithAction = [[UIButton alloc] initWithFrame:CGRectMake(liLight1Xcord + 23, 10, liLight1Width + 5, liLight1Height + 25)];
lBtnWithAction.tag = ROW_BUTTON_ACTION;
lBtnWithAction.titleLabel.font = luiFontCheckmark;
lBtnWithAction.tintColor = [UIColor blackColor];
lBtnWithAction.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin;
[cell.contentView addSubview:lBtnWithAction];
}
else
{
lBtnWithAction = (UIButton *)[cell.contentView viewWithTag:ROW_BUTTON_ACTION];
}
//Set the tag
lBtnWithAction.tag = indexPath.row;
//Add the click event to the button inside a row
[lBtnWithAction addTarget:self action:#selector(rowButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
To do something with the clicked index:
-(void)rowButtonClicked:(UIButton*)sender
{
//Get the index of the clicked button
NSLog(#"%li", (long)sender.tag);
[self doSomething:(long)sender.tag];
}
Constants.h
#define ROW_BUTTON_ACTION 9
Why it is giving an incorrect index when I change the initial items of the tableView? Is there a way to solve this issue?

It looks like you're messing up button tags. Once you set the tag
lBtnWithAction.tag = indexPath.row;
you won't be able to get button correctly with
lBtnWithAction = (UIButton *)[cell.contentView viewWithTag:ROW_BUTTON_ACTION];
(assuming ROW_BUTTON_ACTION is a constant). lBtnWithAction will be nil all the time except when indexPath.row is equal to ROW_BUTTON_ACTION.
I would propose to subclass UITableViewCell, add a button-property there and then just refer to it directly instead of searching by tag. In this case you'll be able to use tags for buttons freely :) –
#interface UIMyTableViewCell : UITableViewCell
#property (nonatomic, strong, nonnull) UIButton *lBtnWithAction;
#end
And then in cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UIMyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UIMyTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
[cell.lBtnWithAction addTarget:self action:#selector(rowButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
}
cell.lBtnWithAction.tag = indexPath.row;
return cell;
}

You can simply update you line -
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
to
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath]

My take on this will be subclassing the UITableViewCell and providing a delegate for it to respond.
code:
//MyCoolTablewViewCell.h
#protocol MyCoolProtocol<NSObject>
-(void)youTouchedMyCoolCell:(MyCoolTableViewCell __nonnull *)myCoolCell;
#end
#interface MyCoolTableViewCell:UITableViewCell
#property(nonatomic, weak,nullable) id<MyCoolProtocol>myCooldelegate;
#end
//MyCoolTablewViewCell.m
#interface MyCoolTableViewCell(){
UIButton *mySuperCoolButton;
}
#end
#implementation MyCoolTablewViewCell
-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
if(!(self = [super initWithStyle:style reuseIdentifier:reuseIdentifier])){
return nil;
}
// init your stuff here
// init button
mySuperCoolButton = [UIBUtton buttonWithType:UIButtonTypeCustom];
[mySuperCoolButton addTarget:self action:#selector(tappedMyCoolButton:) forControlEvents:UIControlEventTouchUpInside];
return self;
}
- (void)tappedMyCoolButton:(id)sender{
if([_myCoolDelegate respondToSelector:#selector(youTouchedMyCoolCell:)]){
[_myCoolDelegate youTouchedMyCoolCell:self];
}
}
#end
then in your controller
// whatEverController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// get the cell
cell = [tableView dequeueReusableCellWithIdentifier:#"myCoolIdentifier"forIndexPath:indexPath];
cell.myCooldelegate = self;
}
-(void)youTouchedMyCoolCell:(MyCoolTableViewCell *)myCoolCell{
NSIndexPath *myCoolIndexPath = [_tableView indexPathForCell:myCoolCell];
// then do whatever you want with the index path
}

Related

How to solve this override UITextField values?

I have created iOS new app. UITextField is added in each row of UITableView. first row UITextField enter values then add new row the first row UITextField values show in second row UITextField. how to solve this issues. please help me . thanks in advance.
Here is my sample code add row functionality
- (IBAction)btnAddRow:(id)sender {
[self loadData:[self.dataArray count]];
[self.tblview reloadData];
}
-(void ) loadData:(NSInteger) Indexvalue
{
newSheet = [NSDictionary dictionaryWithObjectsAndKeys:strEntryID,entryID, nil];
[self.dataArray insertObject:newSheet atIndex:Indexvalue];
}
- (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];
}
UITextField *txtComment = [[UITextField alloc] initWithFrame:CGRectMake(225.0f,5.0f,50.0f,20.0f)];
[txtComment setBorderStyle:UITextBorderStyleLine];
[cell addSubview:txtComment];
[txtComment setTag:indexPath.row];
[txtComment addTarget:self action:#selector(txtCommentChange:) forControlEvents:UIControlEventEditingChanged];
return cell;
}
- (IBAction)btnAddRow:(id)sender {
[self loadData:[self.dataArray count]];
[self.tblview reloadData];
}
-(void ) loadData:(NSInteger) Indexvalue
{
newSheet = [NSDictionary dictionaryWithObjectsAndKeys:strEntryID,entryID, nil];
[self.dataArray insertObject:newSheet atIndex:Indexvalue];
}
- (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];
}
for (UIView *view in cell.subviews) {
if ([view isKindOfClass:[UITextField class]]) {
[view removeFromSuperview];
}
}
UITextField *txtComment = [[UITextField alloc] initWithFrame:CGRectMake(225.0f,5.0f,50.0f,20.0f)];
[txtComment setBorderStyle:UITextBorderStyleLine];
[cell addSubview:txtComment];
[txtComment setTag:indexPath.row];
[txtComment addTarget:self action:#selector(txtCommentChange:) forControlEvents:UIControlEventEditingChanged];
return cell;
}
You write just one for loop after your cell allocation.
txtComment.text = yourText
You missed it
You are adding a new UITextField every time that cellForRowAtIndexPath is called. Cells are re-used as you scroll, so imagine that as a cell goes off the top of the screen, its quickly taken and put at the bottom of the screen to be the new cell that is appearing. This also means that if you added a textfield first time round, then a second one will be added on top of the first.
If you add your UITextField within the same if statement where you initialize your cell, this will prevent more than one textfield appearing. Eg :
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
UITextField *txtComment = [[UITextField alloc] initWithFrame:CGRectMake(225.0f,5.0f,50.0f,20.0f)];
[txtComment setBorderStyle:UITextBorderStyleLine];
[cell addSubview:txtComment];
[txtComment setTag:indexPath.row];
[txtComment addTarget:self action:#selector(txtCommentChange:) forControlEvents:UIControlEventEditingChanged];
}
Theres also a method you can use that is called when a cell is about to be recycled : -prepareForReuse. This is called on the UITableViewCell itself though, so you would need a subclass of UITableViewCell to access it.
- (void) prepareForReuse {
//set text fields to #"" for eg
}

How to detect multiple buttons in tableview cell

How to detect multiple buttons in tableview cell and my doubt is with example i have 3 buttons in cell if i tap on one button that button will change colour and and if i click indexpath.row=1 cell button that button will color also need to change help me
I did like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier];
}
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = CGRectMake(10.0, 0.0, self.tableView.frame.size.width/4, 40.0);
button.tag = 100 + indexPath.row*total_buttons_in_a_row;
[button setTitle:[NSString stringWithFormat:#"%ld",(long)button.tag] forState:UIControlStateNormal];
[button addTarget:self action:#selector(btnClicked:) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:button];
UIButton *button2 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button2.frame = CGRectMake(10.0+self.tableView.frame.size.width/4+10.0, 0.0, self.tableView.frame.size.width/4, 40.0);
button2.tag = 100 + indexPath.row*total_buttons_in_a_row + 1;
[button2 setTitle:[NSString stringWithFormat:#"%ld",(long)button2.tag] forState:UIControlStateNormal];
[button2 addTarget:self action:#selector(btnClicked:) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:button2];
UIButton *button3 = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button3.frame = CGRectMake(10.0+self.tableView.frame.size.width/4*2+10.0, 0.0, self.tableView.frame.size.width/4, 40.0);
button3.tag = 100 + indexPath.row*total_buttons_in_a_row + 2;
[button3 setTitle:[NSString stringWithFormat:#"%ld",(long)button3.tag] forState:UIControlStateNormal];
[button3 addTarget:self action:#selector(btnClicked:) forControlEvents:UIControlEventTouchUpInside];
[cell addSubview:button3];
return cell;
}
-(void)btnClicked:(UIButton *)sender{
id selectedButton = [self.view viewWithTag:sender.tag];
if ([selectedButton backgroundColor] == [UIColor redColor]) {
[selectedButton setBackgroundColor:[UIColor clearColor]];
}else{
[selectedButton setBackgroundColor:[UIColor redColor]];
}
}
total_buttons_in_a_row is an Int. In your case define it in viewDidLoad total_buttons_in_a_row=3
P.S - set the Buttons CGRectMake according to your need.
Assign tag to each button using the combination of section & row..
When method assigned to button get called use '%' & '/' for further manipulation.
Let me know if you have any difficulty in implementing it..
Got to your CustomCell.h & right below #import add this code.
#protocol CustomCellDelegate <NSObject>
- (void)buttonActionwith :(NSIndexPath *)indexPath;
#end
Then Add this code right below #interface CustomCell : UITableViewCell
//Manual Properties
#property (strong, nonatomic) NSIndexPath *buttonIndexPath;
//Delegate
#property (nonatomic, weak) id <CustomCellDelegate> delegate;
The use of this NSIndexPath is to identify which cell user selecting.
Then go to CustomCell.m & in your button IBAction method add this code.
[self.delegate buttonActionwith:self.buttonIndexPath];
The meaning of this line of code is that, when the user TouchUpInside the Button in the cell buttonActionwith delegate method calls, if you set this CustomCellDelegate in your UIViewController where the UITableView is, that delegate method will call inside that ViewController too.
Now go to your MainViewController.h & add
CustomCellDelegate
& it will look like this after that,
#interface MainViewController : UIViewController <CustomCellDelegate,,UITableViewDataSource,UITableViewDelegate>
when creating the Custom UITableViewCell in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath don't forget to add this line of code before return cell;
//Set Cell Values Here
cell.delegate = self; //Setting delegate to self
cell.buttonIndexPath = indexPath;
The modified - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath will look like this,
static NSString *MyIdentifier = #"MyIdentifier"; //Set Identifier for cell
CustomCell *cell = (CustomCell *)[tableView dequeueReusableCellWithIdentifier: MyIdentifier]; //init CustomCell
if (cell == nil) { //Check cell is nill or not
NSArray *nib;
nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCell"
owner:self options:nil]; //if cell is nil add CustomCell Xib
for (id oneObject in nib) if ([oneObject isKindOfClass:[CustomCell class]])
cell = (CustomCell *)oneObject;
}
//Set Cell Values Here
cell.buttonIndexPath = indexPath
return cell;
Finally add this method inside MainViewController.m
#pragma mark - SwipeableCellDelegate
- (void)buttonActionwith:(NSIndexPath *)indexPath { //Delegate method
NSLog(#"Button Clicks at index %ld",(long)indexPath.row);
}
This is how to add a single button inside the Cell. you can use this method to more buttons to the cell.

How to make UIButton appear in last cell only?

I am fairly new to Objective C programming, and have a UITableView setup with a custom cell. I want to make it so a user can touch a button that will add another cell, and this button will appear in the last cell only. Currently, it is not showing up. Here is the code that I am using. I have created the button within the custom cell, and used "setHidden:YES" to hide it within the cell itself. I am trying "setHidden:NO" to make the button appear in the TableView code, but it is not working. I thought maybe it had something to do with reloading the cell, but I am not sure if I am going in the right direction with this or not. I would appreciate any help on this, thanks.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{workoutTableViewCell *cell = (workoutTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
[cell.addButton setTitle:(NSString *)indexPath forState:UIControlStateApplication];
[cell.textLabel setText:[NSString stringWithFormat:#"Row %i in Section %i", [indexPath row], [indexPath section]]];
NSInteger sectionsAmount = [tableView numberOfSections];
NSInteger rowsAmount = [tableView numberOfRowsInSection:[indexPath section]];
if ([indexPath section] == sectionsAmount - 1 && [indexPath row] == rowsAmount - 1) {
NSLog(#"Reached last cell");
[cell.addButton setHidden:NO];
if (lc == NO)
{[[self tableView] reloadData];
lc = YES;
}
}
return cell;
}
Following UITableViewDataSource method will help you to return exact number of rows available in section. Here you need to return additional as you want to have last as your button.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return yourRowCount + 1;
}
Now in folowing method you will check row number using indexpath.row as
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *lastCellIdentifier = #"LastCellIdentifier";
static NSString *workoutCellIdentifier = #"WorkoutCellIdentifier";
if(indexPath.row==(yourRowCount+1)){ //This is last cell so create normal cell
UITableViewCell *lastcell = [tableView dequeueReusableCellWithIdentifier:lastCellIdentifier];
if(!lastcell){
lastcell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:lastCellIdentifier];
CGRect frame = CGRectMake(0,0,320,40);
UIButton *aButton = [UIButton buttonWithType:UIButtonTypeCustom];
[aButton addTarget:self action:#selector(btnAddRowTapped:) forControlEvents:UIControlEventTouchUpInside];
aButton.frame = frame;
[lastcell addSubview:aButton];
}
return lastcell;
} else { //This is normal cells so create your worktouttablecell
workoutTableViewCell *cell = (workoutTableViewCell *)[tableView dequeueReusableCellWithIdentifier:workoutCellIdentifier];
//Configure your cell
}
}
Or you can do like create UIView programatically and set it as FooterView as suggested by #student in comment code would look like,
CGRect frame = CGRectMake(0,0,320,40);
UIView *footerView = [[UIView alloc] initWithFrame:frame];
UIButton *aButton = [UIButton buttonWithType:UIButtonTypeCustom];
[aButton addTarget:self action:#selector(btnAddRowTapped:) forControlEvents:UIControlEventTouchUpInside];
aButton.frame = frame;
[footerView addSubView:aButton];
[yourTableNmae setTableFooterView:footerView];
Declare method as follow
-(IBAction)btnAddRowTapped:(id)sender{
NSLog(#"Your button tapped");
}
if ([indexPath section] == sectionsAmount - 1 && [indexPath row] == rowsAmount - 1) {
NSLog(#"Reached last cell");
[cell.addButton setHidden:NO];
} else {
[cell.addButton setHidden:YES];
}
Replace this code in your program.
If you know your number of cells in the uitable and you wish to just know when the last row will appear, you could implement the following delegate method
(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
this method tells the delegate table view is about to draw cell for particular row, simple compare your row with table rowcount.

Button inside UITableCell - passing data to selector

This subject is all over the stackoverflow but I couldn't find a fitting solution.
I have button on each UITableViewCell.
Here is how I create the cell.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ExaminationCell"];
UIButton *clipButton = (UIButton *)[cell viewWithTag:3];
MedicalExamination *examination = [objects objectAtIndex:indexPath.row];
[clipButton addTarget:self action:#selector(examinatonClicked:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
Here is the method thah should handle the button click:
-(void)examinatonClicked:(MedicalExamination*)examination
{
}
How do I pass examination object to examinatonCommentsClicked method? Obviously the object is different for each cell...
A somewhat hacky thing that I'll do, is I'll use the UIButton's tag attribute to pass the row number so I can pull the object from the array that's backing my table.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"ExaminationCell"];
UIButton *clipButton = (UIButton *)[cell viewWithTag:3];
clipButton.tag = indexPath.row //Set the UIButton's tag to the row.
MedicalExamination *examination = [objects objectAtIndex:indexPath.row];
[clipButton addTarget:self action:#selector(examinatonClicked:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
-(void)examinatonClicked: (id)sender
{
int row = ((UIButton *)sender).tag;
MedicalExamination *examination = [objects objectAtIndex: row];
//Profit!
}
I think you have to add button in uitableviewCell through xib(custum cell) and then connect it through iboutlet.

Actions for UIButtons in UITableViewCells

I implemented a UIButton in every cell of my UITableView and set an action for this button. Now I'm trying to find out which of these buttons was tapped:
- (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];
}
// Configure the cell...
<...>
UIButton *but=[UIButton buttonWithType:UIButtonTypeRoundedRect];
but.frame= CGRectMake(275, 5, 40, cell.frame.size.height-10);
[but setTitle:#"Map" forState:UIControlStateNormal];
[but addTarget:self action:#selector(Map:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:but];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
return cell;
}
- (IBAction) Map: (id) sender {
NSIndexPath *indexPath = [[NSIndexPath alloc] init];
indexPath = [tableView indexPathForCell:(UITableViewCell*)[[sender superview]superview]];
[delegate callMap: [[[data objectAtIndex:indexPath.section]
objectAtIndex:indexPath.row+1]
objectAtIndex:kNameInArray]
: [[[[data objectAtIndex:indexPath.section]
objectAtIndex:indexPath.row+1]
objectAtIndex:kXcoordinateInArray] doubleValue]
: [[[[data objectAtIndex:indexPath.section]
objectAtIndex:indexPath.row+1]
objectAtIndex:kYcoordinateInArray] doubleValue]
];
}
But it always sends data[0][1][*] elements to the delegate. I mean indexPath always has 0 raw and 0 section. Where is my mistake?
Do the following:
add the button inside your if (cell == nil) { ... } method or else you will have lots of buttons inside the same cell when your cell is reused.
Set cell.contentViow.tag = SomeValueIdentifyingYourData (some int value representing your model.. maybe just the index of your object array..) before returning the cell.
Inside your Map: method you can access the value again with ((UIButton)sender).superview.tag and make your decisions based on that value

Resources