A.Baker
A.Baker

Reputation: 23

in iOS, how can I switch between text fields in two cells?

I have an app using a TableView of custom cells, each containing a textField. When the user taps a cell, I want to put the corresponding textField into edit mode. If another cell's textField is already in edit mode, I need to have it resignFirstResponder so I can save its data and then make the new cell's textField become the FirstResponder. But when I call becomeFirstResponder on the new cell's textField, it returns a zero indicating failure. This seems like a messy way to handle it, but I haven't found anything better - yet.

Here's the full text of my TableViewController's files with a lot of clutter stripped out:

//  TEST_TVC.h
//  TVC_Test
#import <UIKit/UIKit.h>
@interface TEST_TVC : UITableViewController <UITextFieldDelegate>
@end


//  TEST_TVC.m
//  TVC_Test
#import "TEST_TVC.h"
@interface TEST_TVC ()

@property (strong, nonatomic) NSMutableArray * TestData;
@property (strong, nonatomic) UITextField * fieldBeingEdited;
@property (assign, nonatomic) NSInteger cellIndex;
@property (strong, nonatomic) UITableViewCell * cellContainingField;

@end

@implementation TEST_TVC

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
    }
    return self;
}

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Init data for testing the TableView
    self.TestData = [NSMutableArray array];
    for (int i=0; i<25; i++) {
        NSString * title = [NSString stringWithFormat:@"Field %d", i];
        NSArray * data = [NSArray arrayWithObjects:title, @"*", nil];
        [self.TestData addObject:data];
    }
    self.fieldBeingEdited = NULL;
    self.cellContainingField = NULL;
    self.cellIndex = -1;
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.TestData.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Configure the cell...
    NSString *CellIdentifier = @"TVCell";
    NSString * titleText;
    NSString * detailText;
    NSString * detailPlaceholder = @"none";

    // Get the data for this row
    int rowNumber = indexPath.row;
    NSArray * cellData = self.TestData[rowNumber];
    titleText = cellData[0];
    detailText = cellData[1];
    //NSLog(@"cellForRow..., entered row=%d, titleText=%@, detailText=%@", rowNumber, titleText, detailText);

    // Retrieve a pre-built cell to fill in the blanks
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    UILabel * cellTitle = (UILabel *)[cell viewWithTag:98];
    cellTitle.text = titleText;
    UITextField * cellDetail = (UITextField *)[cell viewWithTag:99];
    NSLog(@"cellForRow.. reusing cell with Title=%@ and Detail=%@", cellTitle.text, cellDetail.text);
    NSLog(@"cellForRow.. intended Title=%@, intended Detail=%@", titleText, detailText);
    if (cellDetail == NULL) {
        NSLog(@"cellForRow..; cellDetail = NULL *******************************************");
    }

    // Set default configuration of the cellData UITextField here, and make exceptions later
    cellDetail.placeholder = @"";
    cellDetail.text = @"";
    cellDetail.borderStyle = UITextBorderStyleRoundedRect;
    cellDetail.userInteractionEnabled = NO;
    cell.accessoryType = UITableViewCellAccessoryNone;
    cell.userInteractionEnabled = YES;

    // Configure the cell...
    // based on the data type of the cell's contents

    // Set the cell's editable textField
    if ( [detailText isEqualToString:@"*"] ) {
        cellDetail.text = @"";
        cellDetail.placeholder = detailPlaceholder;
    } else {
        cellDetail.text = detailText;
    }
    cellDetail.keyboardType = UIKeyboardTypeASCIICapable;
    cellDetail.autocapitalizationType = UITextAutocapitalizationTypeWords;

    return cell;
}


