This question already has answers here:
first responder is not being set correctly
(2 answers)
Closed 9 years ago.
After a UITextField is edited and return is pressed, I loop through and check for the next field. I get the index path of that field, then save it. Next I reload table data and when the table is being built, if the indexPath is equal to the indexPath of what would be the next text field, then I set it as first responder.
I grab the correct textField at the correct indexPath and everything, however first responder doesn't get set. Instead, focus just disappears.
My code:
//inside UITextFieldShouldReturn
for (int i = uiTextField.fieldTag + 1; i < group.fields.count; i++) {
ObjectEditField *field = [group.fields objectAtIndex:i];
if (field.propName && field.updateObjectOnEdit == YES && [field isKindOfClass:[ObjectEditTextField class]]) {
// set the active field
activeField = field;
// obtain a pointer to the textfield object and set it to be the first responder
ObjectEditTextField *textField = (ObjectEditTextField *)field;
if (![textField.field isFirstResponder]) {
UITableViewCell *cell = (UITableViewCell *)[[[uiTextField superview] superview] superview];
firstResponderIndexPath = [_fieldsTableView indexPathForCell:cell];
Then once I have the firstResponderIndexPath I call reloadData on the table view, and inside tableView:cellForRowAtIndexPath: I have:
if (firstResponderIndexPath.row + 1 == indexPath.row)
{
ObjectEditTextField *textField = (ObjectEditTextField *)field;
NSLog(#"display: %#", textField.field.text);
[textField.field becomeFirstResponder];
}
The output for the NSLog is correct and the field it should be. However the first responder doesn't get set correctly.
You can only set the first responder if the view is part of the view hierarchy of the window. "tableView:cellForRowAtIndexPath:" gets called before it is added. So you'll have to set the first responder after the cells have been added, e.g. after you call reloadData in your UITableViewController subclass:
[self.tableView reloadData];
MyTableCell *cell = (id)[self.tableView cellForRowAtIndexPath: firstResponderIndexPath];
[cell.myTextFieldProperty becomeFirstResponder];
Has the cell been added to the window at the point where you are calling becomeFirstResponder? If it is not, nothing will happen. From the docs:
becomeFirstResponder
You may call this method to make a responder object such as a view the first responder. However, you should only call it on that view if it is part of a view hierarchy. If the view’s window property holds a UIWindow object, it has been installed in a view hierarchy; if it returns nil, the view is detached from any hierarchy.
Try calling it in tableView:willDisplayCell:forRowAtIndexPath: instead. You could also set a boolean property on the desired cell and have it call becomeFirstResponder on its field in didMoveToSuperview.
Related
I'm using a button inside a tableView in which I get the indexPath.row when is pressed. But it only works fine when the cells can be displayed in the screen without scroll.
Once the tableView can be scrolleable and I scrolls throught the tableview, the indexPath.row returned is a wrong value, I noticed that initially setting 20 objects, for example Check is just printed 9 times no 20.
- (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];
//This is printed just 9 times (the the number of cells that are initially displayed in the screen with no scroll), when scrolling the other ones are printed
NSLog(#"Check: %li", (long)indexPath.row);
return cell;
}
To do something with the clicked index:
-(void)rowButtonClicked:(UIButton*)sender
{
NSLog(#"Pressed: %li", (long)sender.tag);
}
Constants.h
#define ROW_BUTTON_ACTION 9
What is the correct way to get the indexPath.row inside rowButtonClicked or setting a tag when I have a lot of of cells in my tableView?
My solution to this kind of problem is not to use a tag in this way at all. It's a complete misuse of tags (in my opinion), and is likely to cause trouble down the road (as you've discovered), because cells are reused.
Typically, the problem being solved is this: A piece of interface in a cell is interacted with by the user (e.g. a button is tapped), and now we want to know what row that cell currently corresponds to so that we can respond with respect to the corresponding data model.
The way I solve this in my apps is, when the button is tapped or whatever and I receive a control event or delegate event from it, to walk up the view hierarchy from that piece of the interface (the button or whatever) until I come to the cell, and then call the table view's indexPath(for:), which takes a cell and returns the corresponding index path. The control event or delegate event always includes the interface object as a parameter, so it is easy to get from that to the cell and from there to the row.
Thus, for example:
UIView* v = // sender, the interface object
do {
v = v.superview;
} while (![v isKindOfClass: [UITableViewCell class]]);
UITableViewCell* cell = (UITableViewCell*)v;
NSIndexPath* ip = [self.tableView indexPathForCell:cell];
// and now we know the row (ip.row)
[NOTE A possible alternative would be to use a custom cell subclass in which you have a special property where you store the row in cellForRowAt. But this seems to me completely unnecessary, seeing as indexPath(for:) gives you exactly that same information! On the other hand, there is no indexPath(for:) for a header/footer, so in that case I do use a custom subclass that stores the section number, as in this example (see the implementation of viewForHeaderInSection).]
I agree with #matt that this is not a good use of tags, but disagree with him slightly about the solution. Instead of walking up the button's superviews until you find a cell, I prefer to get the button's origin, convert it to table view coordinates, and then ask the table view for the indexPath of the cell that contains those coordinates.
I wish Apple would add a function indexPathForView(_:) to UITableView. It's a common need, and easy to implement. To that end, here is a simple extension to UITableView that lets you ask a table view for the indexPath of any view that lies inside one of the tableView's cells.
Below is the key code for the extension, in both Objective-C and Swift. There is a working project on GitHub called TableViewExtension-Obj-C that illustrates the uses of the table view extension below.
EDIT
In Objective-C:
Header file UITableView_indexPathForView.h:
#import <UIKit/UIKit.h>
#interface UIView (indexPathForView)
- (NSIndexPath *) indexPathForView: (UIView *) view;
#end
UITableView_indexPathForView.m file:
#import "UITableView_indexPathForView.h"
#implementation UITableView (UITableView_indexPathForView)
- (NSIndexPath *) indexPathForView: (UIView *) view {
CGPoint origin = view.bounds.origin;
CGPoint viewOrigin = [self convertPoint: origin fromView: view];
return [self indexPathForRowAtPoint: viewOrigin];
}
And the IBAction on the button:
- (void) buttonTapped: (UIButton *) sender {
NSIndexPath *indexPath = [self.tableView indexPathForView: sender];
NSLog(#"Button tapped at indexpPath [%ld-%ld]",
(long)indexPath.section,
(long)indexPath.row);
}
In Swift:
import UIKit
public extension UITableView {
func indexPathForView(_ view: UIView) -> IndexPath? {
let origin = view.bounds.origin
let viewOrigin = self.convert(origin, from: view)
let indexPath = self.indexPathForRow(at: viewOrigin)
return indexPath
}
}
I added this as a file "UITableView+indexPathForView" to a test project to make sure I got everything correct. Then in the IBAction for a button that is inside a cell:
func buttonTapped(_ button: UIButton) {
let indexPath = self.tableView.indexPathForView(button)
print("Button tapped at indexPath \(indexPath)")
}
I made the extension work on any UIView, not just buttons, so that it's more general-purpose.
The nice thing about this extension is that you can drop it into any project and it adds the new indexPathForView(_:) function to all your table views without having do change your other code at all.
You are running into the issue of cell-reuse.
When you create a button for the view you set a tag to it, but then you override this tag to set the row number to it.
When the cell get's reused, because the row number is longer ROW_BUTTON_ACTION, you don't reset the tag to the correct row number and things go wrong.
Using a tag to get information out of a view is almost always a bad idea and is quite brittle, as you can see here.
As Matt has already said, walking the hierarchy is a better idea.
Also, your method doesn't need to be written in this way. If you create your own custom cell, then the code you use to create and add buttons and tags isn't needed, you can do it in a xib, a storyboard, or even in code in the class. Furthermore, if you use the dequeue method that takes the index path, you will always get either a recycled cell, or a newly created cell, so there is no need to check that the cell returned is not nil.
In my app i have a login form, where user enter one field password. so i added a subveiw of UITextField in UITableViewCell during function call of cellForRowAtIndexPath.
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"loginViewCell"];
UITextField *editableTextField = nil;
// Done Some settings of editableTextField
// Adding Subview
[cell addSubview:editableTextField];
When user press login button i called a selector function name login
UITableViewCell *cell = [loginTableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
// Then i travesed in the subviews of cell and from UITextField subview i extract password which was entered by user.
for (UIView *view in cell.subviews) {
if ([view isKindOfClass:[UITextField class]]) {
usernameField = (UITextField *)view;
break;
}
}
This is working fine till IOS 7
After searching on net i trace the problem that in IOS 7 Apple changes the view hierarchy of UITableViewCell and now there is additional class inserted UITableViewCellScrollView.
I debug my code in my selector function login it is getting the same cell in which i added the subview i print the name of cell.subview is is showing UITableViewCellScrollView previously it was showing UITableViewCellcontentView (before ios7)
How can I extract the password from subeview of UITableViewCell?
The cellForRowAtIndexPath: method is not the right place to add subviews but rather to manipulate them.
You should really create a subclass of UITableViewCell having a #property UITextField which is added to the cell within the initWithStyle:reuseIdentifier: method.
[self.contentView addSubview:self.passwordField];
Then you can access this particular UITextField with [cell passwordField] or whatever you want to call it.
The easiest way would be to subclass your UITableView cell and add a class property that is your UITextField. Then you could just call cell.textField.text instead of searching through the view hierarchy.
The second way would be to search recursively through the entire view hierarchy, not just a single layer of subviews. (and you should be searching the cell.contentView anyway, bad things happen when you add views as subview's of the cell directly.)
EDIT adding code for searching recursively through view hierarchy. I do not recommend this method, I recommend subclassing UITableViewCell (it will make your like so much easier), but here you go.
You would call a function like
UIView *yourFoundSubview = [self findTextFieldInCell:cell];
And that function would be defined:
-(UIView*)findTextFieldInCell:(UIView*)input
{
if([input isKindOfClass:[UITextField class]])
{
return input;
}
UIView *foundview;
for(UIView *view in input.subviews)
{
foundview = [self findTextFieldInCell:view];
if(foundview)
{
return foundview;
}
}
return nil;
}
I believe you need to get the subviews of the UITableViewCellScrollView.
Instead of adding the textfield to the cell, add the UITextField to cell.contentView, and look for the textField in the subviews of the cell.contextView.
I think what would really be best in the long run for your solution though, would be to create a custom UITableViewCell, and add the textField in there. You could directly access your textfield that way without having to loop through the subviews of the cell.contentView.
You could do 2 things:
If you have only have 1 textfield for you entire viewcontroller, you define a property that holds a reference to your passwordTextField
#property(strong, nonatomic) UITextField *passwordTextfield;
Or, if you have a textfield for each tableviewCell, you could define a collection of UITextFields
#property(strong, nonatomic) NSMutableArray *textFields;
then you can reference your passwordTextField with:
UITextField *passwordField = self.textFields[PASSWORD_ROW];
I am working on an iOS application where a button is enabled only after all UITextFields that are filled in (i.e. have at least one character inside each one). Each UITextField is in a custom UITableViewCell inside of a UITableView. Once the user has entered a value in each UITextField, I need the button to be enabled. Inside my viewDidLoad method, I disable the button as follows:
[_doneButton setEnabled:NO];
and then I have the following method:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
for (int i = 0; i < [self.table numberOfSections]; i++) {
NSInteger rows = [self.table numberOfRowsInSection:i];
for (int row = 0; row < rows; row++) {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:i];
SimpleTableCell *cell = (SimpleTableCell *)[self.table cellForRowAtIndexPath:indexPath];
for (UIView *subView in cell.subviews) {
if ([subView isKindOfClass:[UITextField class]]) {
//Does not come to this point
UITextField *txtField = (UITextField *)subView;
if([[txtField text] length] > 0) {
//Enable button and bold title
[_doneButton setEnabled:YES];
[_doneButton.titleLabel setFont:[UIFont boldSystemFontOfSize:28]];
}
else {
[_doneButton setEnabled:NO];
}
}
}
}
}
return YES;
}
Unfortunately, my code does not get into the "if" clause where I check to see if the custom cell has a subView of type UITextField, and because of that, the button is never enabled. For the record, "SimpleTableCell" is the name of my custom UITableViewCell class that I've created. My code appears to be correct, but I can't figure out why it is not functioning correctly. Can anyone see what it is I'm doing wrong?
I think I would go about this in a completely different way. I would create a mutable dictionary to hold the strings in the various text fields. Give the text fields tags, and use the tags (converted to NSNumbers) as the keys in the dictionary. Implement textFieldDidEndEditing:, and in that method get the string, check that it's not an empty string, and if not, do setObject:forKey: on the dictionary. After you set the value, check the count of the dictionary's allKeys property, and if it equals the number of text fields you have, enable the button.
That's because the only logical subview of the cell is an UIView containing all the items of the cell. It's property is "contentView".
The content view of a UITableViewCell object is the default superview for content displayed by the cell. If you want to customize cells by simply adding additional views, you should add them to the content view so they will be positioned appropriately as the cell transitions into and out of editing mode.
More information about it here: https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewCell_Class/Reference/Reference.html#//apple_ref/occ/instp/UITableViewCell/contentView
Have you set the UITextField's delegate? If that method is not getting called at all, that is the problem.
I am creating custom control using UITableView. This control is just a table view with multiple row in one section. And let's called it MyCustomControl.
In every cell on the table view, I added a UITextField control.
----------------
| ------------ |
|| Text Field || -> CELL
| ------------ |
----------------
This cell is just default UITableViewCell, and I added the text field using [cell addSubview:textField];
I give access to every text field component on table view with method like this:
- (UITextField*) textFieldAtIndex:(NSInteger)index
{
NSIndexPath* indexPath = [NSIndexPath indexPathForRow:index inSection:0];
UITableViewCell* cell = [self tableView:[self containerTableView] cellForRowAtIndexPath:indexPath];
UITextField* textField = [[cell subviews] lastObject];
return textField; // It should returning the reference of textField right??
}
Once upon a time, I use this MyCustomControl in a view controller somewhere in my project.
- (void) viewDidLoad
{
[super viewDidLoad];
CGPoint origin = CGPointMake(100.f, 100.f);
CGFloat width = 200.f;
// Create custom control with text field generated as many as given parameter
MyCustomControl* textGroup = [[MyCustomControl alloc] initWithOrigin:origin width:width textFieldCount:2];
// Here is the problem, I try to set current view controllers's text fields
// with the text field generated by MyCustomControl.
// Text field returned are new text field with same properties instead of reference.
self.txtUsername = [textGroup textFieldAtIndex:0]; // Text Field are copied!
self.txtPassword = [textGroup textFieldAtIndex:1]; // Not return a pointer!
[self.txtUsername setDelegate:self]; // Fail
[self.txtPassword setDelegate:self]; // Fail
[[self view] addSubview:textGroup];
[[self view] addSubview:[self txtUsername]]; // New text field appeared on view
}
I expected to have full control of text field in MyCustomControl accessed from method textFieldAtIndex: but instead of having reference to that text field I got a new copy of text field in my view controller. And I can't set the delegate for that text field neither all other stuffs like it's text.
How can I get the reference from text field in this table view cell?
Check my answer to this, they wanted a text field but the process is the same, much cleaner than some of the other solutions here:
How to get UITableView Label Text string - Custom Cell
Thats assuming you know the index path.
Make your custom cell class and IBOutlet of your textfield to it
Hide this textfield wherever you dont require in cellForRowAtIndexPath
Textfield has touchDown action method set it when you are creating new one (same as button has touchUpInside by default - you can see this options by selecting your control and click on connection inspector)
From Below approach you can get reference of your textfield
- (IBAction)actionTextField:(id)sender {
UIView *view = sender.superview;
while (view && ![view isKindOfClass:[UITableViewCell self]]) view = view.superview;
UITableViewCell *cell = (UITableViewCell *)view;
indexPathForDeletion = [self.tableViewListViewVC indexPathForCell:cell];
NSLog(#"cell is in section %d, row %d", indexPathForDeletion.section, indexPathForDeletion.row);
}
The cells of tableview in your custom control Class is not created at the time you create instance of your custom class, so it suggest that you are assigning the textfields before their creation that is the problem i guess.
To get a reference of textfield from each cell ,you can give tag value to each textfield at the time you creating cell and can access them by the tag value whenever you want in your current controller.
This is what I'm trying to achieve:
I want a UITextField that is in UITableViewCell to become first responder only when the cell touched. I would like the text field to become the first responder only when I set it to first responder in the method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath`
Any idea how I can achieve this? When the textfield is touched directly, tableView:didSelectRowAtIndexPath: is never called.
Thanks in advance.
I guess, u have a custom UITableViewCell. In that u could have a UITextField member. Right?
In the custom cell class, override the method,
- (void)setSelected:(BOOL)selected animated:(BOOL)animated:
In that , if selected == YES, then make the text field as first responder. Else , resign the first responder.
Let an object be the delegate of the UITextField and implement textFieldShouldBeginEditing:; return NO if the cell hasn't been selected. (You might be able to always return NO from this method, if calling becomeFirstResponder directly bypasses the check.)
For having the textField become the first responder when a uitableviewcell is selected, use your textField as your property, and call [self.textField1 becomeFirstResponder];
Note: You will need as many properties as your the number of UITableViewCells as each cell has a textField.
When a textField is touched, the compiler will not know that the corresponding row got selected.
For this you will need to use tags, such as textField.tag == 0 for the first textField, textField.tag ==1 for the second TextField and so on. And in the textFieldDidBeginEditing you should check for the tag, and then link that value with the corresponding row selected.
Did any of these make sense ? :)
First, you need a custom table cell, your own subclass of UITableViewCell. In that implementation, you need to implement hitTest: to determine where the touch occurred. In that method you can determine if the touch was in fact inside the rect of your UITextField, and if it was, make it the first responder. Here's an example from some code I wrote for a project:
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
if (self.editing) {
if ([nickname pointInside:[self convertPoint:point toView:nickname] withEvent:nil])
return [nickname hitTest:[self convertPoint:point toView:nickname] withEvent:event];
return [super hitTest:point withEvent:event];
}
return [self contentView];
}
The attribute nickname, in this case, was a UITextField inside the custom UITableViewCell.
The condition around self.editing may or may not be relevant to your application. The idea here is show you how hitTest: might be used, in general.