In my tableview, I have several different custom cells. In one of them, it has a button. This button brings up another view controller. However, It is not needed until the tableview has fully loaded. In cellForRowAtIndexPath I set up all my different custom cells. I can uncomment [buttonCell.myButton setHidden:YES]; and it will hide my button. See below.
else if (indexPath.section == 3)
{
ButtonCell *buttonCell = [tableView dequeueReusableCellWithIdentifier:#"ButtonCell"];
//[buttonCell.myButton setHidden:YES];
cell = buttonCell;
}
return cell;
However, I want to then unhide the button after the tableview loads. I finish loading all my arrays in another method where I call reloadData. In that method, I tried to unhide the button by doing this..
[ButtonCell.myButton setHidden:NO];
But the compiler gives me a warning that property myButton is not found in ButtonCell. Does anyone have any ideas how to go about unhiding my button. What am I doing wrong, and what do I not get! Thanks for all your help.
EDIT 1
My button cell class is...
.h
#import
#interface ButtonCell : UITableViewCell
#property (strong, nonatomic) IBOutlet UIButton *myButton;
- (IBAction)YDI:(id)sender;
#end
.m
#import "ButtonCell.h"
#import "AnotherWebViewController.h"
#implementation ButtonCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (IBAction)YDI:(id)sender
{
}
#end
EDIT 2
With everyone's help that answered (thank you all) I have gotten a bit further, but the button is not showing itself. So I still hide the button in cellForRowAtIndexPath, that works as should. Then in my method that I reload the data in I put the following code.
NSIndexPath *index = [NSIndexPath indexPathForRow:0 inSection:3];
ButtonCell *buttonCell = (ButtonCell *) [self.tableView cellForRowAtIndexPath:index];
[buttonCell.myButton setHidden:NO];
The ButtonCell with the button is always the fourth section (counting the first as 0) and it only has one row. Any other help would be appreciated. Almost there!
EDIT 3
Got it! However, it was due to a comment that I was able to figure it out. Thanks to #A-Live. Although I do know how to get the cell in a method outside of cellForRowAtIndexPath thanks to ElJay. So I am giving him the check since I learned something new which is why we post questions anyway. So inside my method cellForRowAtIndexPath is where I hide/show the button. I have a BOOL in my App called finished, it is originally set to true. When the table view ends loading it is set to false. So I just used this bool to show/hide the button.
else if (indexPath.section == 3)
{
ButtonCell *buttonCell = [tableView dequeueReusableCellWithIdentifier:#"ButtonCell"];
if (!_finished)
{
[buttonCell.myButton setHidden:YES];
}else{
[buttonCell.myButton setHidden:NO];
}
cell = buttonCell;
}
return cell;
Once again this is only part of my cellForRowAtIndexPath method. Thanks once again for all the help. I was surprised to see so many answers! Thanks.
Make the property publicaly accessible.
#property (nonatomic, retain) UIButton *myButton;
Then in cellForRowAtIndexpath
ButtonCell *buttonCell =(ButtonCell *) [tableView dequeueReusableCellWithIdentifier:#"ButtonCell"];
myButton belongs to a cell. You will need to get an instance of that UITableViewCell and then you can unhide it, this assumes you want to modify the cell's objects outside of cellForRowAtIndexPsth or willDisplayCell.
In your code
[ButtonCell.myButton setHidden:NO];
You are trying to use the object class name instead of the object name. You need to get the cell that contains your button
buttonCell = [tableView cellForRowAtIndexPath:indexPath];
buttonCell.myButton.hidden = NO;
Mistake in uppercase maybe ?
[buttonCell.myButton setHidden:NO]; // Trying to access instance variable
Instead of :
[ButtonCell.myButton setHidden:NO]; // Trying to access class variable
Do you have a public accessor for that property in the header file of ButtonCell? Something like #property (nonatomic, retain) UIButton *myButton;
This is how I usually see such a compiler warning.
Related
I am trying to use use CollectionView custom cell, where I need to update the cell from the collection view cell custom class itself.
Here is the custom cell-class
Cell_Obj.h
#import <UIKit/UIKit.h>
#interface Cell_Obj : UICollectionViewCell
#property (weak, nonatomic) IBOutlet UILabel *label;
- (void)changeImage;
- (void)updateTextLabelName:(NSString*)str;
#end
Cell_Obj.m
#import "Cell_Obj.h"
static NSString *labelTxt ;
#implementation Cell_Obj{
}
+ (void)initialize {
if (self == [Cell_Obj class]) {
labelTxt = [[NSString alloc] init];
}
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
}
return self;
}
- (void)awakeFromNib {
_label.text = labelTxt;
[NSTimer scheduledTimerWithTimeInterval:2.0f
target:self
selector:#selector(updateLabel)
userInfo:nil
repeats:YES];
}
- (void)updateLabel
{
NSString * txt = [NSString stringWithFormat:#"%#",labelTxt];
_label.text = txt;
}
- (void)updateTextLabelName :(NSString*)str
{
labelTxt = str;
}
#end
Where in viewCotroller I am adding the cell like,
- (void)addCell
{
Cell_Obj *cell = [[Cell_Obj alloc] init];
NSString * txt = [NSString stringWithFormat:#"%d",[GridArray count]];
[cell updateTextLabelName: txt];
[GridArray addObject:cell];
[_collection insertItemsAtIndexPaths:#[[NSIndexPath indexPathForItem:[GridArray count]-1 inSection:0]]];
}
The problem with above code is when I add the first cell, the label of first cell is 0 and that's fine, but when I add the second cell and the timer call happens both cell1 and cell2 has label value 1. And it supposed to have 0,1 respectively.
And it seems like the cell object is sharing static variable upon on any update happens on already created cell, like a timer event.
Why this happening, is there any mistake in my approach?
Please let me know your valuable suggestion.
Edit
Based on below answer I have moved the static variable as instance variable,
#implementation Cell_Obj{
NSString *labelTxt ;
}
but inside updateLabel labelTxt is nil. Where when I debug updateTextLabelName called before updateLabel and the labelTxt has correct value.
This is because collectionview resues the cell to make it memory efficient. So evrery time it will call awakeFromNib when it deque the cell. So you should use collection view datasource methods to update or set content of collection view controls. you should implement cellForItemAtIndexPath to set data in your label!!
As it is an static variable, it's shared by all cell instances.
The way to make it work will be to remove static from labelTxt definition.
Also, what's the meaning of it being static? If it's due to the timer, just check in the timer method that label is not null before making the update and that will solve all your problems.
I'm presenting a lot of data in format of a table with multiple columns. Almost each column has a button (up to 4 in total) and each row is a UITableViewCell.
How could I detect that the buttons were touched and where should I handle touch events of the buttons? I'm certain, it shouldn't be a didSelectRowAtIndexPath method though.
As soon as, I could detect which button was pressed, I would fetch the data in that particular row and manipulate it. So, I need to know the indexPath of the row, as well as what button was pressed on it.
You can subclass UIButton with two properties row and column and implement the logic below:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"
forIndexPath:indexPath];
MyButton *button1 = (MyButton *)[cell viewWithTag:1];
button1.row = indexPath.row;
button1.column = 1; // view tag
[button1 addTarget:self
action:#selector(clickAction:)
forControlEvents:UIControlEventTouchUpInside];
// button2,button3...
return cell;
}
-(void)clickAction:(MyButton *)sender {
// now you can known which button
NSLog(#"%ld %ld", (long)sender.row, (long)sender.column);
}
Generalized undetailed answer:
Create UITableviewcell subclass, link cell ui elements to this class.
Add method configureWithModel:(Model*)model; //Model being the information you want the cell to represent
Manipulate that information or
If you need to manipulate the screen or other objects. You need to give the table view cell subclass a reference to the other objects when the cell is created. (in code or in storyboard or in nib).
how to handle button presses in ios 7: Button in UITableViewCell not responding under ios 7 (set table cell selection to none)
how to link a button: http://oleb.net/blog/2011/06/creating-outlets-and-actions-via-drag-and-drop-in-xcode-4/
If those four views are UIButton then you will receive the tap events on each button or if they are not UIButton then you should add UITapGestureReconiser on each of this views
Several options here. But I would do the following:
Adopt a Delegate Protocol in your custom cell class (see here: How to declare events and delegates in Objective-C?) . This will handle the target selector for the buttons. Pass this message back to your view controller with the sender. To detect which cell it was in do the following:
CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
CGRect senderFrame = CGRectMake(buttonPosition.x, buttonPosition.y, sender.frame.size.width, sender.frame.size.height);
From here you can decide what the do. Use the buttons .x coordinate to determine which button it was or specify a different tag for each button in cellForRowAtIndexPath:. Or if you want to grab the index path of the cell you can do:
NSArray *indexPaths = [YOUR_TABLE_VIEW indexPathsForRowsInRect:senderFrame];
NSIndexPath *currentIndexPath = [indexPaths lastObject];
Because each button has a different action, the only thing you need to get at runtime is the indexPath of the button. That can be done by looking at the button's superviews until a cell is found.
- (IBAction)action1:(UIButton *)button
{
UITableViewCell *cell = [self cellContainingView:button];
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
MyDataModel *object = self.objects[indexPath.row];
// perform action1 on the data model object
// Now that the data model behind indexPath.row was done, reload the cell
[self.tableView reloadRowsAtIndexPaths:#[indexPath]
withRowAnimation: UITableViewRowAnimationAutomatic];
}
- (id)cellContainingView:(UIView *)view
{
if (view == nil)
return nil;
if ([view isKindOfClass:[UITableViewCell class]])
return view;
return [self cellContainingView:view.superview];
}
There: no delegates, no tags, and the action doesn't care about the internals of the cell.
You will still want to subclass UITableViewCell with the four buttons (call them button1, button2, button3, and button4 if you don't have better names). You can make all the connection is Interface Builder. This will only be needed for populating object data into the cell during -tableView:cellForRowAtIndexPath:.
Ideally, you should create a custom cell by subclassing UITableViewCell and implement the actions for each of these buttons in that cell. If your view controller needs to know about these actions, you can define a protocol, MyCustomCellDelegate or similar, and have your view controller conformed to that protocol. Then MyCustomCell will be able to send messages to the view controller when user interacts with its buttons or other controls.
As in the example code below, you can create a cell in storyboard or nib and hook one of the button's action to firstButtonAction method of CustomTableCell class.
Also, you need to set your view controller as delegate property of CustomTableCell object created and implement the method buttonActionAtIndex: of CustomTableCellDelegate in your view controller class. Use controlIndexInCell param passed to this method to determine which button might have generated the action.
#protocol CustomTableCellDelegate <NSObject>
- (void) buttonActionAtIndex:(NSInteger)controlIndexInCell
#end
In CustomTableCell.h class
#interface CustomTableCell: UITableViewCell
#property (nonatomic, weak) id <CustomTableCellDelegate> delegate
- (IBAction) firstButtonAction:(id)sender
#end
In CustomTableCell.m class
#implementation CustomTableCell
#synthesize delegate
- (IBAction) firstButtonAction:(id)sender{
if ([delegate respondToSelector:#selector(buttonActionAtIndex:)])
[delegate buttonActionAtIndex:0];
}
#end
This is a personal preference on how I like to handle situations like these, but I would first subclass UITableViewCell because your table cells do not look like a default iOS UITableViewCell. Basically you have a custom set up, so you need a custom class.
From there you should set up your 4 IBActions in your header file
- (IBAction)touchFirstButton;
- (IBAction)touchSecondButton;
- (IBAction)touchThirdButton;
- (IBAction)touchFourthButton;
You do not need to pass a sender in these actions, because you will not be using that object in these methods. They are being created to forward the call.
After that set up a protocol for your UITableViewSubClass
#protocol UITableViewSubClassDelegate;
Remember to put that outside and before the #interface declaration
Give your sell a delegate property
#property (nonatomic, strong) id<UITableViewSubClassDelegate> delegate;
and finally define your actual protocol, you will need to set up 4 methods, 1 for each button and take your subclass as a parameter
#protocol UITableViewSubClassDelegate <NSObject>
- (void)forwardedFirstButtonWithCell:(UITableViewSubClass*)cell;
- (void)forwardedSecondButtonWithCell:(UITableViewSubClass*)cell;
- (void)forwardedThirdButtonWithCell:(UITableViewSubClass*)cell;
- (void)forwardedFourthButtonWithCell:(UITableViewSubClass*)cell;
#end
This will be placed outside of the #interface #end section at the bottom
After that create a configureWithModel: method in your #interface and #implementation as well as a property for your model
#interface:
#property (nonatomic, strong) Model *model;
- (void)configureWithModal:(Model*)model;
#implementation:
- (void)configureWithModal:(Model*)model {
self.model = model;
// custom UI set up
}
From here you should configure your action methods in your #implementation file to call the delegate methods, i'm only showing the first one, but you would do this with all of the IBActions
- (void)configureWithModal:(Model*)model {
[self.delegate forwardFirstButtonWithCell:self];
}
From here your custom cell set up is done and we need to go back to the UIViewController that is displaying the UITableView. First go into the header file of the view controller, and import your custom UITableViewCellSubClass and then setup the class to implement this protocol.
It should look something like this
#interface MYViewController : UIViewController <UITableViewSubClassDelegate>
from there you should go into your cellForRowAtIndexPath: method and configure your custom UITableViewCell
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCellSubClass *cell = [tableView dequeueReusableCellWithIdentifier:#"CellIdentifier"];
cell.delegate = self;
Model *cellModel = self.tableData[indexPath.row];
[cell configureWithModel:cellModel];
return cell;
}
Now go into your cell class and copy paste all of the protocol methods into your viewController class. I will display one as an example.
In your UIViewController:
- (void)forwardedFirstButtonWithCell:(UITableViewSubClass*)cell {
Model *cellModel = cell.model;
// do stuff with model from outside of the cell
}
do that for all methods and you should be good.
Remember to have all your #imports in so there's no forward declarations and remember to link up the IBActions to your storyboard or xib files. If you want a custom xib for your table cell you will have to check if the cell is nil and then allocate a new one, but if you are using prototype cells then this should be sufficient.
For simplicity sakes i put forwardFirstButtonWithCell: but i would encourage making the name something that describes what it's doing such as, displayPopOverToEnterData or something similar. From there you could even change the parameters of the delegate protocol methods to take models instead so instead of
- (void) displayPopOverToEnterDataWithCell:(UITableViewSubClass*)cell;
make it
- (void) displayPopOverToEnterDataWithModel:(Model*)model;
but, i don't know what type of information you need to access from the cell. So update these methods as you see fit.
I have a view which contain a UITableView. The cells of this UITableView are created in Interface Builder, in order to have different kinds of cells. So the action of each button is managed in the cell classes like following.
- My UITableView containing different kinds of cells :
- The header file of my class for type one cells ("CellTypeOne.h") :
#interface CellTypeOne : UITableViewCell
{
}
- (IBAction)actionForFirstButton:(id)sender;
- (IBAction)actionForSecondButton:(id)sender;
- (IBAction)actionForThirdButton:(id)sender;
- (IBAction)actionForFourthButton:(id)sender;
- The header file of my class for type two cells ("CellTypeTwo.h") :
#interface CellTypeTwo : UITableViewCell
{
}
- (IBAction)actionForTheUniqueButton:(id)sender;
- The view which contain my table view ("ViewContainingMyTableView.h") :
#interface ViewContainingMyTableView : UIViewController <UITableViewDelegate, UITableViewDataSource>
{
UITableView *myTBV;
}
#property (retain, nonatomic) IBOutlet UITableView *myTBV;
#end
Here the thing I want to do :
When I click on the first button in the first cell, for example, I want to be able to show the indexPath of the "current" cell.
For example, I want to have the following output when :
I click on the first button in the first cell : 0
I click on the third button in the first cell : 0
I click on the first and unique button in the second cell : 1
etc...
since u are using the custom cell i think u need to handle selection also, because u are touching the button inside the custom cell not the cell itself therefore tableview delegate methods are not fired, better as i said in ur custom cell put a delegate method for example in ur custom cells
in your CellTypeOne.h add this
//#class CellTypeOne; //if u want t pass cell to controller
#protocol TouchDelegateForCell1 <NSObject> //this delegate is fired each time you clicked the cell
- (void)touchedTheCell:(UIButton *)button;
//- (void) touchedTheCell:(CellTypeOne *)cell; //if u want t send entire cell this may give error add `#class CellTypeOne;` at the beginning
#end
#interface CellTypeOne : UITableViewCell
{
}
#property(nonatomic, assign)id<TouchDelegateForCell1> delegate; //defining the delegate
- (IBAction)actionForFirstButton:(id)sender;
- (IBAction)actionForSecondButton:(id)sender;
- (IBAction)actionForThirdButton:(id)sender;
- (IBAction)actionForFourthButton:(id)sender;
in your CellTypeOne.m file
#synthesize delegate; //synthesize the delegate
- (IBAction)actionForFirstButton:(UIButton *)sender
{
//add this condition to all the actions becz u need to get the index path of tapped cell contains the button
if([self.delegate respondsToSelector:#selector(touchedTheCell:)])
{
[self.delegate touchedTheCell:sender];
//or u can send the whole cell itself
//for example for passing the cell itself
//[self.delegate touchedTheCell:self]; //while at the defining the delegate u must change the sender type to - (void)touchedTheCell:(CellTypeOne *)myCell; if it shows any error in the defining of the delegate add "#class CellTypeOne;" above the defying the delegate
}
}
and in your ViewContainingMyTableView.h
#interface ViewContainingMyTableView : UIViewController <UITableViewDelegate, UITableViewDataSource ,TouchDelegateForCell1> //confirms to custom delegate like table delegates
{
UITableView *myTBV;
}
#property (retain, nonatomic) IBOutlet UITableView *myTBV;
#end
and in the ViewContainingMyTableView.m file
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//during the creating the custom cell
CellTypeOne *cell1 = [self.aTableView dequeueReusableCellWithIdentifier:#"cell"];
if(cell1 == nil)
{
cell1 = [[CustomCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
cell.delegate = self; //should set the delegate to self otherwise delegate methods does not called this step is important
}
//now implement the delegate method , in this method u can get the indexpath of selected cell
- (void)touchedTheCell:(UIButton *)button
{
NSIndexPath *indexPath = [self.aTableView indexPathForCell:(UITableViewCell *)button.superview];
NSLog(#"%#",indexPath.description);
}
/* if u pass the cell itself then the delegate method would be like below
- (void)touchedTheCell:(CellTypeOne *)myCell
{
NSIndexPath *indexPath = [self.aTableView indexPathForCell:myCell];//directly get the cell's index path
//now by using the tag or properties, whatever u can access the contents of the cell
UIButton *myButton = [myCell.contentView viewWithTag:1000]; //get the button
//... u can access all the contents in cell
}
*/
in your case, this is for first button in the cell, add the delegate methods for each buttons having different functions, repeat above procedure for another buttons in the cell
hope u get this :) hapy coding
With iOS7 you can get it like this :
- (IBAction)actionForTheUniqueButton:(id)sender
{
YouCellClass *clickedCell = (YouCellClass*)[[sender superview] superview];
NSIndexPath *indexPathCell = [self.tableView indexPathForCell:clickedCell];
}
You may use tag inside your xib on each cell to differentiate them.
Use after [self tag] inside the cell class
create tableView IBOutlet in .h or .m file. You can get the indexpath of selected tablecell by using
inside ur actionForFirstButton: method
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSLog(#"I click on the first button in the first cell : %d", indexPath.row);
edited : use tag of the button to get the index path , in your actionForFirstButton method
if(sender.tag == 0 || 1 || 2 || 3){
{
NSLog(#"the cell is : %d", 0);
}
else if(sender.tag == 4){
{
NSLog(#"the cell is : %d", 1);
}
else{
NSLog(#"the cell is : %d", 2);
}
Your requirement is just to get back the corresponding row value of a button...
Then you have a very simple way ,
#interface CellTypeOne : UITableViewCell
{
NSNumber *rowval;
}
#property (nonatomic,retain)NSNumber* rowval;
same thing in your CellTypeTwo.h
#interface CellTypeTwo : UITableViewCell
{
NSNumber *rowval;
}
#property (nonatomic,retain)NSNumber* rowval;
and now in both .m file you must have defined a method for buttons, So with in those method
just paste this line
NSLog(#"Number : %d",[rowval integerValue]);
and within )tableView cellForRowAtIndexPath: method add this line too
cell.rowval = [[NSNumber alloc]initWithInt:indexPath.row ];
Now every time you press a button of different cell it will reply back with row value of the cell
Hope this will help.. :-)
So I have a UITableView, where all cells have a UITextField in them as a subview with a tag=1. What's troubling me is that I want when a user clicks on a textField and edits it to know on which row has that happened. What I think can solve it, is to make the cell select itself when the subview (UITextField) is selected. How can I achieve that?
I tried with an array, but because cells are reused, it wouldn't work. Looping through all of the rows would be simply too slow.
Disable the UITextField in each of your cells by default and use your UITableView delegate method didSelectRowAtIndexPath: to
Store the indexPath of the selected row in a property
Enable the UITextField
Make the UITextField first responder
Define the property in your class extension:
#interface MyTableViewController ()
#property (strong, nonatomic) NSIndexPath *activeIndex;
#end
In your implementation of didSelectRowAtIndexPath::
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
self.activeIndex = indexPath;
AddCell *selectedCell = (AddCell *)[self.tableView cellForRowAtIndexPath:indexPath];
[selectedCell.textField setEnabled:YES];
[selectedCell.textField becomeFirstResponder];
}
You'll want to disable the UITextField again when it resigns its first responder status.
Assuming your UITableViewController is the delegate for each UITextField, you can do this in your implementation of the UITextFieldDelegate method:
-(void)textFieldDidEndEditing:(UITextField *)textField
{
[textField setEnabled:NO];
}
textfield.superview.superview gives you the cell instance.
Use the delegate to get the action
The correct approach is to convert the textFields bounds so it is relative to the tableView, and then use the origin of this rect to get the indexPath.
CGRect rect = [self.tableView convertRect:textField.bounds fromView:textField];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:rect.origin];
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionMiddle];
Use the UITextFieldDelegate to know when the user start editing a UITextField (with textFieldDidBeginEditing:).
Two solutions then:
Solution 1: Subclass your cell and make it the delegate of the UITextField.
Then in textFieldDidBeginEditing: of your custom cell, make the cell selected:
// MyCustomCell.m
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
[self setSelected:YES animated:YES];
}
Solution 2: Make the view controller the UITextField delegate, and select the right cell from there.
// MyTableViewController.m
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
// Find the cell containing our UITextField
UIView *cell = textField.superview;
while (![cell isKindOfClass:[UITableViewCell class]])
{
cell = cell.superview;
}
// Make the cell selected
[(UITableViewCell *)cell setSelected:YES animated:YES];
}
I'd recommend the first solution, as Andrey Chevozerov said in the comments of one of the answers:
It's better to not use superview for such tasks. Especially in cascade.
Code below will return NSIndexPath.This can be written in UITextField delegate -
[tableView indexPathForRowAtPoint:textField.superview.superview.frame.origin];
Try above line your code.
Why you're using tags for textfields?
The proper way is:
create custom class for cell;
make an outlet for your UITextField;
when creating cells assign your view controller as a delegate for the cell's text field;
assign a tag == indexPath.row to the corresponding cell's text field;
in textFieldShouldBeginEditing place a code for selecting cell:
[self.tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:textfield.tag inSection:0] animated:YES scrollPosition:UITableViewScrollPositionNone];
I have a UITableView with a UITextField in each of the UITableViewCells. I have a method in my ViewController which handles the "Did End On Exit" event for the text field of each cell and what I want to be able to do is update my model data with the new text.
What I currently have is:
- (IBAction)itemFinishedEditing:(id)sender {
[sender resignFirstResponder];
UITextField *field = sender;
UITableViewCell *cell = (UITableViewCell *) field.superview.superview.superview;
NSIndexPath *indexPath = [_tableView indexPathForCell:cell];
_list.items[indexPath.row] = field.text;
}
Of course doing field.superview.superview.superview works but it just seems so hacky. Is there a more elegant way? If I set the tag of the UITextField to the indexPath.row of the cell its in in cellForRowAtIndexPath will that tag always be correct even after inserting and deleting rows?
For those paying close attention you might think that I have one .superview too many in there, and for iOS6, you'd be right. However, in iOS7 there's an extra view (NDA prevents me form elaborating) in the hierarchy between the cell's content view and the cell itself. This precisely illustrates why doing the superview thing is a bit hacky, as it depends on knowing how UITableViewCell is implemented, and can break with updates to the OS.
Since your goal is really to get the index path for the text field, you could do this:
- (IBAction)itemFinishedEditing:(UITextField *)field {
[field resignFirstResponder];
CGPoint pointInTable = [field convertPoint:field.bounds.origin toView:_tableView];
NSIndexPath *indexPath = [_tableView indexPathForRowAtPoint:pointInTable];
_list.items[indexPath.row] = field.text;
}
One slightly better way of doing it is to iterate up through the view hierarchy, checking for each superview if it's an UITableViewCell using the class method. That way you are not constrained by the number of superviews between your UITextField and the cell.
Something along the lines of:
UIView *view = field;
while (view && ![view isKindOfClass:[UITableViewCell class]]){
view = view.superview;
}
You can attach the UITableViewCell itself as a weak association to the UITextField, then pluck it out in the UITextFieldDelegate method.
const char kTableViewCellAssociatedObjectKey;
In your UITableViewCell subclass:
- (void)awakeFromNib {
[super awakeFromNib];
objc_setAssociatedObject(textField, &kTableViewCellAssociatedObjectKey, OBJC_ASSOCIATION_ASSIGN);
}
In your UITextFieldDelegate method:
UITableViewCell *cell = objc_getAssociatedObject(textField, &kTableViewCellAssociatedObjectKey);
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
//...
I'd also recommend re-associating every time a cell is dequeued from the UITableView to ensure that the text field is associated with the correct cell.
Basically in this case, I would prefer you to put the IBAction method into cell instead of view controller. And then when an action is triggered, a cell send a delegate to a view controller instance.
Here is an example:
#protocol MyCellDelegate;
#interface MyCell : UITableViewCell
#property (nonatomic, weak) id<MyCellDelegate> delegate;
#end
#protocol MyCellDelegate <NSObject>
- (void)tableViewCell:(MyCell *)cell textFieldDidFinishEditingWithText:(NSString *)text;
#end
In a implementation of a cell:
- (IBAction)itemFinishedEditing:(UITextField *)sender
{
// You may check respondToSelector first
[self.delegate tableViewCell:self textFieldDidFinishEditingWithText:sender.text];
}
So now a cell will pass itself and the text via the delegate method.
Suppose a view controller has set the delegate of a cell to self. Now a view controller will implement a delegate method.
In the implementation of your view controller:
- (void)tableViewCell:(MyCell *)cell textFieldDidFinishEditingWithText:(NSString *)text
{
NSIndexPath *indexPath = [_tableView indexPathForCell:cell];
_list.items[indexPath.row] = text;
}
This approach will also work no matter how Apple will change a view hierarchy of a table view cell.