15

I am almost done implementing a UITableViewCell with a UITextField in it. Rather then going through CGRectMake and UITableViewCell.contentView I have implemented it the simpler way:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"Cell"];
    [cell setSelectionStyle:UITableViewCellSelectionStyleBlue];
    amountField = [[UITextField alloc] initWithFrame:CGRectMake(110, 10, 190, 30)];
    amountField.placeholder = @"Enter amount";
    amountField.keyboardType = UIKeyboardTypeDecimalPad;
    amountField.textAlignment = UITextAlignmentRight;
    amountField.clearButtonMode = UITextFieldViewModeNever; 
    [amountField setDelegate:self];

    [[cell textLabel] setText:@"Amount"];
    [cell addSubview:amountField];
    return cell;
}

And then I also implemented the didSelectRow method, resigning the textField to allow showing the other fields input views.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    ...
    [amountField resignFirstResponder];
    ...
}

This works smoothly, only thing is there are other rows in the table, when those others are selected the entire cell is selected and turns Blue, while the one with my UITextField doesn't, I mean the field is selected and I can enter text but the cell is not selected. I have tested it and figured out the problem is in the line:

[cell addSubview:amountField];

It seems that this breaks the selectable cell behavior, and even adding it to [cell contentView] doesn't fix this. Did I miss something?

Vineet Singh
  • 4,775
  • 1
  • 28
  • 39
Fabrizio Prosperi
  • 1,268
  • 3
  • 16
  • 30

5 Answers5

45

If the text field has userInteractionEnabled set to YES, and it fills the entire cell, you can not get the cell to listen to touch. In order to get the cell to respond to touches, you need to set the userInteractionEnabled of the text field to NO.

Edit: And if you want to make the text field editable, when the cell is selected, add the following code in didSelectRowAtIndexPath: method,

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    // get the reference to the text field
    [textField setUserInteractionEnabled:YES];
    [textField becomeFirstResponder];
}
EmptyStack
  • 50,606
  • 20
  • 144
  • 175
  • If you set `userInteractionEnabled = NO;` would this not render the UITextField uneditable? –  Jul 05 '11 at 08:46
  • Apparently not: I have followed EmptyStack advice and set [amountField setUserInteractionEnabled:NO]; at the beginning of DidSelectRow method, and then [amountField setUserInteractionEnabled:YES] only when selected row is the one with the text field. Selection is now ok, but I have to tap twice on the row to actually enter text... – Fabrizio Prosperi Jul 05 '11 at 08:50
  • @micpringle, Ofcourse. He can not have both text field and cell respond to touches as the text field fills the cell. – EmptyStack Jul 05 '11 at 08:51
  • 1
    @Fabrizio Prosperi, You have no need to tap twice on the cell. You can call `[amountField becomeFirstResponder]` after the line `[amountField setUserInteractionEnabled:YES]` in `didSelectRowAtIndexPath:` method. – EmptyStack Jul 05 '11 at 08:56
  • Thank you EmptyStack that did the trick, and your solution it's the simplest and working smoothly here! – Fabrizio Prosperi Jul 05 '11 at 09:24
  • I think it's worth pointing out, if I'm understanding this all correctly, that there's no easy way to dynamically determine which UITextField is associated with a given UITableViewCell. Thus, @MicPringle's approach may be more effective for multiple UITableViewCells. – kbpontius Oct 26 '15 at 17:33
  • if I have multiple textFields in the same cell, assume I have a form? How would I make all of them become firstResponders? Is it possible? – Lohith Korupolu Sep 25 '16 at 15:31
  • Not working in Xcode 10.0, iOS 12.1, swift 4.Please help. – Sofeda Dec 06 '18 at 10:52
6

You've not broken anything by adding a subview, instead the UITextField is capturing the touch ahead of the UITableViewCell. You can test this by tapping outside of the UITextField but within the bounds of the UITableViewCell and you'll see it does in fact still select as you would expect.

To get round this, you could subclass UITextField and add a UITableView property. Set the property when you instantiate the UITextField and add it to the cell.

amountField.tableView = tableView;

Then you'd need to override becomeFirstResponder in your subclass, and in the method get the row for the cell with the UITextField and then select it manually

