在 iOS 中,如何在两个单元格中的文本字段之间切换?

Posted

技术标签:

【中文标题】在 iOS 中,如何在两个单元格中的文本字段之间切换?【英文标题】:in iOS, how can I switch between text fields in two cells? 【发布时间】:2013-09-02 21:46:43 【问题描述】:

我有一个使用自定义单元格的 TableView 的应用程序,每个单元格都包含一个文本字段。当用户点击一个单元格时,我想将相应的 textField 置于编辑模式。如果另一个单元格的 textField 已经处于编辑模式,我需要让它 resignFirstResponder 以便我可以保存它的数据,然后使新单元格的 textField 成为 FirstResponder。但是当我在新单元格的 textField 上调用 becomeFirstResponder 时,它返回一个零,表示失败。这似乎是一种混乱的处理方式,但我还没有找到更好的方法。

这是我的 TableViewController 文件的全文,其中去除了很多杂乱无章的内容:

//  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

【问题讨论】:

编辑格式以强调什么是代码 【参考方案1】:

有一个更简单的方法。在didSelectRowAtIndexPath 中存储对当前单元格的 UITextField 的引用。然后您可以将其设置为第一响应者,处理编辑等。

当您点击一个新单元格以选择它时,代理首先会收到一个didDeselectRowAtIndexPath。在这里,您可以使用您的 UITextField 引用可以辞职第一响应者,保存任何必要的状态等。然后在新单元格上调用 didDeselectRowAtIndexPath,您就可以开始了。所以类似于以下内容:

编辑:添加额外代码

// 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];

【讨论】:

我相信这正是我正在做的。在我的代码中(在问题中),有一个名为 fieldBeingEdited 的私有属性,这是您建议的参考。尝试使新选择的单元格的 textField 成为FirstResponder 时出现我的问题 - 它不会。 是的,如果我的回答不清楚,请见谅。我的主要观点是,我没有尝试在didSelectRowAtIndexPath 中执行所有逻辑,而是在“didDeselectRow ...”和“didSelectRow ...”中将其分为两个步骤。我的猜测是你的函数中存在一些逻辑错误,而不是尝试调试一个大而复杂的方法,我建议改为编写两个简单的方法。无论如何,我有一个允许您切换单元格的工作项目,如果您愿意,请告诉我,我将编辑我的答案以显示更多代码。 我很想看到一个可行的例子。我对您的回答感到困惑的是,您似乎有两种名称相同的方法——这怎么可能?也许你的代码会清除它。 再次查看列表。方法是 didSelect 和 did-DE-select。【参考方案2】:

您也可以使用开源作为 BSKeyboardControls。它还包括其他可能对您有所帮助的键盘-文本字段关系。 s 链接:https://www.cocoacontrols.com/controls/bskeyboardcontrols

【讨论】:

感谢您的建议和链接。我希望能够以任何顺序选择单元格,而不仅仅是下一个和上一个,但如果我找不到使这项工作的方法,我将回到 BSKeyboardControls。【参考方案3】:

您可能会询问第一个 textfield 是否是 firstResponder,然后更改它,然后将第二个设置为 firstResponder

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

但首先textField 会在辞去第一响应者的职务之前询问其代表:

- (BOOL)textFieldShouldEndEditing:(UITextField *)textField

如果你已经实现了这个方法,请检查你为什么返回NO

【讨论】:

我在第一个 textField 上调用 resignFirstResponder 并确认它在调用第二个 textField 上的 becomeFirstResponder 之前返回了 1。文档说新的 textField 必须在响应者链中,我认为这是因为它是同一 TableView 的不同单元格中的 textField。 @A.Baker - 您可以通过调用 [textfield becomeFirstResponder] 来告诉第二个 textField 成为响应者;您也可以要求 nextResponder。所有这些都是通过检查文本字段继承自的 UIResponder 类来获得的。 @A.Baker - 您可以通过退出所有文本字段然后将 becomeFirstResponder 发送给选定的文本字段来解决此问题。还有一件更重要的事情要记住,当您回收 UITableViewCells 时,您不会从 UITableViewCells 用于的最后一个单元格在前一个文本字段之上添加新的文本字段。 @A.Baker - 如果您以编程方式创建它,还要确保 textField 的 editAble 属性设置为 YES。 我相信我已经涵盖了所有基础,除了在所有 textFields 上退出 First Responder。这将需要一些额外的工作来将所有文本字段的列表保留在 TableView 的可重用单元格列表中。不过,在我这样做之前,当我告诉当前 textField resignFirstResponder 时,其他一些控件是否会自动成为 FirstResponder?如果是这样,我如何找到它并让它辞职,以便我可以让我选择的 textField 成为 FirstResponder?显然响应链对我来说仍然很神秘,所以请帮助我。

以上是关于在 iOS 中,如何在两个单元格中的文本字段之间切换?的主要内容,如果未能解决你的问题,请参考以下文章

iOS:如何在代码中的表格单元格中为文本字段添加约束

如何在编辑表格视图单元格中的文本字段时重新加载所有表格视图单元格 - iOS Swift

iOS:如何从 tableview 的自定义单元格中的 UITextField 访问值

iOS7上tableview单元格中的文本字段崩溃

表格视图单元格中的标签和文本字段之间的空间相等。

在excel单元格中的复杂分隔符之间提取文本