UITextView在UITableViewCell中平滑自动调整大小,在iPad上显示和隐藏键盘,但在iPhone上有效。

17

我已经实现了一个自定义的UITableViewCell,其中包含一个UITextView,在用户输入时自动调整大小,类似于“通讯录”应用中的“备注”字段。它在我的iPhone上正常工作,但是在iPad上测试时,出现了一些非常奇怪的行为:当你到达一行的末尾时,键盘会隐藏一毫秒,然后立即再次显示。我本来会将其视为一个小小的程序错误,但它实际上会导致数据丢失,因为如果您正在输入,则会丢失一个或两个字符。这是我的代码:

代码

// returns the proper height/size for the UITextView based on the string it contains.
// If no string, it assumes a space so that it will always have one line.
- (CGSize)textViewSize:(UITextView*)textView {
     float fudgeFactor = 16.0;
     CGSize tallerSize = CGSizeMake(textView.frame.size.width-fudgeFactor, kMaxFieldHeight);
     NSString *testString = @" ";
     if ([textView.text length] > 0) {
          testString = textView.text;
     }
     CGSize stringSize = [testString sizeWithFont:textView.font constrainedToSize:tallerSize lineBreakMode:UILineBreakModeWordWrap];
     return stringSize;
}

// based on the proper text view size, sets the UITextView's frame
- (void) setTextViewSize:(UITextView*)textView {
     CGSize stringSize = [self textViewSize:textView];
     if (stringSize.height != textView.frame.size.height) {
          [textView setFrame:CGRectMake(textView.frame.origin.x,
                                        textView.frame.origin.y,
                                        textView.frame.size.width,
                                        stringSize.height+10)];  // +10 to allow for the space above the text itself 
     }
}

// as per: https://dev59.com/GG865IYBdhLWcg3wkfcW
- (void)textViewDidChange:(UITextView *)textView {

     [self setTextViewSize:textView]; // set proper text view size
     UIView *contentView = textView.superview;
     // (1) the padding above and below the UITextView should each be 6px, so UITextView's
     // height + 12 should equal the height of the UITableViewCell
     // (2) if they are not equal, then update the height of the UITableViewCell
     if ((textView.frame.size.height + 12.0f) != contentView.frame.size.height) {
         [myTableView beginUpdates];
         [myTableView endUpdates];

         [contentView setFrame:CGRectMake(0,
                                          0,
                                          contentView.frame.size.width,
                                          (textView.frame.size.height+12.0f))];
     }
}

- (CGFloat)tableView:(UITableView  *)tableView heightForRowAtIndexPath:(NSIndexPath  *)indexPath {
     int height;
     UITextView *textView = myTextView;
     [self setTextViewSize:textView];
     height = textView.frame.size.height + 12;
     if (height < 44) { // minimum height of 44
          height = 44;
          [textView setFrame:CGRectMake(textView.frame.origin.x,
                                        textView.frame.origin.y,
                                        textView.frame.size.width,
                                        44-12)];
      }
      return (CGFloat)height;
}

这些问题

这里是发生了什么:

  1. 这段代码在我的 iPhone 上和 iPhone 模拟器上完全正常。当我输入文本时,UITextView 会平稳地增长,并伴随着 UITableViewCell 一起。
  2. 然而,在 iPad 模拟器上它变得混乱了。当你输入到一行的末尾时,键盘消失然后立即重新出现,因此如果用户继续输入,应用程序会错过一个或两个字符。
  3. 以下是我注意到的一些其他奇怪行为的附加说明,这可能有助于解释它的原因:
    • 另外,我发现在函数 textViewDidChange:(UITextView *)textView 中删除代码行 [myTableView beginUpdates]; [myTableView endUpdates]; 可以使 UITextView 正常增长,也不会显示和隐藏键盘,但不幸的是,UITableViewCell 的高度不会增长到正确的大小。
    • 更新:遵循这些说明,我现在能够停止文本的奇怪移动;但键盘仍然会隐藏和显示,这非常奇怪。

有没有人有什么想法可以让 iPad 上的键盘继续显示,而不是在一行的末尾隐藏和显示?

P.S.:我不想使用 ThreeTwenty。


你的键盘可能会在动画期间因为文本框首先放弃响应者而显示和隐藏。尝试在xCode中对该方法设置断点(符号断点即可),并运行代码以查看是谁要求其放弃响应者。 - ImHuntingWabbits
你具体指的是哪个动画?UITableView重新绘制自身的动画吗? - Jason
也许问题出在[myTableView beginUpdates]或[myTableView endUpdates],因为当你将它们移除时问题消失了:你能贴上更多的代码吗? - pasine
2个回答

15

在这种情况下,你应该返回 NO:

 -(BOOL) textViewShouldEndEditing:(UITextView *)textView

如果您想始终显示键盘,您应该通过在此委托函数中返回YES来处理应隐藏键盘的情况。

编辑:

我进一步查看了一下,当调用[tableView endUpdates]时,它基本上会执行3个操作:

  1. 禁用tableView上的用户交互
  2. 更新单元格更改
  3. tableView上启用用户交互

SDK(平台)之间的区别在于[UIView setUserInteractionEnabled]方法。因为UITableView没有覆盖setUserInteractionEnabled方法,所以它从超类(UIView)中调用。

