【问题标题】:Resize UITableViewCell containing UITextView upon typing键入时调整包含 UITextView 的 UITableViewCell 的大小
【发布时间】:2015-10-14 05:46:20
【问题描述】:

我有一个包含自定义单元格的 UITableViewController。每个单元格都是使用 nib 创建的,并包含一个不可滚动的 UITextView。我在每个单元格内添加了约束,以便单元格使其高度适应 UITextView 的内容。所以最初我的控制器看起来像这样:

现在我希望当用户在单元格中键入内容时,其内容会自动适应。这个问题已经被问过很多次了,尤其是thisthe second answer here。因此,我在我的代码中编写了以下委托:

- (BOOL) textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString*)text {
    [self.tableView beginUpdates];
    [self.tableView endUpdates];
    return YES;
}

但是它会导致以下奇怪的行为:所有约束都被忽略并且所有单元格高度都崩溃到最小值。见下图:

如果我向下和向上滚动 tableView 以强制调用 cellForRowAtIndexPath,我会恢复单元格的正确高度:

请注意,我没有实现 heightForRowAtIndexPath,因为我希望 autoLayout 能够解决这个问题。

有人可以告诉我我做错了什么或在这里帮助我吗?非常感谢!

【问题讨论】:

标签: ios uitableview cocoa-touch autolayout uitextview


【解决方案1】:

这是一个适合我的快速解决方案。如果您使用自动布局,则需要为estimatedRowHeight 分配一个值,然后返回UITableViewAutomaticDimension 作为行高。最后在文本视图委托中执行类似于下面的操作。

override func viewDidLoad() {
    super.viewDidLoad()
    self.tableView.estimatedRowHeight = 44.0
}

override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return UITableViewAutomaticDimension
}

// MARK: UITextViewDelegate
func textViewDidChange(textView: UITextView) {

    // Calculate if the text view will change height, then only force 
    // the table to update if it does.  Also disable animations to
    // prevent "jankiness".

    let startHeight = textView.frame.size.height
    let calcHeight = textView.sizeThatFits(textView.frame.size).height  //iOS 8+ only

    if startHeight != calcHeight {

        UIView.setAnimationsEnabled(false) // Disable animations
        self.tableView.beginUpdates()
        self.tableView.endUpdates()

        // Might need to insert additional stuff here if scrolls
        // table in an unexpected way.  This scrolls to the bottom
        // of the table. (Though you might need something more
        // complicated if editing in the middle.)

        let scrollTo = self.tableView.contentSize.height - self.tableView.frame.size.height
        self.tableView.setContentOffset(CGPoint(x: 0, y: scrollTo), animated: false)

        UIView.setAnimationsEnabled(true)  // Re-enable animations.
}

【讨论】:

  • 将 Swift 3+ 的 CGPointMake(0, scrollTo) 更改为 CGPoint(x: 0, y: scrollTo) ;)
  • 这终于对我有用了,但是,我的“滚动”功能出现了故障,不得不将其删除(每当单元格尽可能向下滚动时,添加或删除一个字符将触发一个新行会使 tableView 偏离屏幕,添加/删除一个字符会修复它)。
  • 我无法完成这项工作,直到我发现我必须禁用 textview 的滚动。默认情况下,它已启用并返回固有内容大小。如果您可以将此作为注释添加到答案中,那就太好了
【解决方案2】:

以前beginUpdates/endUpdates 是宣传的解决方案。

从 iOS 11 开始,performBatchUpdates 是推荐的 source

对单元格内容进行更改后调用 performBatchUpdates 对我有用。

