43

I would like to have a tablew view with a behaviour similar to the iPhone Contacts app by Apple: a uitableviewcell with a uitextview inside, so that when I write in the uitextview, the uitextview increases its height, and so accordingly the uitableviewcell dynamically adjusts its height. I searched over the whole web, finding only partial solutions and lack of sample code!

please help me I am desperate

Tony

Tony
  • 439
  • 1
  • 4
  • 3

10 Answers10

37

Looking at this,you need to be somewhat tricky. You need to calculate the height of the textView dynamically and based on the Height of the TextView,you need to return the Height for the cell..

It's very easy & somewhat Tricky..

This is the code by which you can calculate the size of string....

First get the size of String

NSString *label =  @"Sample String to get the Size for the textView Will definitely work ";
CGSize stringSize = [label sizeWithFont:[UIFont boldSystemFontOfSize:15]
                      constrainedToSize:CGSizeMake(320, 9999)
                          lineBreakMode:UILineBreakModeWordWrap];

over here ....
NSLog(@"%f",stringSize.height);

Secondly dynamically create the textView in the cell..giving the stringSize.height

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    //if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
    //}

    NSDictionary *d=(NSDictionary *)[self.menuArray objectAtIndex:indexPath.section];

    NSString *string = [d valueForKey:@"Description"];
    CGSize stringSize = [string sizeWithFont:[UIFont boldSystemFontOfSize:15] constrainedToSize:CGSizeMake(320, 9999) lineBreakMode:UILineBreakModeWordWrap];

    UITextView *textV=[[UITextView alloc] initWithFrame:CGRectMake(5, 5, 290, stringSize.height+10)];
    textV.font = [UIFont systemFontOfSize:15.0];
    textV.text=string;
    textV.textColor=[UIColor blackColor];
    textV.editable=NO;
    [cell.contentView addSubview:textV];
    [textV release];

    return cell;
}


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath  {  

    NSDictionary *d=(NSDictionary *)[self.menuArray objectAtIndex:indexPath.section];
    NSString *label =  [d valueForKey:@"Description"];
    CGSize stringSize = [label sizeWithFont:[UIFont boldSystemFontOfSize:15]
                          constrainedToSize:CGSizeMake(320, 9999) 
                              lineBreakMode:UILineBreakModeWordWrap];

    return stringSize.height+25;

} 

After giving so much pain to my fingers,......I think this is enough code...& will surely help to solve your problem..

Good Luck

Ajay Sharma
  • 4,509
  • 3
  • 32
  • 59
  • 1
    this answer is still not complete please complete it – harshalb Jan 19 '11 at 06:26
  • For me it works perfectly....I didn't see anything missing in it. Suggestions for changes are ever welcomed.... – Ajay Sharma Jan 22 '11 at 06:53
  • I think you've accidentally allocated a UILabel and assigned it to the textV variable instead of allocating an actual UITextView. – Shoerob Sep 07 '11 at 21:25
  • 2
    This should really be chosen as the answer, this is a fantastic solution. – Kristian Feb 14 '12 at 07:12
  • @Kristian Thanks for voting up.May be tony won't be aware of accepting the answer.But more important is solving the bugs ;) – Ajay Sharma Feb 14 '12 at 11:43
  • 4
    I don't see how this solution changes the size of the UITableViewCell while the user is entering text. Am I missing something? – Daniel T. Mar 06 '12 at 22:58
  • @DanielT. This isn't for the Dynamic cells while you are entering type in TextField.This solution is for result strings.Rather than this, still you can use the same method.But the same solution would alos work, if reload the TableView while user is entering the text. – Ajay Sharma Mar 07 '12 at 04:23
  • One thing to note, is UITextView has insets of 8 on top and sides. I believe 32 on the bottom. This appeared to throw off the size calculation a little when compared to UILabel which has no inset. I got around it by using self.myTextView.contentInset = UIEdgeInsetsMake(-8, -8, -8, -32); – DenVog Apr 20 '12 at 17:16
  • 2
    @AjaySharma, how do you reload the tableview from within a tableviewcell? you haven't answered the question, which is for when the user is entering text. Should it use delegation or notifications? – Jonathan. May 26 '12 at 00:40
  • @Jonathan. for this I think reloading the TableView will be quite cumbersome action, better approach would be reloading the single cell of the TableView using - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation; – Ajay Sharma Sep 18 '12 at 09:18
  • @AjaySharma, to the point made by @DanielT. I think if you look at Tom Swift's answer (specifically the part about `[tableView beginUpdates]`, which you can call from the `UITextView` delegate method `textViewDidChange:`), it will effectively complete your answer (meeting the requester's requirements). The combination is what ultimately resolved the issue I was having (same as Tony's but for a section header with a `UITextView` instead of a `UITableViewCell`). – Matt Wagner Nov 25 '12 at 22:55
  • I want an editable textview in tableviewcell, how can I achieve this? – Yogesh Maheshwari Nov 26 '12 at 18:40
  • @YogeshMaheshwari There is already TextView .. simply textV.editable=YES; to make it editable and it will work.Rest you need to code to store all these values. – Ajay Sharma Dec 05 '12 at 04:17
  • @AjaySharma Simply making it editable doesn't refresh the tableview, I over-rided textviewdidchange method and updated the table in there to refresh the TableView – Yogesh Maheshwari Dec 05 '12 at 06:48
  • 1
    Just a quick note for iOS 6, `UILineBreakModeWordWrap` was deprecated. For iOS6+, you should use `NSLineBreakByWordWrapping` – acedanger Apr 08 '13 at 15:23
  • Works like a charm! In combination with TomSwift his answer it works perfectly! – P Griep Sep 08 '14 at 11:59
  • sizeWithFont is deprecated from iOS 7. – Ajumal May 29 '16 at 12:12
11

Create a custom UITableViewCell and add your UITextView to the cell's contentView.

In LayoutSubviews, set textView.frame to the cell's contentView.bounds (or do some other custom layout).

When the textView contents change (discovered via UITextViewDelegate), do two things:

1) call [tableView beginUpdates]; [tableView endUpdates]; This will force the table view to recalculate the height for all cells (will call tableView:heightForRowAtIndexPath:). If your cell is the delegate for the textView then you'll have to figure out how to get a pointer to the tableView, but there are a few ways to achieve that. Or you could make the delegate your view controller...

