21
votes

I having the separate custom UITableViewCell for displaying the data(these data come from server JSON response).In each UITableViewCell i am having button as read more.If the user clicks read more button i want to programmatically add UILabel for displaying additional information from server.But initially i set UITableViewCell height so after clicking read more button i cant able to see the additional inforamtion UILabel..

This is the screen shot:

enter image description here

This is my required screen:

enter image description here

This is the following coding i used:

-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{

   int height1;
    if(readMore){
        height1=200;
        NSLog(@"Clicked");
    }
    else{
        height1=100;
        NSLog(@"Not clicked");
    }
    return height1; // Normal height
}


-(NSInteger) tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section
{
    return [TitleArr  count];
}


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

    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
    {
        static NSString *simpleTableIdentifier = @"SimpleTableCell_iPad";

        cell = (TableCell_Leads *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
    }
    else{
        static NSString *simpleTableIdentifier = @"TableCell_Leads";

        cell = (TableCell_Leads *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
    }
    if (cell == nil)
    {
        if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
        {
            NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"SimpleTableCell_iPad" owner:self options:nil];
            cell = [nib objectAtIndex:0];

        }
        else{
            NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"TableCell_Leads" owner:self options:nil];
            cell = [nib objectAtIndex:0];
        }
    }




    cell.labTitle.text = [TitleArr objectAtIndex:indexPath.row];

    cell.labCategory.text=[CategoryArr objectAtIndex:indexPath.row];

    [cell.btnReadmore addTarget:self
                         action:@selector(funReadmore:)
               forControlEvents:UIControlEventTouchUpInside];


    return cell;
}



 - (IBAction)funReadmore:(id)sender
    {
        [self.tableView beginUpdates];
        readMore=TRUE;



        NSLog(@"READ MORE");
        [self.tableView endUpdates];
}
6
Kindly give me suggestions..Its a very urgent need..Kalai
see my sample project..Shankar BS

6 Answers

26
votes

First of all take a bool & int variable.

BOOL isReadMoreButtonTouched = NO;
int indexOfReadMoreButton = -1;

Then Implement below with your code

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    [[cell btnReadmore] setTag:[indexPath row]];

    if(isReadMoreButtonTouched && [indexPath row]== indexOfReadMoreButton)
    {
       // design your read more label here
    }
}

Now implement IBAction

-(IBAction) funReadmore:(id)sender
{
    UIButton *readMoreButton = (UIButton *)sender;
    indexOfReadMoreButton=[readMoreButton tag];
    isReadMoreButtonTouched=YES;

    [[self tableView] beginUpdates];
    [[self tableView] reloadRowsAtIndexPaths:@[[NSIndexPath indexPathForItem: indexOfReadMoreButton inSection:0]] withRowAnimation:UITableViewRowAnimationAutomatic];
    [[self tableView] endUpdates];
}

Now Come to heightForRowAtIndexPath

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if(isReadMoreButtonTouched && [indexPath row]== indexOfReadMoreButton) return 200.0f;
    else return 100.0f;
}

Hope it'll work for you.

4
votes

Take a int readMoreAtIndex; as your class variable. Initialize it with a negative value like -1 in init method and/or viewDidLoad/viewWillAppear. Some basic logic would be like this:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{   
    if(readMoreAtIndex == indexPath.row) {
        return 400; //return as per your requirement
    }
    return 100;
}

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

    //same lines as currently you are doing to setup cell.     

    //important line
    [cell.btnReadmore setTag:indexPath.row];

    [cell.btnReadmore addTarget:self
                         action:@selector(funReadmore:)
               forControlEvents:UIControlEventTouchUpInside];

    if(indexPath.row == readMoreAtIndex) {
        //setup your cell according to your logic to show expanded view
    }
    else {
        //you are reusing cells, so provide logic to disappear shown expanded view if you want
    }

    return cell;
}

- (IBAction)funReadmore:(id)sender
{     
    UIButton *button = (UIButton *)sender;
    readMoreAtIndex = button.tag;
    [yourTableView reloadData];
    NSLog(@"READ MORE");
}

EDIT: Links for tutorials to implement expandable/collapsable tableview.

  1. Expanding/Collapsing TableView Sections
  2. Collapsable Table View for iOS
2
votes

I found another solution based on self-sizing table view cells. Instead of updating cell's height (hardcoded) we can update the constraints priority.

fileprivate extension Int {
   static let rowHeight = 175
}

class CellArticleData {
  var article: Article
  var isExpanded: Bool

  init(article: Article, isExpanded: Bool) {
    self.article = article
    self.isExpanded = isExpanded
  }
}

 enum Article: String {
    case medicine, sport
    static let allArticles: [Article] = [.medicine, .sport]
    var title: String { return self.rawValue.capitalized }
    var description: String {
        switch self {
          case .medicine:
            return "Lorem Ipsum is simply dummy text of the printing and 
            typesetting industry"
          case .sport:
            return "Contrary to popular belief, Lorem Ipsum is not simply 
            random text. It has roots in a piece of classical Latin 
            literature from 45 BC, making it over 2000 years old. Richard 
            McClintock, a Latin professor at Hampden-Sydney College in 
            Virginia."
      }
   }
}

