13

I create a UITableView with different types of UITableViewCell depending on the type of content to display. One of this is a UITableViewCell with inside an UITextView programmatically created in this way:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  ...
  if([current_field.tipo_campo isEqualToString:@"text_area"])
  { 
    NSString *string = current_field.valore;
    CGSize stringSize = [string sizeWithFont:[UIFont boldSystemFontOfSize:15] constrainedToSize:CGSizeMake(320, 9999) lineBreakMode:UILineBreakModeWordWrap];
    CGFloat height = ([string isEqualToString:@""]) ? 30.0f : stringSize.height+10;
    UITextView *textView=[[UITextView alloc] initWithFrame:CGRectMake(5, 5, 290, height)];
    textView.font = [UIFont systemFontOfSize:15.0];
    textView.text = string;
    textView.autoresizingMask =  UIViewAutoresizingFlexibleWidth;
    textView.textColor=[UIColor blackColor];
    textView.delegate = self;
    textView.tag = indexPath.section;
    [cell.contentView addSubview:textView];
    [textView release];   
    return cell;
  }
  ...
}

Since the text view is editable the cell that contains it should change its height to correctly fit the text view sizes. Initially I did this by resizing the UITextView inside the method textViewDidChange:, in this way:

- (void)textViewDidChange:(UITextView *)textView
{
  NSInteger index = textView.tag;
  Field* field = (Field*)[[self sortFields] objectAtIndex:index];
  field.valore = textView.text;
  [self.tableView beginUpdates];
  CGRect frame = textView.frame;
  frame.size.height = textView.contentSize.height;
  textView.frame = frame;
  newHeight = textView.contentSize.height;
  [self.tableView endUpdates];
}

I save the new height of text view in a variable and then when tableView:heightForRowAtIndexPath: method is called, I resize the cell in this way:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
  ...
  if ([current_field.tipo_campo isEqualToString:@"text_area"])
  {
      return newHeight +10.0f;
  }
  else
    return 44.0f;
 ...
}

In this way both are resized but is not done in sync, ie first the TextView is resized and then it is resized the height of the cell, so for an instant the user see that the text view is larger than the cell. How can I fix this bad behavior?

LuckyStarr
  • 1,468
  • 2
  • 26
  • 39
  • For now i find a path setting the uitextview background to clearColor. It is trasparent and the bug is invisible. – LuckyStarr Jan 04 '13 at 00:21
  • Everytime cellForRow... gets called you are allocating,initializing and adding UITextView as a subview. Why not do this setup only once by creating your own UITableViewCell subclass? Seems like the current way you are doing it would cause problems as a result of adding multiple UITextViews to the cell each time that method gets called like for example upon reloadData. – ozz Jan 11 '13 at 09:58
  • @cdo i noticed this error so I modified the code. If cell is nil i create the UITextView otherwise i recover it as a subview of the cell. – LuckyStarr Jan 11 '13 at 15:10

8 Answers8

19

I have created one demo for your problem, hope will help you.

My idea of solution is using AutoResizingMask of UITextView.

My .h file

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController<UITabBarDelegate, UITableViewDataSource, UITextViewDelegate>{
    IBOutlet UITableView *tlbView;
    float height;
}

@end

And my .m file (Includes only required methods)

- (void)viewDidLoad
{
    [super viewDidLoad];

    // Do any additional setup after loading the view, typically from a nib.
    height = 44.0;
}

- (void)textViewDidChange:(UITextView *)textView{
    [tlbView beginUpdates];
    height = textView.contentSize.height;
    [tlbView endUpdates];
}

#pragma mark - TableView datasource & delegates
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return 1;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    if (indexPath.row==0) {
        if (height>44.0) {
            return height + 4.0;
        }
    }
    return 44.0;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"CellIdentifier"];

    UITextView *txtView = [[UITextView alloc] initWithFrame:CGRectMake(0.0, 2.0, 320.0, 40.0)];
    [txtView setDelegate:self];
    [txtView setAutoresizingMask:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight]; // It will automatically resize TextView as cell resizes.
    txtView.backgroundColor = [UIColor yellowColor]; // Just because it is my favourite
    [cell.contentView addSubview:txtView];

    return cell;
}

Hope it will help you out.

Yuvrajsinh
  • 4,536
  • 1
  • 18
  • 32
4

To resize the cells you would use code similar to this

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
    NSString *newText = [textView.text stringByReplacingCharactersInRange:range withString:text];
    CGSize size = // calculate size of new text
CGRect frame = textView.frame;
frame.size.height = textView.contentSize.height;
textView.frame = frame;

    if ((NSInteger)size.height != (NSInteger)[self tableView:nil heightForRowAtIndexPath:nil]) {

        // if new size is different to old size resize cells. 


        [self.tableView beginUpdates];
        [self.tableView endUpdates];
    }
    return YES;
}
Siba Prasad Hota
  • 4,779
  • 1
  • 20
  • 40