2) when tableView:heightForRowAtIndexPath: is called for this cell, return the textView.contentSize.height. You can get your cell from here by calling [tableView cellForRowAtIndexPath]; Or if you just have one of these cells then cache a pointer to it in your viewController.

Hiren
  • 12,720
  • 7
  • 52
  • 72
TomSwift
  • 39,369
  • 12
  • 121
  • 149
3

The only issue I found with the accepted answer was that we are allocating the UITextView each and every time. I found that this raised issues with typing into the view and having the text updating immediately and also keeping the view as first responder. When I tried to reload the cell with the new height it would then try to add a new textView.

Because of this I found a slightly different method to achieve the same goal. Hopefully this different take might help people who are struggling to implement the above code.

1) In the header file define a variable for the height of the text and the textView:

UITextView * _textView;
NSInteger _textHeight;

Setting a variable means that we can load the view to be a certain height if we are loading text into the textView and also reduces the complexity.

2) Load the text view and add it to our cell

_textView = [[UITextView alloc] init];
_textView.delegate = self;
_textView.returnKeyType = UIReturnKeyDone;
_textView.font = [UIFont fontWithName:@"Helvetica" size:16];

if (![_user metaStringForKey:bBioKey].length) {
    _textView.text = @"Placeholder text";
    _textView.textColor = [UIColor lightGrayColor];
}


- (UITableViewCell *)tableView:(UITableView *)tableView_ cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    if (indexPath == 0) { // Add logic to choose the correct cell

        if (_textView.superview != cell) {

            [cell addSubview:_textView];     

            _textView.keepTopInset.equal = KeepRequired(7);
            _textView.keepBottomInset.equal = KeepRequired(7);
            _textView.keepRightInset.equal = KeepRequired(10);
            _textView.keepLeftInset.equal = KeepRequired(70);
            }
        }
    }
}

Using keeplayout has enabled us to keep our textfield to always stay the same height as the cell. We are also only ever adding our UITextView once.

3) Add the code to calculate the height of the text

- (NSInteger)getHeightOfBio: (NSString *)text {

    UILabel * gettingSizeLabel = [[UILabel alloc] init];
    gettingSizeLabel.font = [UIFont fontWithName:@"Helvetica" size:16];
    gettingSizeLabel.text = text;
    gettingSizeLabel.numberOfLines = 0;
    CGSize maximumLabelSize = CGSizeMake(240, 9999); // this width will be as per your requirement

    CGSize expectedSize = [gettingSizeLabel sizeThatFits:maximumLabelSize];

    return expectedSize.height;
}

I have tried many and found this to work the best

4) Add some logic in the cell height to make use of this:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

    if (indexPath.row == 0) { // Set the height for our changing textView

        return 30 + [self getHeightOfBio:_textView.text];
    }

    return 44;
}

Obvious we need a bit more height than our text height so I added an extra cushion amount which can be experimented with.

5) Refresh the view each time a character is typed to check if we need to increase the size:

- (void)textViewDidChange:(UITextView *)textView {

    if ([self getHeightOfText:textView.text] != _textHeight) {

       _textHeight = [self getHeightOfText:textView.text];

       [self.tableView beginUpdates];
       [self.tableView endUpdates];
    }
}

In this section we get the height of the text each time the user types.

