0

背景

我有一个名为 _tableView 的带有 UITableView ivar 的类。该类实现了 UITableViewDatasource 和 UITableViewDelegate _tableView。UITableView 单元格是 FormTableCell 的实例。FormTableCell 是 UITableViewCell 的子类,具有 UILabel 和 UITextField。在课堂上,我有一个名为 textFieldDelegate 的属性。当 tableView:cellForIndexPath: 被调用时,当前的 textFieldDelegate 被分配给返回单元格的 textField 属性。此外,当类 textFieldDelegate 被修改时,我想更新单元格文本字段的委托。我想出了三种方法来做到这一点,但不知道最好的方法。

  1. 使用新的 textFieldDelegate 更新每个可见单元格的委托。
  2. 遍历所有单元格并分配新的 textFieldDelegate
  3. 重新加载 tableViews 数据。

选项 1 很好,但它假定 tableView:cellForIndexPath 将在显示不在 tableView 的 visibleCells 数组中的任何单元格之前被调用。如果这没有发生,那么不同的单元格可能有不同的代表,这不是我想要的。

选项 2 是最差的,因为它只适用于少量单元格,我希望 FormTables 总是有少量单元格,但我永远不知道。

如果我假设 tableView:cellForIndexPath: 在显示不可见单元格之前将被调用是无效的并且它胜过选项 2,因为它处理具有大量单元格的表格,则选项 3 胜过选项 1。

问题

哪个是最好的选择,我假设 tableView:cellForIndexPath: 将在不可见的单元格变得可见或与不正确的交互之前被调用?

代码

- (void)setTextFieldDelegate:(id<UITextFieldDelegate>)textFieldDelegate
{
  if(_textFieldDelegate != textFieldDelegate) {
    _textFieldDelegate = textFieldDelegate;


    //Update _textFieldDelegate for all visible cells. 
    //!DEV: Assuming non-visible cells will be recreated with tableView:cellForIndexPath:
    for (FormTableCell *cell in [_tableView visibleCells]) {
      [[cell textField] setDelegate:_textFieldDelegate];
    }

    //!DEV: This will potentially generate memory issues for a large number of cells
    for(NSUInteger i = 0; i < kNumberOfFields; ++i) {
      NSIndexPath *indexPath = [NSIndexPath indexPathForItem:(NSInteger)i inSection:0];
      FormTableCell *cell = (FormTableCell*)[_tableView cellForRowAtIndexPath:indexPath];
      [[cell textField] setDelegate:_textFieldDelegate];
    }

    //!DEV: Maybe this is the best method, but it requires the recreation of visibleCells.
    [_tableView reloadData];

  }
}

最终状态

所以这就是我最终做的。我实现了一个装饰委托(?)

- (void)setTextFieldDelegate:(id<UITextFieldDelegate>)textFieldDelegate
{
  if(_textFieldDelegate != textFieldDelegate) {
    _textFieldDelegate = textFieldDelegate;
  }
}

#pragma mark - UITextFieldDelegate Implementation

//This class forwards UITextFieldDelegate methods on to _textFieldDelegate, if implemented.
//Provides default functionality is not implemented.

- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField
{
  if([_textFieldDelegate respondsToSelector:@selector(textFieldShouldBeginEditing:)]) {
    return [_textFieldDelegate textFieldShouldBeginEditing:textField];
  } //implicit else
  return YES;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField {

  //Setup _closeKeyboardButton behind _tableView
  //view::closeKeyboardButton (_closeKeyboardButton)
  NSAssert(_closeKeyboardButton == nil, @"!DEV: Thought _closeKeyboardButton would always be nil here.");
  if(_closeKeyboardButton != nil) {
    [_closeKeyboardButton removeFromSuperview];
    _closeKeyboardButton == nil;
  }

  CGRect closeKeyboardFrame = {CGPointZero, [self frame].size};

  UIButton *closeKeyboardButton = [UIButton buttonWithType:UIButtonTypeCustom];
  [closeKeyboardButton setFrame:closeKeyboardFrame];

  [closeKeyboardButton addTarget:self
                          action:@selector(_closeKeyboardButtonTapped:)
                forControlEvents:UIControlEventTouchUpInside];

  [self insertSubview:closeKeyboardButton belowSubview:_tableView];
  _closeKeyboardButton = closeKeyboardButton;


  if([_textFieldDelegate respondsToSelector:@selector(textFieldDidBeginEditing:)]) {
    [_textFieldDelegate textFieldDidBeginEditing:textField];
  }
}

- (BOOL)textFieldShouldEndEditing:(UITextField *)textField
{
  if([_textFieldDelegate respondsToSelector:@selector(textFieldShouldEndEditing:)]) {
    return [_textFieldDelegate textFieldShouldEndEditing:textField];
  } //implicit else
  return YES;
}

- (void)textFieldDidEndEditing:(UITextField *)textField
{
  if([_textFieldDelegate respondsToSelector:@selector(textFieldDidBeginEditing:)]) {
    [_textFieldDelegate textFieldDidBeginEditing:textField];
  }

  //Tear down _closeKeyboardButton
  NSAssert(_closeKeyboardButton != nil, @"!DEV: Thought _closeKeyboardButton would always exist here.");
  [_closeKeyboardButton removeFromSuperview];
  _closeKeyboardButton = nil;
}

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
  if([_textFieldDelegate respondsToSelector:@selector(textField:shouldChangeCharactersInRange:replacementString:)]) {
    return [_textFieldDelegate textField:textField shouldChangeCharactersInRange:range replacementString:string];
  } //implicit else
  return YES;
}

- (BOOL)textFieldShouldClear:(UITextField *)textField
{
  if([_textFieldDelegate respondsToSelector:@selector(textFieldShouldClear:)]) {
    return [_textFieldDelegate textFieldShouldClear:textField];
  } //implicit else
  return YES;
}

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
  if([_textFieldDelegate respondsToSelector:@selector(textFieldShouldReturn:)]) {
    return [_textFieldDelegate textFieldShouldReturn:textField];
  } //implicit else
  return NO;
}
4

1 回答 1

0

根据我的经验,永远不需要访问 tableView 的 visibleCells 属性。

在您的 tableView:cellForRowAtIndexPath: 方法中完成所有操作。每次创建单元格时都会调用它,或者从对 dequeReusableCell 的调用中提取回收的单元格......该方法将在任何可见单元格出现在屏幕上之前被调用。

出于教育目的,您可以在所有 tableview 方法中添加一些 NSLog,以查看它们被调用的确切顺序,以及哪些操作导致它们被调用。

无关说明——如果您需要对单元格中的子视图进行任何帧操作,您可以覆盖 FormTableCell 中的 layoutSubviews 方法,或者您可以实现 tableView:willDisplayCell:forRowAtIndexPath: 方法并在那里执行。

于 2012-12-10T18:37:01.547 回答