iPhone在调用setUserInteractionEnabled时,会查找一个名为_shouldResignFirstResponderWithInteractionDisabled的私有字段,默认返回NO,因此不会取消第一响应者(UITextView)

但是在iPad上,没有这样的检查,所以它在步骤1上取消UITextView,并在步骤3上设置焦点并将其设置为第一个响应者

基本上,根据SDK文档,textViewShouldEndEditing是您当前唯一的选项,它使您可以保持焦点。

当文本视图被要求放弃第一响应者状态时,将调用此方法。这可能发生在用户尝试将编辑焦点更改为另一个控件时。但是,在焦点实际更改之前,文本视图会调用此方法,以使您的委托有机会决定是否应该放弃第一响应者。


这似乎已经起作用了!但是,这还不是全部。通过将 textViewShouldEndEditing 设置为始终返回 NO,那么允许用户关闭键盘的函数就永远不会起作用。因此,我需要在我的视图控制器中设置另一个属性来跟踪 UITextViews 何时应该被允许关闭。这个方法虽然可行,但感觉像是一种 hack。有没有办法真正弄清楚为什么 [UITableView beginUpdates]; [UITableView endUpdates]; 首先会取消 UITextView 的响应? - Jason
其实我猜iPad的行为是正确的,[UITableView beginUpdates]应该放弃第一响应者。但今天我会深入挖掘,看看是否有解决方法。 - Deniz Mert Edincik
更新了我的回答,看起来 textViewShouldEndEditing 是你目前唯一的选择。 - Deniz Mert Edincik
我已经很快地将你的代码片段实现到我的UITableView控制器中,并且没有出现任何问题。我正在iOS 5上运行iPad模拟器,你还在遇到问题吗?我需要发布一些我的代码吗? - Joris Weimar

11

我曾在一个 iPad 应用中遇到了同样的问题,并想出了另一种解决方案,而不必计算文本本身的高度。

首先,在 IB 中创建一个自定义的 UITableViewCell,并将 UITextField 放置在单元格的 contentView 中。重要的是将文本视图的 scrollEnabled 设置为 NO,autoresizingMask 设置为 flexibleWidth 和 flexibleHeight。


在 ViewController 中实现文本视图的委托方法-textViewDidChanged:,其中 textHeight 是一个类型为 CGFloat 的实例变量,-tableViewNeedsToUpdateHeight 是我们将在下一步中定义的自定义方法。

- (void)textViewDidChange:(UITextView *)textView
{
    CGFloat newTextHeight = [textView contentSize].height;

    if (newTextHeight != textHeight)
    {
        textHeight = newTextHeight;
        [self tableViewNeedsToUpdateHeight];
    }
}

-tableViewNeedsToUpdateHeight方法调用了表视图的beginUpdatesendUpdates方法,因此表视图本身会调用-tableView:heightForRowAtIndexPath:代理方法。

- (void)tableViewNeedsToUpdateHeight
{
    BOOL animationsEnabled = [UIView areAnimationsEnabled];
    [UIView setAnimationsEnabled:NO];
    [table beginUpdates];
    [table endUpdates];
    [UIView setAnimationsEnabled:animationsEnabled];
}

在表视图的 -tableView:heightForRowAtIndexPath: 委托方法中,我们需要根据textHeight计算出文字视图单元格的新高度。

首先,我们需要将文本视图单元格的高度调整为最大可用高度(减去表视图中所有其他单元格的高度)。然后我们检查textHeight是否大于计算得出的高度。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{   
    CGFloat heightForRow = 44.0;

    if ([indexPath row] == kRowWithTextViewEmbedded)
    {
        CGFloat tableViewHeight = [tableView bounds].size.height;
        heightForRow = tableViewHeight - ((kYourTableViewsNumberOfRows - 1) * heightForRow);

        if (heightForRow < textHeight) 
        {
            heightForRow = textHeight;
        }
    }

    return heightForRow;
}
为了提供更好的用户体验,将表视图的底部内容插入设置为50.0。我已经在iOS 4.2.1的iPad上进行了测试,效果符合预期。
Florian

1
运行得很好,实际上比Jason和Deniz的解决方案更好。只有当键盘没有立即显示时,我遇到了问题。在这种情况下,当textView被点击并且键盘显示时,tableView会不必要地滚动太远以适应textView过大的高度。我发现我可以始终在-tableview: heightForRowAtIndexPath:中直接返回textHeight。然后,单元格的高度总是与textView一样高。当然,也可以为其定义最小高度。此外,在-viewDidLoad中不要忘记使用合理的值初始化textHeight - Ortwin Gentz
很遗憾,这种方法仅适用于只有一个包含UITextView的单元格。 - Joris Weimar
当然可以!这就是问题的内容:“在UITableViewCell中使用UITextView...” - Florian Mielke
实际上,这对包含多个UITextView的单元格也可以正常工作。你只需要跟踪更多的东西。关键点是tableViewNeedsToUpdateHeight方法,它允许您更改行高而不会失去焦点(因此也不会失去键盘)。 - Julian
1
在你的指示开头,你提到了UITextField。那实际上应该是UITextView,对吗? - Nate Cook

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接