Then though we use our stored value and compare the current value to the stored value. Obviously if they are the same then there is no point in refreshing the view. If they are different we update the value and then refresh our table.

This bit I found a good answer on stackOverflow showing how we can refresh only the heights of the table instead of the cell itself. Why refresh the cell when we don't need to? This means that once this is called the cell height is updated and it increases nicely.

Anyway I found this worked really nicely and was simple enough that it can be put together or have different parts taken and put into other peoples pieces of code.

Props to the accepted answer which was pillaged for various pieces along the way but I also hope that this answer helps some people who are having the same difficulties that I had.

sam_smith
  • 6,023
  • 3
  • 43
  • 60
1

Finally I got it working. The main problem is how to get cell's contentView correct width. Hardcoded width does not work for all cases since it may vary accordingly plain/grouped table style, added accessories, or landscape/portrait layout. The only way to get 100% correct width is to ask it from cell object. So I create cell right in heightForRowAtIndexPath and store it in cache, then this cached cell will be returned by cellForRowAtIndexPath method. Another problem is how to force cell to layout its subviews before it is used. It can be done if we temporary add cell to the tableView and update cell's frame with tabelView's width. After that all subviews will be layouted in the right way. Here is how I got it working in my TableKit library.

onegray
  • 5,105
  • 1
  • 23
  • 29
1

I've written up my learnings and solution for calculating the UITableViewCell height based on an inner UITextView on my blog. The post contains the code that works for universal apps, both table view styles and autorotation.

d11n
  • 997
  • 1
  • 8
  • 7
1

I edit TomSwift response which is the best way to do it :

here is my cell code (in swift)

class CommentaireTextViewCell: UITableViewCell,UITextViewDelegate {

@IBOutlet weak var textViewCom: UITextView!
weak var parentTableView:UITableView?

@IBOutlet weak var constraintTextViewTopMargin: NSLayoutConstraint!
@IBOutlet weak var constraintTextViewHeight: NSLayoutConstraint!
@IBOutlet weak var constraintTextViewBottomMargin: NSLayoutConstraint!
override func awakeFromNib() {
    self.textViewCom.delegate = self;
}

func textViewDidChange(textView: UITextView) {
    self.parentTableView?.beginUpdates();
    self.parentTableView?.endUpdates();
}

func getHeight() -> CGFloat
{
    constraintTextViewHeight.constant = self.textViewCom.contentSize.height;
    // cell height = textview marge top+ textview height+ textView Marge bottom
    return constraintTextViewTopMargin.constant+constraintTextViewHeight.constant+constraintTextViewBottomMargin.constant+8
    // add 8 because it seems to have an inset inside the textView
}

override func setSelected(selected: Bool, animated: Bool) {
    self.textViewCom.becomeFirstResponder();
}
}

for the explaination, the cell table view is a weak propertie of the cell so I can tell it to update layout when user enter text. The height of the cell is the sum of its top constraint to the contentView, its bottom constraint to the contentView and the textView.contantSize.height which is also equal to the textView constant height

enter image description here

Vassily
  • 899
  • 2
  • 8
  • 19
0

Try the following code:

CGSize constraintSize;
constraintSize.height = MAXFLOAT;
constraintSize.width = yourTextView.frame.size.width;
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                                      [UIFont fontWithName:@"yourFontName" size:yourFontSize], NSFontAttributeName,
                                      nil];

CGRect frame = [yourTextView.text boundingRectWithSize:constraintSize
                                  options:NSStringDrawingUsesLineFragmentOrigin
                               attributes:attributesDictionary
                                  context:nil];

CGSize stringSize = frame.size;//The string size can be set to the UITableViewCell
Sujith Thankachan
  • 3,508
  • 2
  • 20
  • 25
0

You need to return the correct height in the heightForRowAtIndexPath delegate method.

Rengers
  • 14,911
  • 1
  • 36
  • 54
  • Yes I know that, but I don't know how to implement the "autoresize" of the contentview and of the cell itself :-( Do you have some sample code? thanks. Tony – Tony Nov 21 '10 at 17:04
  • Ah I see. I dont have any sample code. You could try to reload only that cell when the text updates, so the tableview recalculates the height. It might cause trouble in that the text field resigns first responder though. – Rengers Nov 21 '10 at 19:07
-1

you can get the UITextView size programmatically.According to the size,set the height of the cell using the following delegate

tableView:heightForRowAtIndexPath:
EXC_BAD_ACCESS
  • 2,699
  • 2
  • 21
  • 25
-3

Guys this is really cool stuff but you can use tableview:didSelectRowForIndexPath: to add the UITextView to the cell as it's subview when the user taps it, works very well since you need to use only 1 UITextView which you can reuse and won't interfere with the tableview receiving touches! Release it when done.

Details? just ask!

Nutela
  • 85
  • 7