#pragma mark - Table view delegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    int rowNumber = indexPath.row;
    [tableView deselectRowAtIndexPath:indexPath animated:NO];
    NSLog(@"didSelectRow.. entered; row=%d, cellData=%@", rowNumber, self.TestData[rowNumber]);
    // The selected cell contains a textField with content to be edited
    // If another cell's textField is currently being edited, save its data before proceding
    UITableViewCell * cell = [tableView cellForRowAtIndexPath:indexPath];
    UITextField * cellDetail = (UITextField *)[cell viewWithTag:99];
    NSLog(@"didSelectRow..: curently editing cell at [%p], selected cell at [%p]", self.fieldBeingEdited, cell);
    // if a textField is still in edit mode, show the Responder status of the cell and all in it
    if ( self.fieldBeingEdited != NULL ) {
        [self showCellResponderStatus:self.cellContainingField];
    }

    if ( self.fieldBeingEdited == NULL ) {
        NSLog(@"didSelectRow..: no field is being edited.");
    } else if ( cellDetail == self.fieldBeingEdited ) {
        // the cell selected is the same one being edited
        if ( ![self.fieldBeingEdited isFirstResponder] ) {
            // fieldBeingEdited is NOT the firstResponder. Try to make it the firstResponder.
            BOOL becameFirstResponder = [self.fieldBeingEdited becomeFirstResponder];
            NSLog(@"didSelectRow..: textField at [%p] returned %d from becomeFirstResponder.", self.fieldBeingEdited, becameFirstResponder);
            [self showCellResponderStatus:self.cellContainingField];
        }
    } else if ( cellDetail != self.fieldBeingEdited ) {
        // the cell selected is NOT the one being edited. Save the edited data and release the keyboard
        NSLog(@"didSelectRow..: field in cell with index=%d is being edited. Text=%@", self.cellIndex, self.fieldBeingEdited.text);
        BOOL resignedFirstResponder;
        [self showCellResponderStatus:self.cellContainingField];
        // This method call will log the responder status of this cell and all it is contained within
        //[self showViewHierarchy:self.fieldBeingEdited];
        resignedFirstResponder = [self.fieldBeingEdited resignFirstResponder];
        NSLog(@"didSelect..: resignFirstResponder on [%p] returned %d", self.fieldBeingEdited, resignedFirstResponder);
        //[self showViewHierarchy:self.fieldBeingEdited];
        if (resignedFirstResponder) {
            self.fieldBeingEdited = NULL;
            self.cellContainingField = NULL;
            self.cellIndex = -1; 
        }
    }

    // Enable the textField within the selected cell for in-place editing
    cellDetail.userInteractionEnabled = YES;
    cellDetail.enabled = YES;
    BOOL becameFirstResponder = [cellDetail becomeFirstResponder];
    NSLog(@"didSelectRow..: becomeFirstResponder returned %d", becameFirstResponder);
    if ( becameFirstResponder ) {
        // Update all the references and indexes for the cell and textField being edited.
        self.fieldBeingEdited = cellDetail;
        self.cellContainingField = cell;
        self.cellIndex = rowNumber;
    }

    NSLog(@"didSelectRow.. exit; textFieldBeingEdited.text=%@, cellIndex=%d", self.fieldBeingEdited.text, self.cellIndex);

}

-(BOOL) textFieldShouldBeginEditing:(UITextField *)textField
{
    NSLog(@"textFieldShouldBeginEditing entered");
    NSLog(@"... returning YES");
    return YES;
}

-(void) textFieldDidEndEditing:(UITextField *)textField
{
    NSLog(@"textFieldDidEndEditing entered with text: %@, cellIndex=%d", textField.text, self.cellIndex);
    NSInteger cellIndex = self.cellIndex;

    NSMutableArray * cellData = [NSMutableArray arrayWithArray:self.TestData[cellIndex]];
    [cellData replaceObjectAtIndex:1 withObject:textField.text];
    [self.TestData replaceObjectAtIndex:cellIndex withObject:cellData];
    textField.enabled = NO;

    [self.tableView reloadData];
    if ( [textField isFirstResponder] ) {
        NSLog(@"EditFlight(466): textFieldDidEndEditing; textField IS STILL the firstresponder!");
    }
    NSLog(@"textFieldDidEndEditing exit; cellData=%@", cellData);
}