class ViewController: UIViewController {

@IBOutlet weak var tableView: UITableView!
var articles: [CellArticleData] = Article.allArticles.map { CellArticleData(article: $0, isExpanded: false) }

override func viewDidLoad() {
    super.viewDidLoad()

    tableView.dataSource = self
    tableView.delegate = self
    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = CGFloat(.rowHeight)
}

 extension ViewController: UITableViewDataSource {

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath) as! MyCell
    let articleData = articles[indexPath.row]
    cell.setup(articleData: articleData)
    cell.delegate = self
    return cell
  }
}

extension ViewController: MyCellDelegate {

  func handleReadMore() {
      tableView.reloadData()
   }
}

I have a custom class that represent a cell "MyCell" which handles the protocol and constraints updates:

protocol MyCellDelegate {
    func handleReadMore()
}

class MyCell: UITableViewCell {

   @IBOutlet weak var topConstraint: NSLayoutConstraint!
   @IBOutlet weak var bottomConstraint: NSLayoutConstraint!
   @IBOutlet weak var heightConstraint: NSLayoutConstraint!

  func setup(articleData: CellArticleData) {
    self.articleData = articleData

    titleLabel.text = articleData.article.title
    descriptionLabel.text = articleData.article.description
    readMoreLabel.isUserInteractionEnabled = true

    let readMoreTap = UITapGestureRecognizer(target: self, action: #selector(handleReadMore))
    readMoreTap.cancelsTouchesInView = false
    readMoreLabel.addGestureRecognizer(readMoreTap)
    updateCellConstraints()
}

fileprivate func updateCellConstraints() {
    if let articleData = self.articleData {
        if !articleData.isExpanded {
            heightConstraint.priority = 999
            topConstraint.priority = 250
            bottomConstraint.priority = 250
        }else {
            heightConstraint.priority = 250
            topConstraint.priority = 999
            bottomConstraint.priority = 999
        }

    }
  }

   func handleReadMore() {
      if let articleData = self.articleData {
         articleData.isExpanded = !articleData.isExpanded
         delegate?.handleReadMore(articleData: articleData)
     }
   }
}

Here is an example showing how it looks like: My custom cell MyCell

1
votes

You need to put some kind of flag mechanism and manage the height in

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

The best and ideal way is to calculate the height according to the text and then return the height

 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{       
    if(readMore){
      return 500;
     }else{
      return 100;
     }
}

If you are using autolayout then you can calculate the size of each labels manually according to content by using sizeToFit method

1
votes

I would suggest you to follow these steps:

In custom cell the contents that will be available to you, put it inside a hidden UIView container. So it is not visible by default.

  1. When read more button presses, handle its event trigger inside the class that draws tableView as you are doing it funReadmore handler.

  2. Take the index of cell and manage/add it in NSMutableArray object.

  3. Reload TableView data using:

[yourTableViewInstance reloadData];
  1. In heightForRowAtIndexPath delegate function, write it like this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if(arrayOfExpandedCellIndexes.contains(indexPath.row))    
         return EXTENDED_CELL_HEIGHT;   // Macro : #define EXTENDED_CELL_HEIGHT 230.0f 
    else 
         return NORMAL_CELL_HEIGHT;     // Macro : #define NORMAL_CELL_HEIGHT   100.0f 
}

Using this way you can handle more than one cell with Read More button pressed. If in your requirement only one cell can be expand clear your arrayOfExpandedCellIndexes using:

[arrayOfExpandedCellIndexes removeAllObjects];

NOTE: Once height is adjusted for a cell don't forget to make the hidden view visible.

Hope it helps!

1
votes

I am posting the sample code that will expand cell based on button click and the text size works for both iOS6 and iOS 7, this is just the sample code, just go through this this may helps u ... :)

this is just a sample project that u can try


     in customCell.h
     #import <UIKit/UIKit.h>
     @class CustomCell;
     @protocol ButtonClickDelegate <NSObject> //custom delegate
     - (void)whenReadMoreButtonClicked:(CustomCell *)cell;//i am passing the cell itself
     @end

     @interface CustomCell : UITableViewCell
     @property (nonatomic,assign)id<ButtonClickDelegate>delegate;
    @property (nonatomic,retain)UILabel *mesageLabel;
    @property (nonatomic,retain)NSString *message;
    @property (nonatomic,assign)BOOL expand;

    @end


