it-swarm.com.ru

Изменение размера 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

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

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

13
vib

Вот решение Swift, которое отлично работает для меня. При условии, что вы используете автоматическую разметку, вам нужно присвоить значениезначению 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.
}
36
atlwx

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

Следующий пример работает для динамической высоты строки, когда пользователь вводит текст в ячейку. Даже если вы используете автоматический макет, вам все равно придется реализовать метод 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
Jose Tomy Joseph

Используя 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()
}

Спасибо «Хосе Томи Джозефу» за то, что вдохновили (действительно помогли) этот ответ. 

3
The AI Architect

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

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

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

0
vib

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

[tableView beginUpdates];
[tableView endUpdates];

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

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

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

0
Mig70

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

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

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

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

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

0
MNassar