-(BOOL) textFieldShouldReturn: (UITextField *)textField
{
    NSLog(@"textFieldShouldReturn; textField.text=%@", textField.text);
    [textField resignFirstResponder];
    BOOL resignedFirstResponder = [textField resignFirstResponder];
    NSLog(@"textFieldShouldReturn; resignFirstResponder returned %d",resignedFirstResponder);
    return YES;
}

-(void) showCellResponderStatus:(UITableViewCell*)cell
{
    NSLog(@"showCellResponderStatus entered");
    if ( [cell isFirstResponder] ) {
        NSLog(@"cell at [%p] IS first responder", cell);
    }else{
        NSLog(@"cell at [%p] IS NOT first responder", cell);
    }
    NSArray * cellSubViews = [[cell contentView] subviews];
    for ( UIView* uiv in cellSubViews ) {
        if ( [uiv isFirstResponder ]) {
            NSLog(@"subview at [%p] is a %@ and IS first responder", uiv, uiv.class);
        } else {
            NSLog(@"subview at [%p] is a %@ and IS NOT first responder", uiv, uiv.class);
        }
    }
}

-(void) showViewHierarchy:(UIView*) uiv
{
    NSLog(@"View Hierarchy");
    while (uiv != NULL) {
        BOOL isfirst = [uiv isFirstResponder];
        NSLog(@"view at [%p] is a %@, isFirstResponder=%d", uiv, uiv.class, isfirst);
        uiv = uiv.superview;
    }
    NSLog(@"view at [%p] is a %@", uiv, uiv.class);
    NSLog(@"End of view hierarchy");
}

@end

Upvotes: 2

Views: 1905

Answers (3)

Matthew Burke
Matthew Burke

Reputation: 2364

There's a much simpler approach. In didSelectRowAtIndexPath store a reference to the current cell's UITextField. Then you can set it as first responder, deal with edits, etc.

When you tap on a new cell to select it, the delegate first receives a didDeselectRowAtIndexPath. Here you can use your UITextField reference can resign first responder, save any necessary state, etc. Then didDeselectRowAtIndexPath is called on the new cell and you're good to go. So something like the following:

Edit: added additional code

// Code additions to Xcode's Master-Detail application template

// Use dynamic prototypes for table cells, custom cell class follows:

// MyCell.h
// Property is auto-synthesized, so there is nothing to see in MyCell.m

@interface MyCell : UITableViewCell
@property (nonatomic, weak) IBOutlet UITextField *tf;
@end



// MasterViewController.m

@interface MasterViewController () {
  NSMutableArray *_objects;
}
@property (nonatomic, weak) UITextField *selectedField;
@end


// this method is called when a cell is DE-selected
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
      [self.selectedField resignFirstResponder];
      // save state, etc if necessary
}


- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    MyCell * cell = (MyCell *)[tableView cellForRowAtIndexPath:indexPath];
    self.selectedField = cell.tf; // cell.tf is a UITextField property defined on MyCell
    [self.selectedField becomeFirstResponder];
}

Upvotes: 1

Sverrisson
Sverrisson

Reputation: 18167

You might ask the first textfield if it is the firstResponder and then change that, before setting the second as firstResponder:

if ([textfield isEditing]) {
            [textfield resignFirstResponder];
}

But first textField will ask it´s delegate before resigning as a first responder by calling:

- (BOOL)textFieldShouldEndEditing:(UITextField *)textField{
}

so check why you are returning NO if you have implemented this method.

Upvotes: 0

Metin Say
Metin Say

Reputation: 435

You may also use an open source as BSKeyboardControls. It also includes other keyboard-textfield relationship that may help you in the procces. s Link for it: https://www.cocoacontrols.com/controls/bskeyboardcontrols

Upvotes: 0

Related Questions