【讨论】:

    【解决方案3】:

    在 iOS 12 上测试

    我真的尝试了很多的解决方案,终于找到了一个好的here

    这适用于动画并且看起来很漂亮。诀窍是DispatchQueue.async 块。 我还使用TPKeyboardAvoidingTableView 来确保键盘不重叠任何东西。

    func textViewDidChange(_ textView: UITextView) {
        // Animated height update
        DispatchQueue.main.async {
            self.tableView?.beginUpdates()
            self.tableView?.endUpdates()
        }        
    }
    

    更新

    由于TPKeyboardAvoidingTableView,我遇到了奇怪的跳跃问题。特别是当我滚动到底部然后UITextView 激活时。 所以我用原生的UITableView 替换了TPKeyboardAvoidingTableView 并自己处理插图。表格视图是本机滚动的。

    【讨论】:

    • 我几天来一直在努力寻找解决这个“跳跃”问题的方法......非常感谢这个有用的答案并指向那篇文章。它有助于理解问题。
    • 这对我有用。它包含一个很好的扩展动画。谢谢。
    【解决方案4】:

    几乎每个人都建议的解决方案是要走的路,我只会添加一个小的改进。回顾一下:

    1. 只需设置估计高度,我通过情节提要完成:

    2. 确保您在单元格中正确设置了 UITextView 的约束。

    3. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell

    我只是调用: cell.myTextView.sizeToFit()

    【讨论】:

      【解决方案5】:

      在不关闭键盘的情况下以平滑的方式立即更新 tableview 单元格高度的技巧是在设置 textView 的大小或您在细胞:

      [tableView beginUpdates];
      [tableView endUpdates];
      

      但是这可能还不够。您还应该确保 tableView 有足够的弹性来保持相同的 contentOffset。您可以通过设置 tableView contentInset 底部来获得这种弹性。我建议这个弹性值至少是从最后一个单元格底部到 tableView 底部所需的最大距离。例如,它可能是键盘的高度。

      self.tableView.contentInset = UIEdgeInsetsMake(0, 0, keyboardHeight, 0);
      

      有关此问题的更多详细信息和一些有用的额外功能,请查看以下链接: Resize and move UITableViewCell smoothly without dismissing keyboard

      【讨论】:

        【解决方案6】:

        查看我在以下链接中提供的 Objective C 解决方案。 易于实施,清洁,无需自动布局。不需要约束。在 iOS10 和 iOS11 中测试。

        Resize and move UITableViewCell smoothly without dismissing keyboard

        【讨论】:

        • 虽然这可能会回答问题,但it would be preferable 在此处包含答案的基本部分,并提供链接以供参考。
        • 对不起,我是新来的。
        • 没问题。请随时edit您的问题进行改进。
        【解决方案7】:

        我的解决方案类似于@atlwx,但要短一些。用静态表测试。需要UIView.setAnimationsEnabled(false) 以防止在表格更新该单元格的高度时单元格的内容“跳跃”

        override func viewDidLoad() {
            super.viewDidLoad()
            self.tableView.estimatedRowHeight = 44.0
        }
        
        override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
            return UITableViewAutomaticDimension
        }
        
        func textViewDidChange(_ textView: UITextView) {
            UIView.setAnimationsEnabled(false)
            textView.sizeToFit()
            self.tableView.beginUpdates()
            self.tableView.endUpdates()
            UIView.setAnimationsEnabled(true)
        }
        

        【讨论】:

        • 非常优雅、漂亮的解决方案。谢谢。
        【解决方案8】:

        如果您将 TableView 设置为使用自动尺寸(假设您在子类 UITableViewController 中工作,则使用 Swift 2.2(早期版本可能也可以),如下所示:

            self.tableView.rowHeight = UITableViewAutomaticDimension
            self.tableView.estimatedRowHeight = 50 // or something
        

        你只需要在UITextViewDelegate这个文件中实现委托,并添加下面的函数,它应该可以工作。请记住将 textView 的委托设置为 self(因此,也许在您将单元格出列之后,cell.myTextView.delegate = self

        func textViewDidChange(textView: UITextView) {
            self.tableView.beginUpdates()
            textView.frame = CGRectMake(textView.frame.minX, textView.frame.minY, textView.frame.width, textView.contentSize.height + 40)
            self.tableView.endUpdates()
        }
        

        感谢“Jose Tomy Joseph”启发(确实)这个答案。

        【讨论】:

          【解决方案9】:

          受前两个答案的启发,我找到了解决问题的方法。我认为我有一个 UITextView 的事实给 autoLayout 带来了一些麻烦。我在原始代码中添加了以下两个函数。

          - (CGFloat)textViewHeightForAttributedText: (NSAttributedString*)text andWidth: (CGFloat)width {
              UITextView *calculationView = [[UITextView alloc] init];
              [calculationView setAttributedText:text];
              CGSize size = [calculationView sizeThatFits:CGSizeMake(width, FLT_MAX)];
              return size.height;
          }
          
          
          - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
              UIFont *font = [UIFont systemFontOfSize:14.0];
              NSDictionary *attrsDictionary = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
              NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:self.sampleStrings[indexPath.row] attributes:attrsDictionary];
              return [self textViewHeightForAttributedText:attrString andWidth:CGRectGetWidth(self.tableView.bounds)-31]+20;
          }
          

          在最后一行中,31 是我对单元格左右两侧的约束之和,20 只是一些随意的松弛。

          我在阅读此this very interesting answer 时找到了此解决方案。

          【讨论】:

            【解决方案10】:

            我已经使用 UITextView 实现了类似的方法,但是为此我必须实现 heightForRowAtIndexPath

            #pragma mark - SizingCell
            
            - (USNTextViewTableViewCell *)sizingCell
            {
                if (!_sizingCell)
                {
                    _sizingCell = [[USNTextViewTableViewCell alloc] initWithFrame:CGRectMake(0.0f,
                                                                                             0.0f,
                                                                                             self.tableView.frame.size.width,
                                                                                             0.0f)];
                }
            
                return _sizingCell;
            }
            
            #pragma mark - UITableViewDelegate
            
            - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
            {
                self.sizingCell.textView.text = self.profileUpdate.bio;
            
                [self.sizingCell setNeedsUpdateConstraints];
                [self.sizingCell updateConstraintsIfNeeded];
            
                [self.sizingCell setNeedsLayout];
                [self.sizingCell layoutIfNeeded];
            
                CGSize cellSize = [self.sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
            
                return cellSize.height;
            }
            

            sizingCell 是单元格的一个实例,仅用于调整大小计算。

            需要注意的是,需要将 UITextView 的上下边缘附加到 UITableViewCells contentView 的上下边缘,这样随着 UITableViewCell 高度的变化,UITextView 的高度也会发生变化。

            对于约束布局,我使用 PureLayout (https://github.com/smileyborg/PureLayout),因此以下约束布局代码对您来说可能不常见:

            #pragma mark - Init
            
            - (id)initWithStyle:(UITableViewCellStyle)style
                reuseIdentifier:(NSString *)reuseIdentifier
            {
                self = [super initWithStyle:style
                            reuseIdentifier:reuseIdentifier];
            
                if (self)
                {
                    [self.contentView addSubview:self.textView];
                }
            
                return self;
            }
            
            #pragma mark - AutoLayout
            
            - (void)updateConstraints
            {
                [super updateConstraints];
            
                /*-------------*/
            
                [self.textView autoPinEdgeToSuperviewEdge:ALEdgeLeft
                                                withInset:10.0f];
            
                [self.textView autoPinEdgeToSuperviewEdge:ALEdgeTop
                                                withInset:5.0f];
            
                [self.textView autoPinEdgeToSuperviewEdge:ALEdgeBottom
                                                withInset:5.0f];
            
                [self.textView autoSetDimension:ALDimensionWidth
                                         toSize:200.0f];
            }
            

            【讨论】:

            • 感谢您的回答,您能告诉我更多 sizingCell 的来源吗?我猜您省略了将大小调整单元格的内容设置为要更新的单元格之一的行?谢谢
            • @vib 啊,我不小心错过了 - 我已经更新了答案以包含 sizingCell 属性以及我设置 textview 内容的位置
            • 我试图根据我的情况调整你的代码,但 heightForRowAtIndexPath 总是给我相同的高度,与我放入单元格的内容无关。我不知道我可能做错了什么......我还查看了这个链接useyourloaf.com/blog/2014/02/14/…,这与你的建议相似,但这对我没有帮助
            • 感谢您的帮助。我终于找到了答案。我将其作为单独的答案发布,因为它与您的建议完全无关。 ios的这个autoLayout部分用起来不是很方便……
            【解决方案11】:

            以下示例适用于用户在单元格中键入文本时的动态行高。即使您使用自动布局,您仍然必须实现 heightForRowAtIndexPath 方法。为了使这个示例工作,必须将约束设置为 textView,这样如果单元格高度增加,textView 的高度也会增加。这可以通过将 textView 的顶部约束和底部约束添加到单元格内容视图来实现。但不要为 textView 本身设置高度约束。还为 textView 启用滚动,以便在用户输入文本时更新 textView 的内容大小。然后我们使用这个内容大小来计算新的行高。只要行高足够长以垂直拉伸 textView 等于或大于其内容大小,即使启用了滚动,文本视图也不会滚动,我相信这就是您所需要的。

            在这个例子中,我只有一行,并且我只使用一个变量来跟踪行高。但是当我们有多行时,我们需要为每一行设置一个变量,否则所有行都将具有相同的高度。在这种情况下,可以使用与 tableView 数据源数组对应的 rowHeight 数组。

            @interface ViewController ()
            
            @property (nonatomic, assign)CGFloat rowHeight;;
            @property (weak, nonatomic) IBOutlet UITableView *tableView;
            
            @end
            
            @implementation ViewController
            
            - (void)viewDidLoad {
                [super viewDidLoad];
                self.rowHeight = 60;
            }
            
            #pragma mark - UITableViewDataSource
            
            - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
                return 1;
            }
            
            - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
                UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell1"];
                return cell;
            }
            
            #pragma mark - UITableViewDelegate
            
            - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
                return self.rowHeight;
            }
            
            
            #pragma mark - UITextViewDelegate
            
            - (void)textViewDidChange:(UITextView *)textView {
                [self.tableView beginUpdates];
                CGFloat paddingForTextView = 40; //Padding varies depending on your cell design
                self.rowHeight = textView.contentSize.height + paddingForTextView;
                [self.tableView endUpdates];
            }
            
            @end
            

            【讨论】:

            • 谢谢,这部分工作,但如果填充不够大,我仍然可以在单元格内滚动一点,我发现这种方法在来回旋转设备时会出现一些奇怪的行为。我会等着看我是否得到其他答案...
            • 我没有使用自动布局。这个解决方案对我有用吗?
            猜你喜欢
            • 1970-01-01
            • 2014-01-10
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多