in customCell.m


    #import "CustomCell.h"

    @implementation CustomCell
    @synthesize delegate;//synthesize it
    @synthesize mesageLabel;
    @synthesize message;
    @synthesize expand;


    - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
   {
      self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
      if (self) {
     // Initialization code
      UIButton *button = [[UIButton alloc]initWithFrame:CGRectMake(5,2, 100, 35)];
      [button addTarget:self action:@selector(whenButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
      [button setTitle:@"Read More" forState:UIControlStateNormal];
       button.backgroundColor = [UIColor greenColor];

      self.mesageLabel = [[UILabel alloc]initWithFrame:CGRectMake(0 , 40,0 ,0)];
       self.mesageLabel.backgroundColor = [UIColor redColor];
      self.mesageLabel.numberOfLines = 100;
      [self addSubview:self.mesageLabel];
       [self addSubview:button];
    }
     return self;
   }

  - (void)setSelected:(BOOL)selected animated:(BOOL)animated
  {
       [super setSelected:selected animated:animated];

      // Configure the view for the selected state
  }

   - (void)whenButtonClicked:(id)sender
   {
       if([self.delegate respondsToSelector:@selector(whenReadMoreButtonClicked:)])
       {
          [self.delegate whenReadMoreButtonClicked:self];//delegate to controller
       }

  }

  - (void)layoutSubviews
  {
       [super layoutSubviews];
      self.mesageLabel.text = self.message;
      if(self.expand)
      {
          CGSize size = [self findMessgeStringHeight];
          self.mesageLabel.frame = CGRectMake(0, 40, size.width, size.height);
      }
      else
      {
          self.mesageLabel.frame = CGRectMake(0, 40, self.bounds.size.width, 100);
      }

  }


   //helper method to find height
    - (CGSize)findMessgeStringHeight
     {  
         NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:self.message attributes:@{ NSFontAttributeName:[UIFont systemFontOfSize:17.0f] }];
        CGRect rect = [attributedText boundingRectWithSize:(CGSize){225, MAXFLOAT}
                                           options:NSStringDrawingUsesLineFragmentOrigin
                                           context:nil];
        CGSize requiredSize = rect.size;

         return requiredSize; //finally u return your height
     } 

     @end


in viewController

      #import "ViewController.h"
      #import "CustomCell.h"

      @interface ViewController ( <UITableViewDataSource,UITableViewDelegate,ButtonClickDelegate>//confirm's to delegate
      {

        BOOL ButtonClickedForExpand;
        NSMutableArray *array;

     }

     @property (nonatomic,retain)NSIndexPath *previousIndexPath;
      @property (nonatomic,retain)NSIndexPath *currentIndexPath;

     @end

    @implementation ViewController
     @synthesize previousIndexPath;
    @synthesize currentIndexPath;

    - (void)viewDidLoad
     {
         [super viewDidLoad];
        ButtonClickedForExpand = NO;
// Do any additional setup after loading the view, typically from a nib.
      array = [[NSMutableArray alloc]initWithObjects:@"hello happy coding some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext",@"some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext",@"ello happy coding some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext ello happy coding some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext ello happy coding some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext ello happy coding some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext ello happy coding some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtext some longtextsome longtext some longtext", nil];
      }

    - (void)didReceiveMemoryWarning
    {
       [super didReceiveMemoryWarning];
      // Dispose of any resources that can be recreated. 
    }

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
   {
       return 1;
   }

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
   {
      return array.count;
   }
     - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  { 
      CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:@"CELL"];
     if(cell == nil)
      {
        cell = [[CustomCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"CELL"];
      }

     if(ButtonClickedForExpand)
     {
        if(indexPath.row == currentIndexPath.row)
        {
           cell.expand = YES;
        }
        else
       {
          cell.expand = NO;
       }
    }
    else
   {
       cell.expand = NO;
   }

    cell.message = [array objectAtIndex:indexPath.row];
    cell.delegate = self;//u need to set delegate to self
    return cell;
  }

   - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  {
      CGSize size = [self findMessgeStringHeight:[array objectAtIndex:indexPath.row]];
     if(ButtonClickedForExpand)
      {
         if(indexPath.row == currentIndexPath.row)
         {
             return size.height + 30;
         }
        else
        {
           return 100;//by default
        }
    }
    else
     {
         return 100;
     }

  } 

    //helper function to return the correct height for your label
  - (CGSize)findMessgeStringHeight:(NSString *)str
  {

     NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:str attributes:@{ NSFontAttributeName:[UIFont systemFontOfSize:17.0f] }];
     CGRect rect = [attributedText boundingRectWithSize:(CGSize){225, MAXFLOAT}
                                           options:NSStringDrawingUsesLineFragmentOrigin
                                           context:nil];
     CGSize requiredSize = rect.size;

     return requiredSize; //finally u return your height
  }



  - (void)whenReadMoreButtonClicked:(CustomCell *)cell
 {
       ButtonClickedForExpand = YES;
       self.previousIndexPath = self.currentIndexPath;
      self.currentIndexPath = [self.tableView indexPathForCell:cell];


       [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:self.currentIndexPath] withRowAnimation:UITableViewRowAnimationFade];

    if(self.previousIndexPath.row == nil)
     { 
      return;
     }
     else
     {
       [self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:self.previousIndexPath] withRowAnimation:UITableViewRowAnimationFade];
      }

   }


   @end


EDIT:ADDED ButtonClickedForExpand to for first click

EDIT:2 changed if(self.previousIndexPath.row == nil) in "whenReadMoreButtonClicked" method of view controller

Comment if u don't get