- (BOOL)becomeFirstResponder
{
    // Get the rect of the UITextView in the UITableView's coordinate system
    CGRect position = [self convertRect:self.frame toView:self.tableView];
    // Ask the UITableView for all the rows in that rect, in this case it should be 1
    NSArray *indexPaths = [self.tableView indexPathsForRowsInRect:position];
    // Then manually select it
    [self.tableView selectRowAtIndexPath:[indexPaths objectAtIndex:0] animated:YES scrollPosition:UITableViewScrollPositionNone];
    return YES;
}
  • This also works, but it required a lot of additional code, while setting the text field userInteractionEnabled=NO as suggested below by EmptyStack was a couple of lines of code....at least until I find some other problem :) Thank you for your help though, it made the issue clearer. – Fabrizio Prosperi Jul 05 '11 at 09:29
  • Is this not a retain cycle? `tableView` -> `visibleCells` -> `amountField` -> `tableView` – jakenberg Aug 06 '15 at 18:15
  • @jakenberg true, the `tableView` property on `amountField` should be specified `weak` in order to avoid a potential retain cycle – Ilias Karim Sep 04 '15 at 21:14
  • Don't forget to call [super becomeFirstResponder]; – John D. Mar 10 '16 at 15:45
1

inside the cell for row at index path add this line which is given in bold,

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:@"Cell"];
    [cell setSelectionStyle:UITableViewCellSelectionStyleBlue];

    amountField = [[UITextField alloc] initWithFrame:CGRectMake(110, 10, 190, 30)];
    amountField.placeholder = @"Enter amount";
    amountField.keyboardType = UIKeyboardTypeDecimalPad;
    amountField.textAlignment = UITextAlignmentRight;
    amountField.clearButtonMode = UITextFieldViewModeNever; 

    [amountField setDelegate:self];
    [[cell textLabel] setText:@"Amount"];

    **for (UIView *cellSubViews in cell.subviews) {
            cellSubViews.userInteractionEnabled = NO;
    }**

    [cell addSubview:amountField];
    return cell;
}

try this, it will work

tailec
  • 591
  • 4
  • 20
Mutablegopi
  • 281
  • 2
  • 11
1

First Thing is you havent used reusable cells. The coding which you provided will cause a lot of memory.

Next thing is you can select the row by touching the area other than the textfield.

One solution for your ques is

In your textField Delegate

UITableViewCell *cell = (UITableViewCell *)[textField superView];
cell.selected=YES; //I think this will call the didSelectRowATIndex;

I am not sure this will work. But worth a try.

ipraba
  • 16,095
  • 3
  • 54
  • 58
  • Apart the fact that this table is 4 cells only because it's a nice input mask, so memory shouldn't be a big problem, but why are u saying I have not used reusable cells? I thought I did. I am going to try your solution now. Thanks. – Fabrizio Prosperi Jul 05 '11 at 08:57
  • I think you misunderstood the reusable concept.. See here http://developer.apple.com/library/ios/#documentation/UserExperience/Conceptual/TableView_iPhone/TableViewCells/TableViewCells.html... Anyway 4 cells is ok but with a row of more than 10 will get you a problem. – ipraba Jul 05 '11 at 09:12
  • I might have misunderstood of course, but reusing cells when they all have different controls inside shouldn't be avoided? Anyway thanks for the link :) – Fabrizio Prosperi Jul 05 '11 at 09:19
  • I tried also your solution, implemented in textFieldShouldBeginEditing, it works, but requires then other code to deselect the cell when selecting the others. Anyway is a good hint. Thanks. – Fabrizio Prosperi Jul 05 '11 at 09:26
1

First of all you needed to use reuseIdentifier for cellForRowAtIndexPath, reason if you do not use reuseIdentifier is: when you scroll up and down, it will always allocate new cell and new textfields so you need to put cell==nil condition, so revised code is here:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  static NSString *reuseIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (cell==nil) {
    cell = [[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier] autorelease];

     UITextField *amountField = [[UITextField alloc] initWithFrame:CGRectMake(110, 10, 190, 30)];
     amountField.placeholder = @"Enter amount";
     amountField.keyboardType = UIKeyboardTypeDecimalPad;
     amountField.textAlignment = UITextAlignmentRight;
     amountField.clearButtonMode = UITextFieldViewModeNever; 
     [amountField setDelegate:self];
     [cell.contentView addSubview:amountField];
}
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
[[cell textLabel] setText:@"Amount"];

return cell;
}

In didSelect delegate method you can do like this

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  [prevField resignFirstResponder];
  UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
  UIView *view = [[cell.contentView subviews] lastObject];
  if([view isKindOfClass:[UITextField class]]{
     currentField = (UITextField*)view;
  }
  [currentField becomeFirstResponder];
  prevField = currentField;
}
iMOBDEV
  • 3,567
  • 2
  • 19
  • 33
  • Guys, I appreciate your effort to teach Cocoa best practices, but I tried to put there the only relevant code, amountField is not instantiated every time, but only at view did load, check with !cell is there but I omitted because not relevant to my question, and my table doesn't scroll because it's 4 rows only. The rest of your comments are very helpful though. Thanks! – Fabrizio Prosperi Jul 05 '11 at 09:11