I have a basic UITableView which contains basic UITableViewCells. When I add a new cell to the table I'd like to:
1) scroll to the new row
2) select all of the text in the new UITableViewCell so that the keyboard becomes visible and the user can immediately edit the cell's text.
The next time through the event loop (after calling -reloadData on the table view) I do:
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:wordIndex inSection:0];
[self.tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:(UITableViewScrollPositionNone)];
UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:indexPath];
[cell.textLabel becomeFirstResponder];
cell.highlighted = YES;
The scrolling is correct but the text in the cell is not selected.
A UILabel can't become a first responder because it returns NO from canBecomeFirstResponder. To give the illusion of a label that is edited, you could try using a UITextField with a borderStyle of UITextBorderStyleNone.
Be careful with the timing of the becomeFirstResponder call, since a control can't be first responder if it is not a subview of a window. This can happen if you are scrolling to a row that is very far off screen and immediately trying to call becomeFirstResponder before it is added as a visible row.
As in previous answer you can create custom child of UITableViewCell, add UITextField on that.
#interface MyTableViewCell : UITableViewCell
#property(nonatomic, retain) IBOutlet UITextField *editableTextField;
#end
Than you can catch didSelectRowAtIndexPath in your controller and set it's textfield to become first responder
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
MyTableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
[cell.editableTextField becomeFirstResponder];
}
UPD: as a trouble resolve for Not-in-window-cell trouble, described by upper answer, you must call selectCellAtIndexPath in method
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
Related
All
It might possible the same question asked many times in different manner. But my condition is somewhat different.
I have table where I have around 10 cell, now each cell have different custom controls(i.e. UILabel, UIButton) etc.
Now I want when I click button on cell 0 it change label(custom UILabel and not cell label) value on the same cell, without reload whole table.
yes is your custom cell class you assign the property to UiButton like this
#property(strong,nonatomic) IBOutlet UIButton *btnAdd;
and next thing in this method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
you write like this code
cell.btnAdd.tag=indexPath.row;
[cell.btnAdd addTarget:self action:#selector(AddMethod:) forControlEvents:UIControlEventTouchUpInside];
and in this class write this code
-(void)AddMethod:(UIButton *)btnAdd
{
NSIndexPath *indexPath=[NSIndexPath indexPathForRow:btnAdd.tag inSection:0]; // if section is 0
customcell *cell = (customcell*)[maintableView cellForRowAtIndexPath:indexPath];
cell.yourlabelname.text=[NSString stringWithFormat:#"%d",[yourlabelname.text intValue] + 1];
}
You can update perticular cell of UITableview just you have store indexPath of that row and use following methods to update it .
[self.myTablviewname beginUpdates];
[self.myTablviewname reloadRowsAtIndexPaths:[NSArray arrayWithObject:button.indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self.myTablviewname endUpdates];
Also you can give animation to row as per your convenience.
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.
I need to detect when a tableview has finished reloading data. There was an older solution where you could subclass the tableview then overload the reloadData method, however apparently that no longer works because tables are handled on multiple threads now and reloadData is called before cellForRowAtIndexPath.
My question is, has there been any solution to this problem since the change?
My problem is I am losing the pointer to a textField when the table reloads its data, so the first responder I am trying to set to the next text field (to auto focus on the next data input field), is lost.
This is essentially a repeat of #wain 's answer, but I thought I would add a little code.
You can keep a reference to the index path of the cell that owns the active text field (as a property).
Then, in cellForRowAtIndexPath: something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
MyTableViewCell *cell = (MyTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//I would hold a reference to the text field as a property on a subclass of UITableViewCell so that you can check for whether it exists.
if (!cell.textField) {
cell.textField = [[UITextField alloc] initWithFrame:cell.contentView.frame];
[cell.contentView addSubview:cell.textField];
}
return cell;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
UITableViewCell *cell = (UITableViewCell *)textField.superview.superview;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
self.indexPathForActiveTextField = indexPath;
}
- (BOOL) textFieldShouldReturn:(UITextField *)textField
{
MyTableViewCell *cell = (MyTableViewCell *)textField.superview.superview;
NSIndexPath *ip = [self.tableView indexPathForCell:cell];
[self.tableView reloadData];
NSIndexPath *nextIndexPath = [NSIndexPath indexPathForRow:ip.row+1 inSection:ip.section];
MyTableViewCell *theNewCell = (MyTableViewCell *)[self.tableView cellForRowAtIndexPath:nextIndexPath];
if (theNewCell) {
[theNewCell.textField becomeFirstResponder];
}
return YES;
}
Store the NSIndexPath of the table cell that holds the text field that should be the first responder. When you want to change the first responder you can ask the table view for the cell at that index path, then find the text field and make it first responder.
If the table view gets reloaded, in cellForRowAtIndexPath: check the index path and make the 'new' text field the first responder.
In this way you can set the first responder at any time and you can't loose the reference to it as the reference is to a location, not an object (which will be reused or removed).
UITableView uses a pool to reuse displayed cell. The target cell is possibly reused in other row. Storing the NSIndexPath like Wain suggested is good untill you not reorder the cells or delete some entry from the datasource. Define a key in the model, that you set the firstResponder according to that. Hope i did not misunderstood the problem.
I have a UITableView with a UITextField inside of each cell. A model object that stores the index of the cell that is currently being edited. If the cell scrolls off-screen, my app takes away first-responder status. (Failing to do so may cause problems). Now, suppose a cell (possibly the same one, or possibly a different one) corresponding to that index is about to scroll back onto the screen. I want to make that cell's textField the firstResponder. My delegate does receive a call
tableView: willDisplayCell: forRowAtIndexPath:
corresponding to the new cell. However, calling becomeFirstResponder: at that point does not help as the cell won't accept firstResponder status until it has been displayed.
Short of using a timer, any ideas for how to call becomeFirstResponder: at a point when the cell is in fact able to become the first responder?
EDIT: cellForRowAtIndexPath: is always called before willDisplayCell:. So no help there.
I haven't tried this, but the first thing I'd try is in cellForRowAtIndexPath...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// standard stuff to build cell and setup it's state
if ([indexPath isEqual:self.myModel.indexPathOfTextFieldBeingEdited]) {
// you probably have a handle to the text field from the setup above
UITextField *textField = (UITextField *)[cell viewWithTag:SOME_TAG];
[textField performSelector:#selector(becomeFirstResponder) withObject:nil afterDelay:0.0];
}
return cell;
}
You have to show cell on the screen to make it as first responder. Do at first:
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
and then call first responder on it's label/textField.
Here's what I did in MonoTouch - it's important that you do not animate the ScrollToRow() - i.e. "animated:NO" as shown in the answer by edzio27 (thanks edzio27 :) ).
var newCell = (UIOrderLineCell)tableView.CellAt(newIndexPath);
if (newCell == null)
{
tableView.ScrollToRow(newIndexPath, UITableViewScrollPosition.Middle, false);
newCell = (UIOrderLineCell)tableView.CellAt(newIndexPath);
}