Resize a UITableViewCell containing a UITextView as you type

I have a UITableViewController that contains a custom cell. Each cell was created with a pen and contains one non-scrollable UITextView. I have added restrictions within each cell so that the cell adapts its height to the contents of the UITextView. So, initially my controller looks like this:

initial state

Now I want that when the user enters something into the cell, its contents automatically adapt. This question has been asked many times, see, In particular, this or the second answer here . So I wrote the following delegate in my code:

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

However, this leads to the following strange behavior: all restrictions are ignored, and the height of all cells is reduced to the minimum value. See the picture below:

wrong behavior

If I scroll down and up the tableView to initiate a new call to cellForRowAtIndexPath, I restore the correct heights for the cells:

correct behavior

Note that I have not implemented heightForRowAtIndexPath, since I expect autoLayout to take care of this.

Can someone tell me what I did wrong or help me here? Thank you very much !

+18
source share
10 answers

Here is a quick solution that works great for me. If you use auto-layout, you need to assign a value to the estimated RowHeight, and then return a UITableViewAutomaticDimension for the row height. Finally, do something similar below in the text view delegate.

 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. } 
+41
source

My solution is similar to @atlwx, but a little shorter. Tested with a static table. UIView.setAnimationsEnabled(false) necessary to prevent β€œjumping” of the cell contents, while updating the table increases the cell height

 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) } 
+10
source

The following example works for dynamic row height as the user types text into a cell. Even if you use auto-layout, you still have to implement the heightForRowAtIndexPath method. For this example, working limits should be set in textView so that as the cell height increases, the textView also grows in height. This can be achieved by adding an upper limit and a lower limit from textView to the cell content view. But do not set a height limit for the textView itself. Also enable scrolling for textView so that the size of the contents of the textView is updated as the user enters the text. Then we use this content size to calculate the new row height. As long as the line height is long enough to vertically stretch the textView equal to or larger than the size of its contents, the text view will not scroll even if the scroll is on, and that is what you need, I believe.

In this example, I have only one row, and I use only one variable to track the height of the row. But when we have several lines, we need a variable for each line, otherwise all lines will have the same height. In this case, the rowHeight array can be used, which corresponds to the array of tableView data sources.

 @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
source

Using Swift 2.2 (most likely, earlier versions will work) if you install TableView to use automatic measurements (provided that you are working in a subclass of UITableViewController , for example:

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

You just need to implement the delegate in this UITextViewDelegate file and add the function below, and it should work. Just remember to point the textView delegate to self (so maybe after you selected the cell, 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() } 

Thanks to Jose Tomi Joseph for inspiring (really giving) this answer.

+3
source

I implemented a similar approach using a UITextView, however, for this I had to implement 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 is an instance of a cell that is used only for size calculation.

It is important to note that you need to attach the upper and lower edges of the UITextView to the upper and lower edges of the contents of the UITableViewCells so that when you change the height of the UITableViewCell, the UITextView also changes in height.

I use PureLayout ( https://github.com/smileyborg/PureLayout ) to compose the constraints, so the following constraint layout code might be unusual for you:

 #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
source

Tested on iOS 12

I really tried many solutions and finally found a good one here

It works with animation and looks beautiful. The trick was in the DispatchQueue.async block. I also used TPKeyboardAvoidingTableView so that the keyboard would not overlap anything.

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

UPDATE

I have weird jumping problems due to TPKeyboardAvoidingTableView . Especially when I scroll to the end, and then a UITextView . So I replaced TPKeyboardAvoidingTableView with my own UITableView and handled the inserts myself. The table view does the scrolling initially.

+1
source

Inspired by the two previous answers, I found a way to solve my problem. I think the fact that I had a UITextView was causing some problems with autoLayout. I added the following two functions to my source code.

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

where in the last line 31 is the sum of my restrictions on the left and right sides of the cell, and 20 is just an arbitrary failure.

I found this solution by reading this this very interesting answer .

0
source

The trick to immediately update the cell height of the table, smoothly, without releasing the keyboard, is to run the following fragment, which will be called in the textViewDidChange event after you set the size of the textView or other content that you have in the cell:

 [tableView beginUpdates]; [tableView endUpdates]; 

However, this may not be enough. You must also make sure that the tableView is flexible enough to support the same content. You get this elasticity by setting the bottom value of tableView contentInset. I suggest that this elasticity value be at least the maximum distance you need from the bottom of the last cell to the bottom of the View table. For example, it could be the height of the keyboard.

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

For more information and some useful additional features on this subject, please see the following link: Resize and move the UITableViewCell smoothly without releasing the keyboard

0
source

The solution that almost everyone has proposed is the way to go; I will add only a slight improvement. As a summary:

  1. Just set the estimated height, I do this through the storyboard: Cell height in storyboard Tableview row height estimates

  2. Make sure that you have the UITextView restrictions set correctly in the cell.

  3. In func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) β†’ UITableViewCell view func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) β†’ UITableViewCell

I just call: cell.myTextView.sizeToFit()

0
source

Check out the Objective-C solution I provided at the following link below. Easy to use, clean and no need for automatic layout. No restrictions are required. Tested on iOS10 and iOS11.

Resize and move the UITableViewCell smoothly without dropping the keyboard

-2
source

All Articles