2

Set the TextView Frame With An Animation ..so that it syncs with the cell's animation of expanding height

Mohammed Shahid
  • 175
  • 1
  • 10
1

Check this out: UIView Contentmode - play with the values like:

cell.contentMode = //...//
Nirav Bhatt
  • 6,940
  • 5
  • 45
  • 89
  • Do not change textview height, only resize the cell using heightForRowAtIndexPath with any random height. See if textview follows it or not. If yes, modify heightForRowAtIndexPath to change the cell height according to your need. – Nirav Bhatt Jan 06 '13 at 06:38
  • No the UITextView don't change. I tried with textView.autoresizingMask = UIViewAutoresizingFlexibleHeight; end so seems to work: if i change height of cell also that of TextView is changed. – LuckyStarr Jan 09 '13 at 09:20
1
- (void)textViewDidChange:(UITextView *)textView
{
    UITableViewCell *cell = (UITableViewCell*)textView.superview.superview;

    if (cell.frame.size.height < textView.contentSize.height) {
        [self.tableView beginUpdates];
        CGRect frame = textView.frame;
        frame.size.height = textView.contentSize.height;
        textView.frame = frame;
        CGRect cellFrame = cell.frame;
        cellFrame.size.height = textView.frame.size.height;
        cell.frame = cellFrame;
        [self.tableView endUpdates];
    }

}
Warif Akhand Rishi
  • 23,920
  • 8
  • 80
  • 107
1

Siba Prasad Hota's code probably will do the trick (You need reference to table view from cell level), but I have another, longer approach. I always do such stuff in this way, because I like to have all things separated (MVC pattern). If I were You, I would do this like that (code from head):

Cell parent protocol:

@protocol CellParent <NSObject>
@required
@property (nonatomic, strong) UITableView *tableView;
@end

Cell model:

@interface CellModel
@property (nonatomic, assign) BOOL hasTextView;
@property (nonatomic, strong) NSString *textViewContent;
-(float)getCurrentHeightForCell;//implement calculating current height of cell. probably     2 * SOME_MARGIN + height of temporary textView with textViewContent variable

Cell

@interface MyCell
@property (nonatomic, strong) CellModel *dataModel;
@property (nonatomic, weak) id<CellParent> parent;
@property (nonatomic, strong) UITextView *textView;
- (id)initWithStyle:(UITableViewCellStyle)style andModel:(CellModel*) model;

with implementations like this:

(id)initWithStyle:(UITableViewCellStyle)style andModel:(CellModel*) model
{
    self = [super initWithStyle:style reuseIdentifier:@"MyCell"];
    if (self) 
    {
        [[NSBundle mainBundle] loadNibNamed:@"MyCell" owner:self options:nil];
        self.dataModel = model;
    }
    return self;
}


-(void) setDataModel:(CellModel *)dataModel
{
    _dataModel = dataModel;
    if(_dataModel.hasTextView)
    {
        //show text view
    }
    else
    {
        //hide text view
    }
    //do other cell modifications
}

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

Controller with table view

-(UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{  
    MyCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyCell"];
    if (cell == nil) 
    {
        cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault andModel:          [self.cellsModels objectAtIndex:indexPath.row]];
    }
    cell.dataModel = [self.cellsModels objectAtIndex:indexPath.row];
    cell.parent = self;
    return cell;
}

-(CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath     *)indexPath
{
    return [((CellModel*)[self.tableContentArray objectAtIndex:indexPath.row]) getCurrentHeightForCell];
}
Szuwar_Jr
  • 683
  • 8
  • 11
1

You should calculate newHeight for cell before loading cell. Instead of calculating newHeight in textViewDidChange, calculate it in heightForRowAtIndexPath and return same as

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
  if ([current_field.tipo_campo isEqualToString:@"text_area"])
  {
      NSString *string = current_field.valore;
      CGSize stringSize = [string sizeWithFont:[UIFont boldSystemFontOfSize:15] constrainedToSize:CGSizeMake(320, 9999) lineBreakMode:UILineBreakModeWordWrap];
      CGFloat height = ([string isEqualToString:@""]) ? 30.0f : stringSize.height+10;

      return height + 10.0f;
  }
  else
  {
      return 44.0f;
  }
}
Rahul Wakade
  • 4,765
  • 2
  • 23
  • 25
1

I would not bother with cells height using method

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

and rather made your view text field delegate and handle the following event in a way shown below:

- (void) textFieldDidResize:(id)sender
{
          [self.tableView beginUpdates];
          [self.tableView endUpdates];
}

Also make sure that you did the following:

yourInputField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

Then, the only one thing you need is to resize your text field. Your cells in tableview will adopt to the size of inner text field. Just add it as subview to cell.contentView.

Cynichniy Bandera
  • 5,991
  • 2
  • 29
  • 33