Изменение размера UITableViewCell, содержащего UITextView при вводе текста

У меня есть UITableViewController, который содержит пользовательскую ячейку. Каждая ячейка была создана с помощью пера и содержит один не прокручиваемый UITextView. Я добавил ограничения внутри каждой ячейки, чтобы ячейка адаптировала свою высоту к содержимому UITextView. Итак, изначально мой контроллер выглядит так:

initial state

Теперь я хочу, чтобы, когда пользователь вводил что-то в ячейку, его содержимое автоматически адаптировалось. Этот вопрос задавался много раз, см., В частности, этот или второй ответ здесь. Таким образом, я написал следующий делегат в моем коде:

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

Однако это приводит к следующему странному поведению: все ограничения игнорируются, а высота всех ячеек уменьшается до минимального значения. Смотрите картинку ниже:

wrong behavior

Если я прокручиваю вниз и вверх tableView, чтобы инициировать новый вызов cellForRowAtIndexPath, я восстанавливаю правильные высоты для ячеек:

correct behavior

Обратите внимание, что я не реализовал heightForRowAtIndexPath, так как я ожидаю, что autoLayout позаботится об этом.

Может кто-нибудь сказать мне, что я сделал не так или помочь мне здесь? Большое спасибо !

Ответ 1

Вот быстрое решение, которое отлично работает для меня. Если вы используете автоматическую компоновку, вам нужно назначить значение для оценочного RowHeight, а затем вернуть 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(CGPointMake(0, scrollTo), animated: false)

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

Ответ 2

Мое решение похоже на @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)
}

Ответ 3

Следующий пример работает для динамической высоты строки, поскольку пользователь вводит текст в ячейку. Даже если вы используете автоматическую компоновку, вам все равно придется реализовать метод heightForRowAtIndexPath. Для этого примера рабочие ограничения должны быть установлены в textView таким образом, чтобы при увеличении высоты ячейки textView также увеличивался в высоту. Этого можно достичь, добавив верхнее ограничение и нижнее ограничение от textView к представлению содержимого ячейки. Но не устанавливайте ограничение по высоте для самого textView. Также разрешите прокрутку для textView, чтобы размер содержимого textView обновлялся по мере ввода пользователем текста. Затем мы используем этот размер содержимого для вычисления новой высоты строки. Пока высота строки достаточно длинная, чтобы вертикально растянуть textView равным или большим, чем размер его содержимого, текстовое представление не будет прокручиваться, даже если прокрутка включена, и это то, что вам нужно, я верю.

В этом примере у меня есть только одна строка, и я использую только одну переменную, чтобы отслеживать высоту строки. Но когда у нас есть несколько строк, нам нужна переменная для каждой строки, иначе все строки будут иметь одинаковую высоту. В этом случае может использоваться массив rowHeight, который соответствует массиву источников данных tableView.

@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

Ответ 4

Использование Swift 2.2 (скорее всего, будут работать и более ранние версии), если вы установите TableView для использования автоматических измерений (при условии, что вы работаете в подклассе UITableViewController, например:

    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()
}

Благодаря "Жозе Томи Джозефу" за то, что он вдохновил (давал, действительно) этот ответ.

Ответ 5

Я реализовал аналогичный подход, используя 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, чтобы при изменении высоты 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];
}

Ответ 6

Протестировано на iOS 12

Я действительно попробовал много решений и, наконец, нашел хорошее здесь

Это работает с анимацией и выглядит красиво. Трюк заключался в блоке DispatchQueue.async. Я также использовал TPKeyboardAvoidingTableView чтобы клавиатура ничего не перекрывала.

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

ОБНОВИТЬ

У меня странные проблемы с прыжками из-за TPKeyboardAvoidingTableView. Особенно, когда я прокрутил до конца, а затем UITextView. Поэтому я заменил TPKeyboardAvoidingTableView на собственный UITableView и сам обработал вставки. Табличное представление делает прокрутку изначально.

Ответ 7

Вдохновленный двумя предыдущими ответами, я нашел способ решить мою проблему. Я думаю, что факт, что у меня был 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 - это просто произвольный провал.

Я нашел это решение, читая этот этот очень интересный ответ.

Ответ 8

Трюк, чтобы сразу обновить высоту ячеек таблицы, плавно, не отпуская клавиатуру, - это запустить следующий фрагмент, который будет вызываться в событии textViewDidChange после того, как вы установите размер textView или другого содержимого, которое у вас есть в ячейке:

[tableView beginUpdates];
[tableView endUpdates];

Однако этого может быть недостаточно. Вы также должны убедиться, что tableView обладает достаточной эластичностью, чтобы поддерживать тот же контент. Вы получаете эту эластичность, установив нижнее значение tableView contentInset. Я предлагаю, чтобы это значение эластичности было, по крайней мере, максимальным расстоянием, которое вам нужно, от нижней части последней ячейки до нижней части таблицыView. Например, это может быть высота клавиатуры.

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

Для получения дополнительной информации и некоторых полезных дополнительных функций по этому вопросу, пожалуйста, ознакомьтесь со следующей ссылкой: Изменять размер и перемещать UITableViewCell плавно, не отпуская клавиатуру

Ответ 9

Решение, которое предложили почти все, - это путь, я добавлю лишь незначительное улучшение. Как резюме:

  1. Просто установите предполагаемую высоту, я делаю это через раскадровку: Cell height in storyboard TableView row height estimates

  2. Убедитесь, что у вас правильно установлены ограничения для UITextView в ячейке.

  3. В func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) → UITableViewCell представлении func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) → UITableViewCell

Я просто вызываю: cell.myTextView.sizeToFit()

Ответ 10

Проверьте решение Objective C, которое я предоставил по следующей ссылке ниже. Простота в использовании, очистка и отсутствие необходимости в автоматической компоновке. Никаких ограничений не требуется. Протестировано в iOS10 и iOS11.

Изменять размер и перемещать UITableViewCell плавно, не отбрасывая клавиатуру