Reputation: 3733
HEADS UP EDIT: I posted the (simple) answer to this below. No need to waste time looking through all this code. Also see structural suggestions in Jack Lawrence's answer.
I’ve set up a UITextField in a tableViewCell that starts out with enabled = NO
. Later it is changed to enabled = YES
so that it can become first responder and call a keyboard, etc.
But once enabled
is NO, it can never be YES again:
tf.enabled = YES;
printf(" Is tf enabled? %d", [tf isEnabled]); // this prints 0 — it refuses to be re-enabled.
Even weirder, if I subclass UITextField and give it a simple custom method, I am able to call that method successfully from the controller — before enabled
is set to NO. Once enabled
is NO, calling the custom method does nothing. There are no warnings or exceptions; it just doesn’t run.
So the problem isn’t just some trouble with the responder chain. The once-disabled textfield behaves like a black hole.
I did take a look at Apple’s demo for editable textfields in cells, “TaggedLocations,” and they fiddle with enabled
with complete impunity: nameField.enabled = NO;
and nameField.enabled = YES;
and everything goes like clockwork.
There is one other clue: The UIControl class ref says, “If the enabled state is NO, the control ignores touch events and subclasses may draw differently.” But refuse to be re-enabled? Refuse to accept method calls? I don’t get it.
OK, this is probably more than you really want to look at, but hopefully there are some clues buried in it.
Here are the UITextField subclass’s h and m files:
#import "FooNameFieldForCell.h"
@implementation FooNameFieldForCell
- (BOOL) canBecomeFirstResponder {
printf("\nCBFR called, self.enabled is %d.", self.enabled);
if (self.enabled)
return YES;
else
return NO;
}
- (BOOL) canYouHearMe:(id)sender {
printf("Yes, I hear you.");
return YES;
}
@end
#import <UIKit/UIKit.h>
@interface FooNameFieldForCell : UITextField {
}
- (BOOL) canYouHearMe:(id)sender;
@end
Here’s the cellForRow and the action method:
BOOL bPortrait = UIInterfaceOrientationIsPortrait(self.interfaceOrientation);
// Cell Which Layout
NSString *identifier = nil;
UITableViewCell *cellFooList = nil;
switch (indexPath.row) {
case 0:
// this textfield is in row 0
identifier = bPortrait ? @"cellFooListName_portrait" : @"cellFooListName_landscape";
cellFooList = [tableView dequeueReusableCellWithIdentifier:identifier];
if (!cellFooList)
cellFooList = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
break;
// other rows…
}
// Foo
Foo *Foo = [self.frcFoosAllReg_byName.fetchedObjects objectAtIndex:indexPath.section];
// Cell Fill
switch (indexPath.row) {
case 0: {
if (cellFooList.tag == 1) {
for (UIView *subview in cellFooList.subviews) {
if (subview.tag == 2) {
((FooNameFieldForCell *)cellFooList).text = Foo.FooName;
break;
}
}
break;// dequeued cell, already done
}
// button "Edit"
// (Yes, I know you’re supposed to put this on the navBar, but as a user I have never liked that metaphor. I’m doing the editability by section/row.)
UIButton *btnEditFooName = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btnEditFooName addTarget:self action:@selector(presentEditFooName:) forControlEvents:UIControlEventTouchUpInside]; // the selector is where I’m trying to enable the textfield
// do button setup & framing
[btnEditFooName setFrame:rectBtnEditFooName];
[cellFooList.contentView addSubview:btnEditFooName];
// TEXTFIELD SETUP FOLLOWS
// do framing
FooNameFieldForCell *tfFooName = [[FooNameFieldForCell alloc] initWithFrame:rectFooName];
[tfFooName canYouHearMe:self]; // custom method runs OK here
tfFooName.text = Foo.FooName;
tfFooName.enabled = NO;
tfFooName.tag = 2; // tag to locate among cell's subviews
[cellFooList.contentView addSubview:tfFooName];
[tfFooName release];
// no row selection allowed
cellFooList.selectionStyle = UITableViewCellSelectionStyleNone;
// tag as done
cellFooList.tag = 1;
break;
// other cases…
}
}
return cellFooList;
}
// Called by the Edit button:
- (void) presentEditFooName:(id)sender {
// Determine which Foo is associated with sender.
if (![sender isKindOfClass:[UIButton class]])
return;
UIView *cell = ((UIButton *)sender).superview.superview;
if (!cell || ![cell isKindOfClass:[UITableViewCell class]])
return;
NSIndexPath *indexPath = [self.tvFooList indexPathForCell:((UITableViewCell *)cell)];
Foo *Foo = [self.frcFoosAllReg_byName.fetchedObjects objectAtIndex:indexPath.section];
FooNameFieldForCell *tfFooName = nil;
for (UIView *subview in cell.subviews) {
if (subview.tag == 2) {
tfFooName = subview;
break;
}
}
// BLACK HOLE
// because tfFooName is nil. But why? It shows up in the cell.
tfFooName.enabled = YES;
[tfFooName becomeFirstResponder];
printf("\ntfFooName can become firstResp? %d", tfFooName.canBecomeFirstResponder); // always prints 0, can't be firstResp
[tfFooName canYouHearMe:self]; // does not run
printf(" Is tfFooName enabled? %d", [tfFooName isEnabled]); // prints 0, still disabled
}
Well, my mysterious black hole turns out to be -- null! OK, seems obvious now, especially since objective-c allows calls on null. But I'm left with another mystery: The field's text appears in the cell, so how can it be nil?
Upvotes: 0
Views: 246
Reputation: 3733
As is usual when I'm tearing my hair out, the black hole mystery turned out to be simple.
This...
FooNameFieldForCell *tfFooName = nil;
for (UIView *subview in cell.subviews) {
if (subview.tag == 2) {
tfFooName = subview;
break;
}
}
...should have been this:
FooNameFieldForCell *tfFooName = nil;
for (UIView *subview in ((UITableViewCell *)cell).contentView.subviews) {
if (subview.tag == 2) {
tfFooName = subview;
break;
}
}
Custom views are one layer down, in the cell's contentView, not its direct subviews. Aargh! So much time wasted!
Upvotes: 0
Reputation: 10772
The text field itself isn't nil - you're right, it wouldn't display in the cell if it was nil. Instead, I think that this block of code isn't finding the cell:
FooNameFieldForCell *tfFooName = nil;
for (UIView *subview in cell.subviews) {
if (subview.tag == 2) {
tfFooName = subview;
break;
}
}
Insert a breakpoint and print the value of tfFooName
after the for loop or inside the if statement to check.
Instead of searching through subviews or super views, consider using one of the following methods:
UIButton
and add an indexpath property (kind of messy)UIButton
(most difficult but most awesome, but there are tutorials and pre-built solutions out there) and just set up exactly what you want to happen in -tableView:cellForRowAtIndexPath:
.Upvotes: 1
Reputation: 182
sometimes if I have a problem setting an object's properties in cases like this, it helps to add a public method to the object like:
-(void) setEnabledToYes
{
self.enabled=YES;
}
I know that is a jury rig solution, but its worked for me before
Upvotes: 0