I want to make this type of expandable/collapsible table view.
there are categories and subcategories as in picture.
for example "health and beauty" is a category and when i click this cell than its open subcategories as in picture below.
So how can I make this type of table view?
please suggest me.
Finally i get two very useful helping link below which describes exact what the requirement is here
Expanding/Collapsing TableView Sections
Collapsable Table View for iOS
Really, good articles for such kind of expanding/collapsing tableview sections
Use Following code for expandable Cell into UITableView
- (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];
}
cell.textLabel.text=[[self.arForTable objectAtIndex:indexPath.row] valueForKey:#"name"];
[cell setIndentationLevel:[[[self.arForTable objectAtIndex:indexPath.row] valueForKey:#"level"] intValue]];
return cell;
}
code for expanding & collapsing rows – TableView DidSelectRow Method
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSDictionary *d=[self.arForTable objectAtIndex:indexPath.row];
if([d valueForKey:#"Objects"]) {
NSArray *ar=[d valueForKey:#"Objects"];
BOOL isAlreadyInserted=NO;
for(NSDictionary *dInner in ar ){
NSInteger index=[self.arForTable indexOfObjectIdenticalTo:dInner];
isAlreadyInserted=(index>0 && index!=NSIntegerMax);
if(isAlreadyInserted) break;
}
if(isAlreadyInserted) {
[self miniMizeThisRows:ar];
} else {
NSUInteger count=indexPath.row+1;
NSMutableArray *arCells=[NSMutableArray array];
for(NSDictionary *dInner in ar ) {
[arCells addObject:[NSIndexPath indexPathForRow:count inSection:0]];
[self.arForTable insertObject:dInner atIndex:count++];
}
[tableView insertRowsAtIndexPaths:arCells withRowAnimation:UITableViewRowAnimationLeft];
}
}
}
A Method which will help to minimize & maximize/expand-collapse rows.
-(void)miniMizeThisRows:(NSArray*)ar{
for(NSDictionary *dInner in ar ) {
NSUInteger indexToRemove=[self.arForTable indexOfObjectIdenticalTo:dInner];
NSArray *arInner=[dInner valueForKey:#"Objects"];
if(arInner && [arInner count]>0){
[self miniMizeThisRows:arInner];
}
if([self.arForTable indexOfObjectIdenticalTo:dInner]!=NSNotFound) {
[self.arForTable removeObjectIdenticalTo:dInner];
[self.tableView deleteRowsAtIndexPaths:
[NSArray arrayWithObject:[NSIndexPath indexPathForRow:indexToRemove inSection:0]]
withRowAnimation:UITableViewRowAnimationRight];
}
}
}
You can download the source code from my tutorial site.
If this helps: [Access uitableview's expandable and collapsable sections] https://github.com/OliverLetterer/UIExpandableTableView
I have a little bit of a different approach to expandable table views - one that aligns with how these kinds of table views are generally built.
There are headers and there are cells. Headers should be tappable, and then cells underneath the headers would show or hide. This can be achieved by adding a gesture recognizer to the header, and when tapped, you just remove all of the cells underneath that header (the section), and viceversa (add cells). Of course, you have to maintain state of which headers are "open" and which headers are "closed."
This is nice for a couple of reasons:
The job of headers and cells are separated which makes code cleaner.
This method flows nicely with how table views are built (headers and cells) and, therefore, there isn't much magic - the code is simply removing or adding cells, and should be compatible with later versions of iOS.
I made a very simple library to achieve this. As long as your table view is set up with UITableView section headers and cells, all you have to do is subclass the tableview and the header. Try it :)
Link: https://github.com/fuzz-productions/FZAccordionTableView
Try Using this code... May be this can help..
And Feel free to Edit the code according to your requirements...
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#interface ViewController ()
#end
#implementation ViewController
#synthesize myTable;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//myTable.backgroundColor=[UIColor clearColor];
// self.view.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:#"wood.png"]];
muArr= [[NSMutableArray alloc]initWithObjects:#"Vinay",#"Anmol",#"Jagriti", nil];
ExpArr=[[NSMutableArray alloc]initWithObjects:#"Useeee",#"Thissss",#"Codeee", nil];
otherExpand=100;
checker=100;
}
-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
return muArr.count;
}
-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(otherExpand==section)
return ExpArr.count;
return 0;
}
-(BOOL)tableView:(UITableView *)table canCollapse:(NSIndexPath *)indexPath
{
return NO;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *Identifier=#"Cell";
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:Identifier];
if (cell==nil)
{
cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:Identifier];
}
cell.textLabel.text=[ExpArr objectAtIndex:indexPath.row];
cell.textLabel.backgroundColor=[UIColor clearColor];
UIView *viewww=[[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
viewww.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:#"wood.png"]];
cell.backgroundView=viewww;
// cell.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:#"wood.png"]];
[tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLineEtched];
[tableView setSeparatorColor:[UIColor purpleColor]];
return cell;
}
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *view1=[[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 44)];
[view1.layer setCornerRadius:20];
view1.layer.borderWidth=2;
view1.layer.borderColor=[UIColor brownColor].CGColor;
UILabel *label=[[UILabel alloc]initWithFrame:CGRectMake(10, 0, 295, 44)];
label.backgroundColor=[UIColor clearColor];
label.text=[muArr objectAtIndex:section];
UIButton *btn=[UIButton buttonWithType:UIButtonTypeDetailDisclosure];
btn.frame=CGRectMake(280, -5, 50, 50);
btn.backgroundColor=[UIColor clearColor];
btn.tag=section;
view1.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:#"wood.png"]];
label.textColor=[UIColor blackColor];
label.font=[UIFont fontWithName:#"American TypeWriter" size:18];
//btn.backgroundColor=[UIColor blackColor];
[view1 addSubview:btn];
[view1 addSubview:label];
[btn addTarget:self action:#selector(Btntap:) forControlEvents:UIControlEventTouchUpInside];
return view1;
}
-(void)Btntap : (UIButton *)btn
{
if(otherExpand!=100)
{
if (otherExpand==btn.tag)
{
NSMutableArray *tempArr2=[[NSMutableArray alloc]init];
for(int j=0;j<ExpArr.count;j++)
{
NSIndexPath *indexx1=[NSIndexPath indexPathForRow:j inSection:otherExpand];
[tempArr2 addObject:indexx1];
}
checker=0;
otherExpand=100;
[myTable deleteRowsAtIndexPaths:tempArr2 withRowAnimation:UITableViewRowAnimationAutomatic];
}
else
{
NSMutableArray *tempArr2=[[NSMutableArray alloc]init];
for(int j=0;j<ExpArr.count;j++)
{
NSIndexPath *indexx1=[NSIndexPath indexPathForRow:j inSection:otherExpand];
[tempArr2 addObject:indexx1];
}
checker=1;
otherExpand=100;
[myTable deleteRowsAtIndexPaths:tempArr2 withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
if(checker!=0)
{
otherExpand=btn.tag;
//checker=
NSMutableArray *tempArr=[[NSMutableArray alloc]init];
for(int i=0;i<ExpArr.count;i++)
{
NSIndexPath *indexx=[NSIndexPath indexPathForRow:i inSection:btn.tag];
[tempArr addObject:indexx];
}
[myTable insertRowsAtIndexPaths:tempArr withRowAnimation:UITableViewRowAnimationAutomatic];
checker=1;
}
checker=100;
}
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 44;
}
#end
There is a great video in WWDC 2011 called UITableView Changes, Tips and Tricks - session 125 that shows how to do things like this.
Also check out the example code TVAnimationsGestures
You may take a look at this accordion example in Swift: https://github.com/tadija/AEAccordion
It's got very little code to create accordion effect (not by using sections but cells), and as a bonus there is also a solution to use XIB files inside other XIB files (useful for custom cells which use custom views).
Please try this example :
best example for Expandable TableView
https://github.com/OliverLetterer/UIExpandableTableView
TLIndexPathTools can do this sort of thing naturally. In fact, there is are extensions for both expandable sections and expandable tree structures. Try running the Collapse sample project for expandable sections and the Outline sample project for expandable trees.
One advantage of using TLIndexPathTools is that, as a simple, low-level API, it can solve all kinds of dynamic table view and collection view problems using a common approach. And it works interchangeably with Core Data and plain arrays.
it is so easy to create an expandable tableview
here is an example how I did this,
data I m using for this one
struct ItemList {
var name: String
var items: [String]
var collapsed: Bool
init(name: String, items: [String], collapsed: Bool = false) {
self.name = name
self.items = items
self.collapsed = collapsed
}
}
var sections = [ItemList]()
var items: [ItemList] = [
ItemList(name: "Mac", items: ["MacBook", "MacBook Air"]),
ItemList(name: "iPad", items: ["iPad Pro", "iPad Air 2"]),
ItemList(name: "iPhone", items: ["iPhone 7", "iPhone 6"])
]
now just add this piece of code and use accordingly
extension ViewController:UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerHeading = UILabel(frame: CGRect(x: 5, y: 10, width: self.view.frame.width, height: 40))
let imageView = UIImageView(frame: CGRect(x: self.view.frame.width - 30, y: 20, width: 20, height: 20))
if items[section].collapsed{
imageView.image = UIImage(named: "collapsed")
}else{
imageView.image = UIImage(named: "expand")
}
let headerView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 60))
let tapGuesture = UITapGestureRecognizer(target: self, action: #selector(headerViewTapped))
tapGuesture.numberOfTapsRequired = 1
headerView.addGestureRecognizer(tapGuesture)
headerView.backgroundColor = UIColor.red
headerView.tag = section
headerHeading.text = items[section].name
headerHeading.textColor = .white
headerView.addSubview(headerHeading)
headerView.addSubview(imageView)
return headerView
}
func numberOfSections(in tableView: UITableView) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let itms = items[section]
return !itms.collapsed ? 0 : itms.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! UITableViewCell
cell.textLabel?.text = items[indexPath.section].items[indexPath.row]
return cell
}
#objc func headerViewTapped(tapped:UITapGestureRecognizer){
print(tapped.view?.tag)
if items[tapped.view!.tag].collapsed == true{
items[tapped.view!.tag].collapsed = false
}else{
items[tapped.view!.tag].collapsed = true
}
if let imView = tapped.view?.subviews[1] as? UIImageView{
if imView.isKind(of: UIImageView.self){
if items[tapped.view!.tag].collapsed{
imView.image = UIImage(named: "collapsed")
}else{
imView.image = UIImage(named: "expand")
}
}
}
tableView.reloadData()
}
}
and the result is Bingo :)
I had the requirement of expanding a single cell to a fuller view and collapsing it back to a summarised view.
So what I did was to design my cell using UIStackView. And I kept the view I didn't want to show in the collapsed state hidden and then showing it when the cell was tapped.
The trick here is to show and hide the view within tableView.beginUpdates() and tableView.endUpdates() statements. In this way table view automatically adjusts the cell height and does it animatedly.
Here is how a basic cell would look in the IB:
Cells Custom Class:
class AccordionCell: UITableViewCell {
#IBOutlet weak var stackView: UIStackView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var descriptionLabel: UILabel!
#IBOutlet weak var extendedDescriptionLabel: UILabel!
var expanded: Bool = false {
didSet {
if let extended = self.extendedDescriptionLabel {
extended.isHidden = !expanded
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.expanded = false
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
UITableView Delegate Implementation:
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier) as! AccordionCell
cell.titleLabel.text = "Row: \(indexPath.row)"
cell.expanded = indexPath.row == expanded
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? AccordionCell {
tableView.beginUpdates()
if expanded == indexPath.row {
cell.expanded = false
expanded = -1
}
else {
cell.expanded = true
expanded = indexPath.row
}
tableView.endUpdates()
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? AccordionCell {
tableView.beginUpdates()
cell.expanded = false
tableView.endUpdates()
}
}
}
In order to keep track which cell is expanded, I introduced a variable saving indexpath of currently expanded cell so that the right cell is expanded when tableview is scrolled.
Check this Link :
http://iostechnotips.blogspot.in/2014/05/expandable-uitableview.html
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
*Use UITableView delegate method viewForHeaderInSection and return a custom UIView.
*Add a UIButton as subview with action "expandable:(id)sender" check the sender id as section number and reload the table view.
In your .h file
LoadCustomCell *cell1;
NSMutableArray *arrayForBool;
NSMutableArray *questionArray;
NSMutableArray *answerArray;
In your .m file
viewDidLoadMethod {
_faqTblView.estimatedRowHeight = 30;
_faqTblView.rowHeight = UITableViewAutomaticDimension;
arrayForBool = [[NSMutableArray alloc]init];
_questionArray = [[NSMutableArray alloc]init];
_answerArray = [[NSMutableArray alloc]init];
for (int i = 0; i < _questionArray.count; i++) {
[arrayForBool addObject:#"0"];
}
self.faqTblView.dataSource = self;
self.faqTblView .delegate = self;
[self.faqTblView reloadData];
}
after that
#pragma mark - TableView Datasource & Delegate Method.
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [_questionArray count];
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
UILabel *lblText = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 260, 100)];
lblText.text = [_questionArray objectAtIndex:section];
return [lblText getLabelHeight] + 20;(created custom class)
}
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UITapGestureRecognizer *headerTapped = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(sectionHeaderTapped:)];
cell1 = [[[NSBundle mainBundle] loadNibNamed:#"LoadCustomCell" owner:self options:nil] objectAtIndex:0];
[cell1 setFrame:CGRectMake(0, 0, cell1.frame.size.width, cell1.frame.size.height)];
NSString *numStr = [NSString stringWithFormat:#"%ld. ",section + 1];
[cell1.sideMenuUserNameLabel setText:[numStr stringByAppendingString:[_questionArray objectAtIndex:section]]];
[cell1 setBackgroundColor:[UIColor lightGrayColor]];
cell1.tag = section;
[cell1 addGestureRecognizer:headerTapped];
return cell1;
}
- (void)sectionHeaderTapped:(UITapGestureRecognizer *)gestureRecognizer {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:gestureRecognizer.view.tag];
if (indexPath.row == 0) {
BOOL collapsed = [[arrayForBool objectAtIndex:indexPath.section] boolValue];
for (int i = 0; i < [_questionArray count]; i++) {
if (indexPath.section==i) {
[arrayForBool removeObjectAtIndex:i];
[arrayForBool insertObject:[NSString stringWithFormat:#"%d", !collapsed] atIndex:i];
}
}
NSLog(#"%#", arrayForBool);
[self.faqTblView reloadSections:[NSIndexSet indexSetWithIndex:gestureRecognizer.view.tag] withRowAnimation:UITableViewRowAnimationAutomatic];
for (NSIndexPath *indexPath in self.faqTblView.indexPathsForSelectedRows) {
[self.faqTblView deselectRowAtIndexPath:indexPath animated:NO];
}
cell1.imageView.transform = CGAffineTransformMakeRotation(M_PI);
}
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *questionCellIdentifier = #"questionCellIdentifier";
QuestionCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:questionCellIdentifier];
if (cell == nil) {
NSArray * myNib;
myNib =[[NSBundle mainBundle]loadNibNamed:#"QuestionCustomCell" owner:self options:nil];
cell = (QuestionCustomCell *)[myNib lastObject];
}
BOOL manyCells = [[arrayForBool objectAtIndex:indexPath.section] boolValue];
if(manyCells){
cell.questionNameLbl.text = [_answerArray objectAtIndex:indexPath.section];
}
return cell;
}
You can use ExpyTableView
Which makes an expandable section from your given cell. Compatible down to iOS 8.0. You will have flexibility by generating an expandable table view with multiple table view cells. Just manipulate the separators for states and then no one will know you are using multiple cells for expanding.
Other solutions: You manipulate the height to expand a cell, when an update needed in design of the cell, you have to re-construct all the auto-layout constraints or logic in code.
ExpyTableView: You make an expandable table view by using multiple cells and inserting and deleting them(which can mean expanding and collapsing), you will have a great chance in future design requests. All you will have to do is adding a new UITableViewCell and writing the code for it. You will easily have the new design.
All you have to do is to import ExpyTableView and then:
class ViewController: ExpyTableViewDataSource, ExpyTableViewDelegate {
#IBOutlet weak var expandableTableView: ExpyTableView!
// First, set data source and delegate for your table view.
override func viewDidLoad() {
super.viewDidLoad()
expandableTableView.dataSource = self
expandableTableView.delegate = self
}
// Then return your expandable cell instance from expandingCell data source method.
func expandableCell(forSection section: Int, inTableView tableView: ExpyTableView) -> UITableViewCell {
// this cell will be displayed at IndexPath with section: section and row 0
}
}
You can see your former table view section is now an expandable table view section. You can also download the example project and see more detailed examples.
UITableView with Collapsible (expand and collapse) Cells swift 5
Very Easy to Use with Custom Cells
Expendable
Dynamic Content
Check Github Link : https://github.com/Murteza12/ExpandableTablew/wiki/UITableView-with-Collapsible-(expand-and-collapse)-Cells
Related
I have a UICollectionView inside a UITableViewCell. You may refer the image at here
I would like to reload the collectionView if any update happen.
I have done some research and found this :
how to reload a collectionview that is inside a tableviewcell
Reloading collection view inside a table view cell happens after all cells in table view have been loaded
UICollectionView not updating inside UITableViewCell
I called the #IBOutlet weak var collectionView: UICollectionView! from UITableViewCell to UITableViewController at cellForRowAt.
Here is the code:
var refreshNow: Bool = false
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.allCardCell, for: indexPath) as! AllCardTableViewCell
if refreshNow {
cell.collectionView.reloadData()
refreshNow = false
}
cell.collectionView.collectionViewLayout.invalidateLayout()
return cell
}
If the user click Ok on UIAlertAction :
let alert = UIAlertController(title: "Success", message: "Card successfully added", preferredStyle: .alert)
let action = UIAlertAction(title: "Ok", style: .default) { (action) in
self.refreshNow = true
self.tableView.reloadData()
}
alert.addAction(action)
self.present(alert, animated: true, completion: nil)
The reason why I put the refreshNow is to prevent the apps from lagging and slow. But still did not update if any changes happen.
The problem is the collectionView did not refresh. But when I debug, it was went through the cell.collectionView.reloadData().
The update/changes only happen when I restart the apps. I want it to be so called real-times update.
Any help is really appreciated and many thanks.
Image credit: How to use StoryBoard quick build a collectionView inside UITableViewCell
At end of your update add:
DispatchQueue.main.async() { () -> Void in
self.tableView.reloadData()
}
In your case, you should assign tag to your collection view in order to get access outside the cellForRowAt function.
This is how your function should look like:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Storyboard.allCardCell, for: indexPath) as! AllCardTableViewCell
cell.collectionView.tag = 1234
return cell
}
and the action will reload it will access the collectionView by using the tag
let action = UIAlertAction(title: "Ok", style: .default) { (action) in
let collectionView = self.tableView.viewWithTag(1234) as! UICollectionView
collectionView.reloadData()
}
Also take note that cellForRowAt will keep reload the content based what you added inside it every time the cell appear. So, keep updating your data outside the cellForRowAt function.
Because you reused UITableViewCell so you must alway reload your UICollectionView. If you use refreshNow to reload UICollectionView, at the cell have refreshNow = false, UICollectionView will display like cell that it 's reused => wrong
Udate rep:
See , in picture uitableviewcell 1 will reuse at index 6. If you not reload content of cell (reload collectionview) it will display like uitableviewcell 1 at index 0
#import "AddPhotoViewController.h"
#import "PhotoTableViewCell.h"
#import "ShareTableViewCell.h"
#interface AddPhotoViewController ()
#property (weak, nonatomic) IBOutlet UITableView *tblView;
#property (strong,nonatomic)NSMutableArray *arrImages,*arrIndexPath,*selectImages;
#end
#pragma mark - TableViewDelegate&DataSource
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 3;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *returnCell;
static NSString *cellIdentifier = #"CellOne";
static NSString *cellIdentifierTwo = #"CellTwo";
static NSString *cellIdentifierThree = #"CellThree";
if (indexPath.row == 0) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
returnCell = cell;
} else if (indexPath.row == 1){
ShareTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifierTwo forIndexPath:indexPath];
cell.viewMood.layer.cornerRadius = 5;
cell.viewPeople.layer.cornerRadius = 5;
[cell.viewMood layer].borderWidth = 1;
[cell.viewMood layer].borderColor = [UIColor colorWithRed:241.0/255.0 green:143.0/255.0 blue:48.0/255.0 alpha:1].CGColor;
[cell.viewPeople layer].borderWidth = 1;
[cell.viewPeople layer].borderColor = [UIColor colorWithRed:241.0/255.0 green:143.0/255.0 blue:48.0/255.0 alpha:1].CGColor;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
returnCell = cell;
}else if (indexPath.row == 2){
PhotoTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifierThree forIndexPath:indexPath];
cell.collView.dataSource = self;
cell.collView.delegate = self;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
returnCell = cell;
}
return returnCell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return UITableViewAutomaticDimension;
}
#pragma mark- UIImagePickerControllerDelegate
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
UIImage *chosenImage = info[UIImagePickerControllerOriginalImage];
[_arrImages addObject:chosenImage];
PhotoTableViewCell *cell = [self.tblView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:2 inSection:0]];
[cell.collView reloadData];
[picker dismissViewControllerAnimated:YES completion:^{
}];
}
#pragma mark - CollectionViewDataSource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return [_arrImages count];
}
- ( UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"CellCollection";
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
UIImageView *imgView = [(UIImageView*)[cell contentView] viewWithTag:100];
UIImageView *imgViewTick = [(UIImageView*)[cell contentView] viewWithTag:200];
UIView *view = [(UIView*)[cell contentView] viewWithTag:300];
if (indexPath.row == 0){
imgViewTick.hidden = YES;
view.hidden = YES;
}
if ([_arrIndexPath containsObject:indexPath]) {
[_selectImages removeAllObjects];
view.hidden = NO;
view.alpha = 0.4;
imgViewTick.hidden = NO;
imgView.image = [_arrImages objectAtIndex:indexPath.row];
[_selectImages addObject:[_arrImages objectAtIndex:indexPath.row]];
NSLog(#"Pick images:%#",_selectImages);
}else{
view.hidden = YES;
imgViewTick.hidden = YES;
imgView.image = [_arrImages objectAtIndex:indexPath.row];
}
return cell;
}
I want to make this type of expandable/collapsible table view.
there are categories and subcategories as in picture.
for example "health and beauty" is a category and when i click this cell than its open subcategories as in picture below.
So how can I make this type of table view?
please suggest me.
Finally i get two very useful helping link below which describes exact what the requirement is here
Expanding/Collapsing TableView Sections
Collapsable Table View for iOS
Really, good articles for such kind of expanding/collapsing tableview sections
Use Following code for expandable Cell into UITableView
- (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];
}
cell.textLabel.text=[[self.arForTable objectAtIndex:indexPath.row] valueForKey:#"name"];
[cell setIndentationLevel:[[[self.arForTable objectAtIndex:indexPath.row] valueForKey:#"level"] intValue]];
return cell;
}
code for expanding & collapsing rows – TableView DidSelectRow Method
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSDictionary *d=[self.arForTable objectAtIndex:indexPath.row];
if([d valueForKey:#"Objects"]) {
NSArray *ar=[d valueForKey:#"Objects"];
BOOL isAlreadyInserted=NO;
for(NSDictionary *dInner in ar ){
NSInteger index=[self.arForTable indexOfObjectIdenticalTo:dInner];
isAlreadyInserted=(index>0 && index!=NSIntegerMax);
if(isAlreadyInserted) break;
}
if(isAlreadyInserted) {
[self miniMizeThisRows:ar];
} else {
NSUInteger count=indexPath.row+1;
NSMutableArray *arCells=[NSMutableArray array];
for(NSDictionary *dInner in ar ) {
[arCells addObject:[NSIndexPath indexPathForRow:count inSection:0]];
[self.arForTable insertObject:dInner atIndex:count++];
}
[tableView insertRowsAtIndexPaths:arCells withRowAnimation:UITableViewRowAnimationLeft];
}
}
}
A Method which will help to minimize & maximize/expand-collapse rows.
-(void)miniMizeThisRows:(NSArray*)ar{
for(NSDictionary *dInner in ar ) {
NSUInteger indexToRemove=[self.arForTable indexOfObjectIdenticalTo:dInner];
NSArray *arInner=[dInner valueForKey:#"Objects"];
if(arInner && [arInner count]>0){
[self miniMizeThisRows:arInner];
}
if([self.arForTable indexOfObjectIdenticalTo:dInner]!=NSNotFound) {
[self.arForTable removeObjectIdenticalTo:dInner];
[self.tableView deleteRowsAtIndexPaths:
[NSArray arrayWithObject:[NSIndexPath indexPathForRow:indexToRemove inSection:0]]
withRowAnimation:UITableViewRowAnimationRight];
}
}
}
You can download the source code from my tutorial site.
If this helps: [Access uitableview's expandable and collapsable sections] https://github.com/OliverLetterer/UIExpandableTableView
I have a little bit of a different approach to expandable table views - one that aligns with how these kinds of table views are generally built.
There are headers and there are cells. Headers should be tappable, and then cells underneath the headers would show or hide. This can be achieved by adding a gesture recognizer to the header, and when tapped, you just remove all of the cells underneath that header (the section), and viceversa (add cells). Of course, you have to maintain state of which headers are "open" and which headers are "closed."
This is nice for a couple of reasons:
The job of headers and cells are separated which makes code cleaner.
This method flows nicely with how table views are built (headers and cells) and, therefore, there isn't much magic - the code is simply removing or adding cells, and should be compatible with later versions of iOS.
I made a very simple library to achieve this. As long as your table view is set up with UITableView section headers and cells, all you have to do is subclass the tableview and the header. Try it :)
Link: https://github.com/fuzz-productions/FZAccordionTableView
Try Using this code... May be this can help..
And Feel free to Edit the code according to your requirements...
#import "ViewController.h"
#import <QuartzCore/QuartzCore.h>
#interface ViewController ()
#end
#implementation ViewController
#synthesize myTable;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//myTable.backgroundColor=[UIColor clearColor];
// self.view.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:#"wood.png"]];
muArr= [[NSMutableArray alloc]initWithObjects:#"Vinay",#"Anmol",#"Jagriti", nil];
ExpArr=[[NSMutableArray alloc]initWithObjects:#"Useeee",#"Thissss",#"Codeee", nil];
otherExpand=100;
checker=100;
}
-(NSInteger) numberOfSectionsInTableView:(UITableView *)tableView
{
return muArr.count;
}
-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(otherExpand==section)
return ExpArr.count;
return 0;
}
-(BOOL)tableView:(UITableView *)table canCollapse:(NSIndexPath *)indexPath
{
return NO;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *Identifier=#"Cell";
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:Identifier];
if (cell==nil)
{
cell=[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:Identifier];
}
cell.textLabel.text=[ExpArr objectAtIndex:indexPath.row];
cell.textLabel.backgroundColor=[UIColor clearColor];
UIView *viewww=[[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
viewww.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:#"wood.png"]];
cell.backgroundView=viewww;
// cell.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:#"wood.png"]];
[tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLineEtched];
[tableView setSeparatorColor:[UIColor purpleColor]];
return cell;
}
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *view1=[[UIView alloc]initWithFrame:CGRectMake(0, 0, 320, 44)];
[view1.layer setCornerRadius:20];
view1.layer.borderWidth=2;
view1.layer.borderColor=[UIColor brownColor].CGColor;
UILabel *label=[[UILabel alloc]initWithFrame:CGRectMake(10, 0, 295, 44)];
label.backgroundColor=[UIColor clearColor];
label.text=[muArr objectAtIndex:section];
UIButton *btn=[UIButton buttonWithType:UIButtonTypeDetailDisclosure];
btn.frame=CGRectMake(280, -5, 50, 50);
btn.backgroundColor=[UIColor clearColor];
btn.tag=section;
view1.backgroundColor=[UIColor colorWithPatternImage:[UIImage imageNamed:#"wood.png"]];
label.textColor=[UIColor blackColor];
label.font=[UIFont fontWithName:#"American TypeWriter" size:18];
//btn.backgroundColor=[UIColor blackColor];
[view1 addSubview:btn];
[view1 addSubview:label];
[btn addTarget:self action:#selector(Btntap:) forControlEvents:UIControlEventTouchUpInside];
return view1;
}
-(void)Btntap : (UIButton *)btn
{
if(otherExpand!=100)
{
if (otherExpand==btn.tag)
{
NSMutableArray *tempArr2=[[NSMutableArray alloc]init];
for(int j=0;j<ExpArr.count;j++)
{
NSIndexPath *indexx1=[NSIndexPath indexPathForRow:j inSection:otherExpand];
[tempArr2 addObject:indexx1];
}
checker=0;
otherExpand=100;
[myTable deleteRowsAtIndexPaths:tempArr2 withRowAnimation:UITableViewRowAnimationAutomatic];
}
else
{
NSMutableArray *tempArr2=[[NSMutableArray alloc]init];
for(int j=0;j<ExpArr.count;j++)
{
NSIndexPath *indexx1=[NSIndexPath indexPathForRow:j inSection:otherExpand];
[tempArr2 addObject:indexx1];
}
checker=1;
otherExpand=100;
[myTable deleteRowsAtIndexPaths:tempArr2 withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
if(checker!=0)
{
otherExpand=btn.tag;
//checker=
NSMutableArray *tempArr=[[NSMutableArray alloc]init];
for(int i=0;i<ExpArr.count;i++)
{
NSIndexPath *indexx=[NSIndexPath indexPathForRow:i inSection:btn.tag];
[tempArr addObject:indexx];
}
[myTable insertRowsAtIndexPaths:tempArr withRowAnimation:UITableViewRowAnimationAutomatic];
checker=1;
}
checker=100;
}
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 44;
}
#end
There is a great video in WWDC 2011 called UITableView Changes, Tips and Tricks - session 125 that shows how to do things like this.
Also check out the example code TVAnimationsGestures
You may take a look at this accordion example in Swift: https://github.com/tadija/AEAccordion
It's got very little code to create accordion effect (not by using sections but cells), and as a bonus there is also a solution to use XIB files inside other XIB files (useful for custom cells which use custom views).
Please try this example :
best example for Expandable TableView
https://github.com/OliverLetterer/UIExpandableTableView
TLIndexPathTools can do this sort of thing naturally. In fact, there is are extensions for both expandable sections and expandable tree structures. Try running the Collapse sample project for expandable sections and the Outline sample project for expandable trees.
One advantage of using TLIndexPathTools is that, as a simple, low-level API, it can solve all kinds of dynamic table view and collection view problems using a common approach. And it works interchangeably with Core Data and plain arrays.
it is so easy to create an expandable tableview
here is an example how I did this,
data I m using for this one
struct ItemList {
var name: String
var items: [String]
var collapsed: Bool
init(name: String, items: [String], collapsed: Bool = false) {
self.name = name
self.items = items
self.collapsed = collapsed
}
}
var sections = [ItemList]()
var items: [ItemList] = [
ItemList(name: "Mac", items: ["MacBook", "MacBook Air"]),
ItemList(name: "iPad", items: ["iPad Pro", "iPad Air 2"]),
ItemList(name: "iPhone", items: ["iPhone 7", "iPhone 6"])
]
now just add this piece of code and use accordingly
extension ViewController:UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 60
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerHeading = UILabel(frame: CGRect(x: 5, y: 10, width: self.view.frame.width, height: 40))
let imageView = UIImageView(frame: CGRect(x: self.view.frame.width - 30, y: 20, width: 20, height: 20))
if items[section].collapsed{
imageView.image = UIImage(named: "collapsed")
}else{
imageView.image = UIImage(named: "expand")
}
let headerView = UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 60))
let tapGuesture = UITapGestureRecognizer(target: self, action: #selector(headerViewTapped))
tapGuesture.numberOfTapsRequired = 1
headerView.addGestureRecognizer(tapGuesture)
headerView.backgroundColor = UIColor.red
headerView.tag = section
headerHeading.text = items[section].name
headerHeading.textColor = .white
headerView.addSubview(headerHeading)
headerView.addSubview(imageView)
return headerView
}
func numberOfSections(in tableView: UITableView) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let itms = items[section]
return !itms.collapsed ? 0 : itms.items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! UITableViewCell
cell.textLabel?.text = items[indexPath.section].items[indexPath.row]
return cell
}
#objc func headerViewTapped(tapped:UITapGestureRecognizer){
print(tapped.view?.tag)
if items[tapped.view!.tag].collapsed == true{
items[tapped.view!.tag].collapsed = false
}else{
items[tapped.view!.tag].collapsed = true
}
if let imView = tapped.view?.subviews[1] as? UIImageView{
if imView.isKind(of: UIImageView.self){
if items[tapped.view!.tag].collapsed{
imView.image = UIImage(named: "collapsed")
}else{
imView.image = UIImage(named: "expand")
}
}
}
tableView.reloadData()
}
}
and the result is Bingo :)
I had the requirement of expanding a single cell to a fuller view and collapsing it back to a summarised view.
So what I did was to design my cell using UIStackView. And I kept the view I didn't want to show in the collapsed state hidden and then showing it when the cell was tapped.
The trick here is to show and hide the view within tableView.beginUpdates() and tableView.endUpdates() statements. In this way table view automatically adjusts the cell height and does it animatedly.
Here is how a basic cell would look in the IB:
Cells Custom Class:
class AccordionCell: UITableViewCell {
#IBOutlet weak var stackView: UIStackView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var descriptionLabel: UILabel!
#IBOutlet weak var extendedDescriptionLabel: UILabel!
var expanded: Bool = false {
didSet {
if let extended = self.extendedDescriptionLabel {
extended.isHidden = !expanded
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.expanded = false
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
UITableView Delegate Implementation:
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifier) as! AccordionCell
cell.titleLabel.text = "Row: \(indexPath.row)"
cell.expanded = indexPath.row == expanded
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? AccordionCell {
tableView.beginUpdates()
if expanded == indexPath.row {
cell.expanded = false
expanded = -1
}
else {
cell.expanded = true
expanded = indexPath.row
}
tableView.endUpdates()
tableView.selectRow(at: indexPath, animated: true, scrollPosition: .none)
}
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
if let cell = tableView.cellForRow(at: indexPath) as? AccordionCell {
tableView.beginUpdates()
cell.expanded = false
tableView.endUpdates()
}
}
}
In order to keep track which cell is expanded, I introduced a variable saving indexpath of currently expanded cell so that the right cell is expanded when tableview is scrolled.
Check this Link :
http://iostechnotips.blogspot.in/2014/05/expandable-uitableview.html
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
*Use UITableView delegate method viewForHeaderInSection and return a custom UIView.
*Add a UIButton as subview with action "expandable:(id)sender" check the sender id as section number and reload the table view.
In your .h file
LoadCustomCell *cell1;
NSMutableArray *arrayForBool;
NSMutableArray *questionArray;
NSMutableArray *answerArray;
In your .m file
viewDidLoadMethod {
_faqTblView.estimatedRowHeight = 30;
_faqTblView.rowHeight = UITableViewAutomaticDimension;
arrayForBool = [[NSMutableArray alloc]init];
_questionArray = [[NSMutableArray alloc]init];
_answerArray = [[NSMutableArray alloc]init];
for (int i = 0; i < _questionArray.count; i++) {
[arrayForBool addObject:#"0"];
}
self.faqTblView.dataSource = self;
self.faqTblView .delegate = self;
[self.faqTblView reloadData];
}
after that
#pragma mark - TableView Datasource & Delegate Method.
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [_questionArray count];
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
UILabel *lblText = [[UILabel alloc]initWithFrame:CGRectMake(0, 0, 260, 100)];
lblText.text = [_questionArray objectAtIndex:section];
return [lblText getLabelHeight] + 20;(created custom class)
}
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UITapGestureRecognizer *headerTapped = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(sectionHeaderTapped:)];
cell1 = [[[NSBundle mainBundle] loadNibNamed:#"LoadCustomCell" owner:self options:nil] objectAtIndex:0];
[cell1 setFrame:CGRectMake(0, 0, cell1.frame.size.width, cell1.frame.size.height)];
NSString *numStr = [NSString stringWithFormat:#"%ld. ",section + 1];
[cell1.sideMenuUserNameLabel setText:[numStr stringByAppendingString:[_questionArray objectAtIndex:section]]];
[cell1 setBackgroundColor:[UIColor lightGrayColor]];
cell1.tag = section;
[cell1 addGestureRecognizer:headerTapped];
return cell1;
}
- (void)sectionHeaderTapped:(UITapGestureRecognizer *)gestureRecognizer {
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:gestureRecognizer.view.tag];
if (indexPath.row == 0) {
BOOL collapsed = [[arrayForBool objectAtIndex:indexPath.section] boolValue];
for (int i = 0; i < [_questionArray count]; i++) {
if (indexPath.section==i) {
[arrayForBool removeObjectAtIndex:i];
[arrayForBool insertObject:[NSString stringWithFormat:#"%d", !collapsed] atIndex:i];
}
}
NSLog(#"%#", arrayForBool);
[self.faqTblView reloadSections:[NSIndexSet indexSetWithIndex:gestureRecognizer.view.tag] withRowAnimation:UITableViewRowAnimationAutomatic];
for (NSIndexPath *indexPath in self.faqTblView.indexPathsForSelectedRows) {
[self.faqTblView deselectRowAtIndexPath:indexPath animated:NO];
}
cell1.imageView.transform = CGAffineTransformMakeRotation(M_PI);
}
}
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *questionCellIdentifier = #"questionCellIdentifier";
QuestionCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:questionCellIdentifier];
if (cell == nil) {
NSArray * myNib;
myNib =[[NSBundle mainBundle]loadNibNamed:#"QuestionCustomCell" owner:self options:nil];
cell = (QuestionCustomCell *)[myNib lastObject];
}
BOOL manyCells = [[arrayForBool objectAtIndex:indexPath.section] boolValue];
if(manyCells){
cell.questionNameLbl.text = [_answerArray objectAtIndex:indexPath.section];
}
return cell;
}
You can use ExpyTableView
Which makes an expandable section from your given cell. Compatible down to iOS 8.0. You will have flexibility by generating an expandable table view with multiple table view cells. Just manipulate the separators for states and then no one will know you are using multiple cells for expanding.
Other solutions: You manipulate the height to expand a cell, when an update needed in design of the cell, you have to re-construct all the auto-layout constraints or logic in code.
ExpyTableView: You make an expandable table view by using multiple cells and inserting and deleting them(which can mean expanding and collapsing), you will have a great chance in future design requests. All you will have to do is adding a new UITableViewCell and writing the code for it. You will easily have the new design.
All you have to do is to import ExpyTableView and then:
class ViewController: ExpyTableViewDataSource, ExpyTableViewDelegate {
#IBOutlet weak var expandableTableView: ExpyTableView!
// First, set data source and delegate for your table view.
override func viewDidLoad() {
super.viewDidLoad()
expandableTableView.dataSource = self
expandableTableView.delegate = self
}
// Then return your expandable cell instance from expandingCell data source method.
func expandableCell(forSection section: Int, inTableView tableView: ExpyTableView) -> UITableViewCell {
// this cell will be displayed at IndexPath with section: section and row 0
}
}
You can see your former table view section is now an expandable table view section. You can also download the example project and see more detailed examples.
UITableView with Collapsible (expand and collapse) Cells swift 5
Very Easy to Use with Custom Cells
Expendable
Dynamic Content
Check Github Link : https://github.com/Murteza12/ExpandableTablew/wiki/UITableView-with-Collapsible-(expand-and-collapse)-Cells
I am using tableView to display some information which is just four line of information. And i want to assign respective information to each row.
Like how shown in the below image there are four rows, same as in the image so i am using tableView for that. Here my problem is that i have created four cells but don't know how should i use label in specific cell and show the info.
and also if the value is null that row should not be there means if two values among four are null then only two rows having values should be there in tableView. How can i achieve this. Till now i am only able to show one row information only.
- (NSArray *)myTableViewCells
{
if (!_myTableViewCells)
{
_myTableViewCells = #[
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil],
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil],
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil],
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]
];
}
return _myTableViewCells;
}
if([managedObject valueForKey:#"personality_company_master_values"] != nil)
{
[_displayValues addObject:[NSString stringWithFormat:#"Personality %#",[managedObject valueForKey:#"personality_company_master_values"]]];
}
if([managedObject valueForKey:#"video_tag"] != nil)
{
[_displayValues addObject:[NSString stringWithFormat:#"Tag %#",[managedObject valueForKey:#"video_tag"]]];
}
if([managedObject valueForKey:#"industry_master_values"] != nil)
{
[_displayValues addObject:[NSString stringWithFormat:#"Industry %#",[managedObject valueForKey:#"industry_master_values"]]];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.myTableViewCells.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = self.myTableViewCells[indexPath.row];
// NSManagedObject *managedObject = [self.devices lastObject];
cell.backgroundColor = [self colorFromHexString:#"#014455"];
cell.textLabel.text = _displayValues[indexPath.row];
cell.textLabel.backgroundColor = [self colorFromHexString:#"#014455"];
cell.textLabel.textColor = [UIColor whiteColor];
cell.textLabel.font=[UIFont systemFontOfSize:14.0];
// UILabel *lbl=(UILabel*)[cell viewWithTag:900];
// [lbl setText:[managedObject valueForKey:#"personality_company_master_values"]];
// lbl.textColor=[UIColor blackColor];
return cell;
}
Get the value you want display in an array.
Something like this
#property (monatomic, strong)NSMuatableArray *displayValues;
-(void)viewDidLoad
{
self.displayValues = [[NSMutableArray alloc]init];
NSManagedObject *managedObject = [self.devices lastObject];
if([managedObject valueForKey:#"personality_company_master_values"] != nil)
{
[self.displayValues addObject:[managedObject valueForKey:#"personality_company_master_values"]];
}
if([managedObject valueForKey:#"company_master_values"] != nil)
{
[self.displayValues addObject:[managedObject valueForKey:#"company_master_values"]];
}
if([managedObject valueForKey:#"tag_master_values"] != nil)
{
[self.displayValues addObject:[managedObject valueForKey:#"tag_master_values"]];
}
}
- (NSArray *)myTableViewCells
{
if (!_myTableViewCells)
{
_myTableViewCells = #[
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil],
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil],
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil],
[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil]
];
}
return _myTableViewCells;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.displayValues.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = self.myTableViewCells[indexPath.row];
// NSManagedObject *managedObject = [self.devices lastObject];
//cell.textLabel.text = [NSString stringWithFormat:#"%#",[managedObject valueForKey:#"personality_company_master_values"]];
cell.textLabel.text = self.displayValues[indexPath.row];
//not getting have to do this way or any other way please help
// secondLabel.text = [NSString stringWithFormat:#"%#",[managedObject valueForKey:#"company_master_values"]];
// thirdLabel.text = [NSString stringWithFormat:#"%#",[managedObject valueForKey:#"tag_master_values"]];
return cell;
}
I'm afraid you're doing several things wrong, starting with preallocating an array of cells. Tableviews don't work like that, you provide cells on demand and populate them with values from your data model. When you want to remove a cell update your data model then call reloadData(). Here's a simple example:
import UIKit
class MyCell: UITableViewCell {
var row: Int = -1 // serves no purpose but to show how you might subclass a UITableViewCell
}
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var dataModel = [
"Hello", "World,", "this", "is", "a", "tableview"
]
var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
var frame = view.bounds
let statusBarHeight = UIApplication.sharedApplication().statusBarFrame.height
frame.origin.y += statusBarHeight
frame.size.height -= statusBarHeight
tableView = UITableView(frame: frame, style: .Plain)
tableView.delegate = self
tableView.dataSource = self
tableView.registerClass(MyCell.self, forCellReuseIdentifier: "mycell")
view.addSubview(tableView)
}
// MARK: - UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataModel.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("mycell") as! MyCell
let row = indexPath.row
cell.row = row // there is no point in doing this other than to show it as an example
cell.textLabel!.text = dataModel[row]
return cell
}
// MARK: - UITableViewDelegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
dataModel.removeAtIndex(indexPath.row)
tableView.reloadData()
}
}
EDIT: Here's an objective c version
////////////////////////////
/// Objective C Version //
////////////////////////////
#import "ViewController.h"
#interface MyCell: UITableViewCell
#property(assign) NSInteger row; // serves no purpose but to show how you might subclass a UITableViewCell
#end
#implementation MyCell #end
#interface ViewController() <UITableViewDataSource, UITableViewDelegate>
#property NSMutableArray *dataModel;
#property UITableView *tableView;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
_dataModel = [NSMutableArray arrayWithArray: #[#"Hello", #"World,", #"this", #"is", #"a", #"tableview"]];
CGRect frame = self.view.bounds;
CGFloat statusBarHeight = [UIApplication sharedApplication].statusBarFrame.size.height;
frame.origin.y += statusBarHeight;
frame.size.height -= statusBarHeight;
_tableView = [[UITableView alloc] initWithFrame: frame style: UITableViewStylePlain];
_tableView.delegate = self;
_tableView.dataSource = self;
[_tableView registerClass: [MyCell class] forCellReuseIdentifier: #"mycell"];
[self.view addSubview: _tableView];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return _dataModel.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = (MyCell *) [tableView dequeueReusableCellWithIdentifier: #"mycell"];
NSInteger row = indexPath.row;
cell.row = row; // there is no point in doing this other than to show it as an example
cell.textLabel.text = _dataModel[row];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[_dataModel removeObjectAtIndex: indexPath.row];
[_tableView reloadData];
}
#end
You dont need to create a fixed amount of cells this is not an efficient solution to the problem. You should create NSMutableDictionary and save the data like this:
NSMutableArray *data = [NSMutableDictionary dictionary];
[data setValue:#"Vijayakanth" forKey:#"Personality"];
Now in the table view delegate you can return the keys count for noOfRowsInSection and in cellForRowAtIndexPath you can get the key from dictionary get the value w.r.t that key and assign the values to your cell. In your case:
Key: Personality (which is shown on the left side)
Value: Vijayakanth (which is shown on the right side)
Hope you understand the point.
I want to allow the default row movement in a UITableView without it being in editing mode, and without compromising the default behaviour of the UITableView.
The image above displays a cell in editing mode, with movement enabled.
I tried simply running for (UIView *subview in cell.subviews) (while my UITableView was in editing mode), but the button didn't turn up:
<UITableViewCellScrollView: 0x8cabd80; frame = (0 0; 320 44); autoresize = W+H; gestureRecognizers = <NSArray: 0x8c9ba20>; layer = <CALayer: 0x8ca14b0>; contentOffset: {0, 0}>
How can allow/add the movement "button" without enabling editing mode in my UITableView?
Creating and adding a UIButton with the default function for movement is also an option.
I actually do something similar for one of my apps. It uses the delegate methods for table editing and a bit of 'tricking' the user. 100% built-in Apple functionality.
1 - Set the table to editing (I do it in viewWillAppear)
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
[self.tableView setEditing:YES];
}
2 - Hide the default accessory icon:
-(UITableViewCellEditingStyle)tableView:(UITableView *)tableView
editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath{
//remove any editing accessories (AKA) delete button
return UITableViewCellAccessoryNone;
}
3 - Keep editing mode from moving everything to the right (in the cell)
-(BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath{
return NO;
}
4 - At this point you should be able to drag the cells around without it looking like it is in editing mode. Here we trick the user. Create your own "move" icon (three lines in the default case, whatever icon you want in your case) and add the imageView right where it would normally go on the cell.
5 - Finally, implement the delegate method to actually rearrange your underlying datasource.
-(void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{
//Get original id
NSMutableArray *originalArray = [self.model.items objectAtIndex:sourceIndexPath.section];
Item * original = [originalArray objectAtIndex:sourceIndexPath.row];
//Get destination id
NSMutableArray *destinationArray = [self.model.items objectAtIndex:destinationIndexPath.section];
Item * destination = [destinationArray objectAtIndex:destinationIndexPath.row];
CGPoint temp = CGPointMake([original.section intValue], [original.row intValue]);
original.row = destination.row;
original.section = destination.section;
destination.section = #(temp.x);
destination.row = #(temp.y);
//Put destination value in original array
[originalArray replaceObjectAtIndex:sourceIndexPath.row withObject:destination];
//put original value in destination array
[destinationArray replaceObjectAtIndex:destinationIndexPath.row withObject:original];
//reload tableview smoothly to reflect changes
dispatch_async(dispatch_get_main_queue(), ^{
[UIView transitionWithView:tableView duration:duration options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
[tableView reloadData];
} completion:NULL];
});
}
William Falcon's answer in swift 3
1 - Set the table to editing (I do it in viewWillAppear)
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated: animated)
tableView.setEditing(true, animated: false)
}
2 - Hide the default accessory icon:
override func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
return .none
}
3 - Keep editing mode from moving everything to the right (in the cell)
override func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
4 - Not required in swift 3
5 - Reorder your array
Extra Note
If you want to have your table cells selectable, add the following code within viewWillAppear() function.
tableView.allowsSelectionDuringEditing = true
For Swift 5... I know the question asks WITHOUT editing, but if your only functionality needs are to
be able to reorder cells and
be able to select cells
Then you can follow this (source for reorder: https://www.ralfebert.de/ios-examples/uikit/uitableviewcontroller/reorderable-cells/):
set tableView.isEditing = true
set tableView.allowsSelectionDuringEditing = true
With UITableViewDataSource, add the following methods:
func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .none
}
func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool {
return false
}
You may or may not need to override those functions. I didn't need to.
(I think) in UITableViewDelegate, add this:
override func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
let movedObject = items[sourceIndexPath.row]
items.remove(at: sourceIndexPath.row)
items.insert(movedObject, at: destinationIndexPath.row)
}
where items is the data source array that handles the number of items in your table view.
The key is step 2, in which you can finally add the func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) method.
If you don't want to set the UITableView into editing mode then you will need to reinvent the capability for dragging cells around.
I present a fairly complete solution that enables the user to move rows around within the visible area of the UITableView. It works whether the UITableView is in editing mode or not. You'll need to extend it if you want the table to scroll when a row is dragged near the top or bottom of the visible area. There are likely some failure and edge cases you'll need to ferret out as well.
#implementation TSTableViewController
{
NSMutableArray* _dataSource;
}
- (void) viewDidLoad
{
[super viewDidLoad];
_dataSource = [NSMutableArray new];
for ( int i = 0 ; i < 10 ; i++ )
{
[_dataSource addObject: [NSString stringWithFormat: #"cell %d", i]];
}
}
- (void) longPress: (UILongPressGestureRecognizer*) lpgr
{
static NSString* dragCellData = nil;
static UIView* dragCellView = nil;
static NSInteger dragCellOffset = 0;
// determine the cell we're hovering over, etc:
CGPoint pt = [lpgr locationInView: self.tableView];
NSIndexPath* ip = [self.tableView indexPathForRowAtPoint: pt];
UITableViewCell* cell = [self.tableView cellForRowAtIndexPath: ip];
CGPoint ptInCell = [lpgr locationInView: cell];
// where the current placeholder cell is, if any:
NSInteger placeholderIndex = [_dataSource indexOfObject: #"placeholder"];
switch ( lpgr.state )
{
case UIGestureRecognizerStateBegan:
{
// get a snapshot-view of the cell we're going to drag:
cell.selected = cell.highlighted = NO;
dragCellView = [cell snapshotViewAfterScreenUpdates: YES];
dragCellView.clipsToBounds = NO;
dragCellView.layer.shadowRadius = 10;
dragCellView.layer.shadowColor = [UIColor blackColor].CGColor;
dragCellView.layer.masksToBounds = NO;
dragCellView.frame = [cell convertRect: cell.bounds
toView: self.tableView.window];
// used to position the dragCellView nicely:
dragCellOffset = ptInCell.y;
// the cell will be removed from the view hierarchy by the tableview, so transfer the gesture recognizer to our drag view, and add it into the view hierarchy:
[dragCellView addGestureRecognizer: lpgr];
[self.tableView.window addSubview: dragCellView];
// swap out the cell for a placeholder:
dragCellData = _dataSource[ip.row];
_dataSource[ip.row] = #"placeholder";
[self.tableView reloadRowsAtIndexPaths: #[ip]
withRowAnimation: UITableViewRowAnimationNone];
break;
}
case UIGestureRecognizerStateChanged:
{
// where should we move the placeholder to?
NSInteger insertIndex = ptInCell.y < cell.bounds.size.height / 2.0 ? ip.row : ip.row + 1;
if ( insertIndex != placeholderIndex )
{
// remove from the datasource and the tableview:
[_dataSource removeObjectAtIndex: placeholderIndex];
[self.tableView deleteRowsAtIndexPaths: #[ [NSIndexPath indexPathForRow: placeholderIndex inSection: 0] ]
withRowAnimation: UITableViewRowAnimationFade];
// adjust:
if ( placeholderIndex < insertIndex )
{
insertIndex--;
}
// insert to the datasource and tableview:
[_dataSource insertObject: #"placeholder"
atIndex: insertIndex];
[self.tableView insertRowsAtIndexPaths: #[ [NSIndexPath indexPathForRow: insertIndex inSection: 0] ]
withRowAnimation: UITableViewRowAnimationFade];
}
// move our dragCellView
CGRect f = dragCellView.frame;
f.origin.y = pt.y - dragCellOffset;
dragCellView.frame = f;
break;
}
case UIGestureRecognizerStateEnded:
{
// replace the placeholdercell with the cell we were dragging
[_dataSource replaceObjectAtIndex: placeholderIndex
withObject: dragCellData];
[self.tableView reloadRowsAtIndexPaths: #[ [NSIndexPath indexPathForRow: placeholderIndex inSection: 0] ]
withRowAnimation: UITableViewRowAnimationFade];
// reset state
[dragCellView removeFromSuperview];
dragCellView = nil;
dragCellData = nil;
break;
}
default:
{
break;
}
}
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return _dataSource.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString* cellData = _dataSource[indexPath.row];
if ( [cellData isEqualToString: #"placeholder" ] )
{
// an empty cell to denote where the "drop" would go
return [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
reuseIdentifier: nil];
}
// a cell...
UITableViewCell* cell = [[UITableViewCell alloc] initWithStyle: UITableViewCellStyleDefault
reuseIdentifier: nil];
// our "fake" move handle & gesture recognizer
UILongPressGestureRecognizer* lpgr = [[UILongPressGestureRecognizer alloc] initWithTarget: self action: #selector( longPress:) ];
lpgr.minimumPressDuration = 0.3;
UILabel* dragLabelView = [UILabel new];
dragLabelView.text = #"☰";
dragLabelView.userInteractionEnabled = YES;
[dragLabelView addGestureRecognizer: lpgr];
[dragLabelView sizeToFit];
cell.textLabel.text = cellData;
if ( tableView.isEditing )
{
cell.editingAccessoryView = dragLabelView;
}
else
{
cell.accessoryView = dragLabelView;
}
return cell;
}
#end
To make the cells move you have to implement the corresponding <UITableViewDelegate> methods for moving and explicit allowing the cell (indexpath) to move. The move-indicator icon will not show up because it depends on editing mode. While the tableview is in editing state the move-icon will overlap the accessoryType icon. Edit mode comes by default with the round delete button on the left side which you can hide like so while editing.
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
if (self.editing) return UITableViewCellEditingStyleNone;
return UITableViewCellEditingStyleDelete;
}
This way you keep your UITableView class with UITableViewDelegate methods clean to understand, you don't need to implement some kind of self-made move-mode.
And you can use the tableview's editing property to distinguish how the cell should look like in your tableView:cellForRowAtIndexPath: method. So even turning off the move-functionality is easier by setting your tableview back to editing = NO, the move-icon would disappear and the accessoryType symbol shows up again.
Could somebody tell me the way to perform UITableView expandable/collapsible animations in sections of UITableView as below?
or
You have to make your own custom header row and put that as the first row of each section. Subclassing the UITableView or the headers that are already there will be a pain. Based on the way they work now, I am not sure you can easily get actions out of them. You could set up a cell to LOOK like a header, and setup the tableView:didSelectRowAtIndexPath to manually expand or collapse the section it is in.
I'd store an array of booleans corresponding the the "expended" value of each of your sections. Then you could have the tableView:didSelectRowAtIndexPath on each of your custom header rows toggle this value and then reload that specific section.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == 0) {
///it's the first row of any section so it would be your custom section header
///put in your code to toggle your boolean value here
mybooleans[indexPath.section] = !mybooleans[indexPath.section];
///reload this section
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:indexPath.section] withRowAnimation:UITableViewRowAnimationFade];
}
}
Then set numberOfRowsInSection to check the mybooleans value and return 1 if the section isn't expanded, or 1+ the number of items in the section if it is expanded.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (mybooleans[section]) {
///we want the number of people plus the header cell
return [self numberOfPeopleInGroup:section] + 1;
} else {
///we just want the header cell
return 1;
}
}
Also, you will need to update cellForRowAtIndexPath to return a custom header cell for the first row in any section.
Some sample code for animating an expand/collapse action using a table view section header is provided by Apple here: Table View Animations and Gestures
The key to this approach is to implement - (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section and return a custom UIView which includes a button (typically the same size as the header view itself). By subclassing UIView and using that for the header view (as this sample does), you can easily store additional data such as the section number.
I got a nice solution inspired by Apple's Table View Animations and Gestures. I deleted unnecessary parts from Apple's sample and translated it into swift.
I know the answer is quite long, but all the code is necessary. Fortunately, you can just copy and paste most of the code and just need to do a bit modification on step 1 and 3
1.create SectionHeaderView.swift and SectionHeaderView.xib
import UIKit
protocol SectionHeaderViewDelegate {
func sectionHeaderView(sectionHeaderView: SectionHeaderView, sectionOpened: Int)
func sectionHeaderView(sectionHeaderView: SectionHeaderView, sectionClosed: Int)
}
class SectionHeaderView: UITableViewHeaderFooterView {
var section: Int?
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var disclosureButton: UIButton!
#IBAction func toggleOpen() {
self.toggleOpenWithUserAction(true)
}
var delegate: SectionHeaderViewDelegate?
func toggleOpenWithUserAction(userAction: Bool) {
self.disclosureButton.selected = !self.disclosureButton.selected
if userAction {
if self.disclosureButton.selected {
self.delegate?.sectionHeaderView(self, sectionClosed: self.section!)
} else {
self.delegate?.sectionHeaderView(self, sectionOpened: self.section!)
}
}
}
override func awakeFromNib() {
var tapGesture: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "toggleOpen")
self.addGestureRecognizer(tapGesture)
// change the button image here, you can also set image via IB.
self.disclosureButton.setImage(UIImage(named: "arrow_up"), forState: UIControlState.Selected)
self.disclosureButton.setImage(UIImage(named: "arrow_down"), forState: UIControlState.Normal)
}
}
the SectionHeaderView.xib(the view with gray background) should look something like this in a tableview(you can customize it according to your needs, of course):
note:
a) the toggleOpen action should be linked to disclosureButton
b) the disclosureButton and toggleOpen action are not necessary. You can delete these 2 things if you don't need the button.
2.create SectionInfo.swift
import UIKit
class SectionInfo: NSObject {
var open: Bool = true
var itemsInSection: NSMutableArray = []
var sectionTitle: String?
init(itemsInSection: NSMutableArray, sectionTitle: String) {
self.itemsInSection = itemsInSection
self.sectionTitle = sectionTitle
}
}
3.in your tableview
import UIKit
class TableViewController: UITableViewController, SectionHeaderViewDelegate {
let SectionHeaderViewIdentifier = "SectionHeaderViewIdentifier"
var sectionInfoArray: NSMutableArray = []
override func viewDidLoad() {
super.viewDidLoad()
let sectionHeaderNib: UINib = UINib(nibName: "SectionHeaderView", bundle: nil)
self.tableView.registerNib(sectionHeaderNib, forHeaderFooterViewReuseIdentifier: SectionHeaderViewIdentifier)
// you can change section height based on your needs
self.tableView.sectionHeaderHeight = 30
// You should set up your SectionInfo here
var firstSection: SectionInfo = SectionInfo(itemsInSection: ["1"], sectionTitle: "firstSection")
var secondSection: SectionInfo = SectionInfo(itemsInSection: ["2"], sectionTitle: "secondSection"))
sectionInfoArray.addObjectsFromArray([firstSection, secondSection])
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sectionInfoArray.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if self.sectionInfoArray.count > 0 {
var sectionInfo: SectionInfo = sectionInfoArray[section] as! SectionInfo
if sectionInfo.open {
return sectionInfo.open ? sectionInfo.itemsInSection.count : 0
}
}
return 0
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let sectionHeaderView: SectionHeaderView! = self.tableView.dequeueReusableHeaderFooterViewWithIdentifier(SectionHeaderViewIdentifier) as! SectionHeaderView
var sectionInfo: SectionInfo = sectionInfoArray[section] as! SectionInfo
sectionHeaderView.titleLabel.text = sectionInfo.sectionTitle
sectionHeaderView.section = section
sectionHeaderView.delegate = self
let backGroundView = UIView()
// you can customize the background color of the header here
backGroundView.backgroundColor = UIColor(red:0.89, green:0.89, blue:0.89, alpha:1)
sectionHeaderView.backgroundView = backGroundView
return sectionHeaderView
}
func sectionHeaderView(sectionHeaderView: SectionHeaderView, sectionOpened: Int) {
var sectionInfo: SectionInfo = sectionInfoArray[sectionOpened] as! SectionInfo
var countOfRowsToInsert = sectionInfo.itemsInSection.count
sectionInfo.open = true
var indexPathToInsert: NSMutableArray = NSMutableArray()
for i in 0..<countOfRowsToInsert {
indexPathToInsert.addObject(NSIndexPath(forRow: i, inSection: sectionOpened))
}
self.tableView.insertRowsAtIndexPaths(indexPathToInsert as [AnyObject], withRowAnimation: .Top)
}
func sectionHeaderView(sectionHeaderView: SectionHeaderView, sectionClosed: Int) {
var sectionInfo: SectionInfo = sectionInfoArray[sectionClosed] as! SectionInfo
var countOfRowsToDelete = sectionInfo.itemsInSection.count
sectionInfo.open = false
if countOfRowsToDelete > 0 {
var indexPathToDelete: NSMutableArray = NSMutableArray()
for i in 0..<countOfRowsToDelete {
indexPathToDelete.addObject(NSIndexPath(forRow: i, inSection: sectionClosed))
}
self.tableView.deleteRowsAtIndexPaths(indexPathToDelete as [AnyObject], withRowAnimation: .Top)
}
}
}
To implement the collapsible table section in iOS, the magic is how to control the number of rows for each section, or we can manage the height of rows for each section.
Also, we need to customize the section header so that we can listen to the tap event from the header area (whether it's a button or the whole header).
How to deal with the header? It's very simple, we extend the UITableViewCell class and make a custom header cell like so:
import UIKit
class CollapsibleTableViewHeader: UITableViewCell {
#IBOutlet var titleLabel: UILabel!
#IBOutlet var toggleButton: UIButton!
}
then use the viewForHeaderInSection to hook up the header cell:
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = tableView.dequeueReusableCellWithIdentifier("header") as! CollapsibleTableViewHeader
header.titleLabel.text = sections[section].name
header.toggleButton.tag = section
header.toggleButton.addTarget(self, action: #selector(CollapsibleTableViewController.toggleCollapse), forControlEvents: .TouchUpInside)
header.toggleButton.rotate(sections[section].collapsed! ? 0.0 : CGFloat(M_PI_2))
return header.contentView
}
remember we have to return the contentView because this function expects a UIView to be returned.
Now let's deal with the collapsible part, here is the toggle function that toggle the collapsible prop of each section:
func toggleCollapse(sender: UIButton) {
let section = sender.tag
let collapsed = sections[section].collapsed
// Toggle collapse
sections[section].collapsed = !collapsed
// Reload section
tableView.reloadSections(NSIndexSet(index: section), withRowAnimation: .Automatic)
}
depends on how you manage the section data, in this case, I have the section data something like this:
struct Section {
var name: String!
var items: [String]!
var collapsed: Bool!
init(name: String, items: [String]) {
self.name = name
self.items = items
self.collapsed = false
}
}
var sections = [Section]()
sections = [
Section(name: "Mac", items: ["MacBook", "MacBook Air", "MacBook Pro", "iMac", "Mac Pro", "Mac mini", "Accessories", "OS X El Capitan"]),
Section(name: "iPad", items: ["iPad Pro", "iPad Air 2", "iPad mini 4", "Accessories"]),
Section(name: "iPhone", items: ["iPhone 6s", "iPhone 6", "iPhone SE", "Accessories"])
]
at last, what we need to do is based on the collapsible prop of each section, control the number of rows of that section:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (sections[section].collapsed!) ? 0 : sections[section].items.count
}
I have a fully working demo on my Github: https://github.com/jeantimex/ios-swift-collapsible-table-section
If you want to implement the collapsible sections in a grouped-style table, I have another demo with source code here: https://github.com/jeantimex/ios-swift-collapsible-table-section-in-grouped-section
Hope that helps.
I have a better solution that you should add a UIButton into section header and set this button's size equal to section size, but make it hidden by clear background color, after that you are easily to check which section is clicked to expand or collapse
I ended up just creating a headerView that contained a button ( i saw Son Nguyen's solution above after the fact, but heres my code.. it looks like a lot but it's pretty simple):
declare a couple bools for you sections
bool customerIsCollapsed = NO;
bool siteIsCollapsed = NO;
...code
now in your tableview delegate methods...
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *headerView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, _tblSearchResults.frame.size.width, 35)];
UILabel *lblSection = [UILabel new];
[lblSection setFrame:CGRectMake(0, 0, 300, 30)];
[lblSection setFont:[UIFont fontWithName:#"Helvetica-Bold" size:17]];
[lblSection setBackgroundColor:[UIColor clearColor]];
lblSection.alpha = 0.5;
if(section == 0)
{
if(!customerIsCollapsed)
[lblSection setText:#"Customers --touch to show--"];
else
[lblSection setText:#"Customers --touch to hide--"];
}
else
{
if(!siteIsCollapsed)
[lblSection setText:#"Sites --touch to show--"];
else
[lblSection setText:#"Sites --touch to hide--"]; }
UIButton *btnCollapse = [UIButton buttonWithType:UIButtonTypeCustom];
[btnCollapse setFrame:CGRectMake(0, 0, _tblSearchResults.frame.size.width, 35)];
[btnCollapse setBackgroundColor:[UIColor clearColor]];
[btnCollapse addTarget:self action:#selector(touchedSection:) forControlEvents:UIControlEventTouchUpInside];
btnCollapse.tag = section;
[headerView addSubview:lblSection];
[headerView addSubview:btnCollapse];
return headerView;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if(section == 0)
{
if(customerIsCollapsed)
return 0;
else
return _customerArray.count;
}
else if (section == 1)
{
if(siteIsCollapsed)
return 0;
else
return _siteArray.count;
}
return 0;
}
and finally the function that gets called when you touch one of the section header buttons:
- (IBAction)touchedSection:(id)sender
{
UIButton *btnSection = (UIButton *)sender;
if(btnSection.tag == 0)
{
NSLog(#"Touched Customers header");
if(!customerIsCollapsed)
customerIsCollapsed = YES;
else
customerIsCollapsed = NO;
}
else if(btnSection.tag == 1)
{
NSLog(#"Touched Site header");
if(!siteIsCollapsed)
siteIsCollapsed = YES;
else
siteIsCollapsed = NO;
}
[_tblSearchResults reloadData];
}
This is the best way i found to create expandable table view cells
.h file
NSMutableIndexSet *expandedSections;
.m file
if (!expandedSections)
{
expandedSections = [[NSMutableIndexSet alloc] init];
}
UITableView *masterTable = [[UITableView alloc] initWithFrame:CGRectMake(0,100,1024,648) style:UITableViewStyleGrouped];
masterTable.delegate = self;
masterTable.dataSource = self;
[self.view addSubview:masterTable];
Table view delegate methods
- (BOOL)tableView:(UITableView *)tableView canCollapseSection:(NSInteger)section
{
// if (section>0) return YES;
return YES;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 4;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if ([self tableView:tableView canCollapseSection:section])
{
if ([expandedSections containsIndex:section])
{
return 5; // return rows when expanded
}
return 1; // only top row showing
}
// Return the number of rows in the section.
return 1;
}
- (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] ;
}
// Configure the cell...
if ([self tableView:tableView canCollapseSection:indexPath.section])
{
if (!indexPath.row)
{
// first row
cell.textLabel.text = #"Expandable"; // only top row showing
if ([expandedSections containsIndex:indexPath.section])
{
UIImageView *imView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"UITableContract"]];
cell.accessoryView = imView;
}
else
{
UIImageView *imView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"UITableExpand"]];
cell.accessoryView = imView;
}
}
else
{
// all other rows
if (indexPath.section == 0) {
cell.textLabel.text = #"section one";
}else if (indexPath.section == 1) {
cell.textLabel.text = #"section 2";
}else if (indexPath.section == 2) {
cell.textLabel.text = #"3";
}else {
cell.textLabel.text = #"some other sections";
}
cell.accessoryView = nil;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
}
else
{
cell.accessoryView = nil;
cell.textLabel.text = #"Normal Cell";
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self tableView:tableView canCollapseSection:indexPath.section])
{
if (!indexPath.row)
{
// only first row toggles exapand/collapse
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSInteger section = indexPath.section;
BOOL currentlyExpanded = [expandedSections containsIndex:section];
NSInteger rows;
NSMutableArray *tmpArray = [NSMutableArray array];
if (currentlyExpanded)
{
rows = [self tableView:tableView numberOfRowsInSection:section];
[expandedSections removeIndex:section];
}
else
{
[expandedSections addIndex:section];
rows = [self tableView:tableView numberOfRowsInSection:section];
}
for (int i=1; i<rows; i++)
{
NSIndexPath *tmpIndexPath = [NSIndexPath indexPathForRow:i
inSection:section];
[tmpArray addObject:tmpIndexPath];
}
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (currentlyExpanded)
{
[tableView deleteRowsAtIndexPaths:tmpArray
withRowAnimation:UITableViewRowAnimationTop];
UIImageView *imView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"UITableExpand"]];
cell.accessoryView = imView;
}
else
{
[tableView insertRowsAtIndexPaths:tmpArray
withRowAnimation:UITableViewRowAnimationTop];
UIImageView *imView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"UITableContract"]];
cell.accessoryView = imView;
}
}
}
NSLog(#"section :%d,row:%d",indexPath.section,indexPath.row);
}
So, based on the 'button in header' solution, here is a clean and minimalist implementation:
you keep track of collapsed (or expanded) sections in a property
you tag the button with the section index
you set a selected state on that button to change the arrow direction (like △ and ▽)
Here is the code:
#interface MyTableViewController ()
#property (nonatomic, strong) NSMutableIndexSet *collapsedSections;
#end
...
#implementation MyTableViewController
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (!self)
return;
self.collapsedSections = [NSMutableIndexSet indexSet];
return self;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// if section is collapsed
if ([self.collapsedSections containsIndex:section])
return 0;
// if section is expanded
#warning incomplete implementation
return [super tableView:tableView numberOfRowsInSection:section];
}
- (IBAction)toggleSectionHeader:(UIView *)sender
{
UITableView *tableView = self.tableView;
NSInteger section = sender.tag;
MyTableViewHeaderFooterView *headerView = (MyTableViewHeaderFooterView *)[self tableView:tableView viewForHeaderInSection:section];
if ([self.collapsedSections containsIndex:section])
{
// section is collapsed
headerView.button.selected = YES;
[self.collapsedSections removeIndex:section];
}
else
{
// section is expanded
headerView.button.selected = NO;
[self.collapsedSections addIndex:section];
}
[tableView beginUpdates];
[tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
}
#end
I found another relatively simple way to solve that problem. By using this method we will not required to alter our cell which is almost always related to data array index, potentially causing mess in our view controller.
First, we add this following properties to our controller class:
#property (strong, nonatomic) NSMutableArray* collapsedSections;
#property (strong, nonatomic) NSMutableArray* sectionViews;
collapsedSections will save collapsed section numbers.
sectionViews will store our custom section view.
Synthesize it:
#synthesize collapsedSections;
#synthesize sectionViews;
Initialize it:
- (void) viewDidLoad
{
[super viewDidLoad];
self.collapsedSections = [NSMutableArray array];
self.sectionViews = [NSMutableArray array];
}
After that, we must connect our UITableView so it can be accessed from within our view controller class:
#property (strong, nonatomic) IBOutlet UITableView *tblMain;
Connect it from XIB to view controller using ctrl + drag like usually.
Then we create view as custom section header for our table view by implementing this UITableView delegate:
- (UIView*) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
// Create View
CGRect frame = CGRectZero;
frame.origin = CGPointZero;
frame.size.height = 30.f;
frame.size.width = tableView.bounds.size.width;
UIView* view = [[UIView alloc] initWithFrame:frame];
[view setBackgroundColor:[UIColor blueColor]];
// Add label for title
NSArray* titles = #[#"Title 1", #"Title 2", #"Title 3"];
NSString* selectedTitle = [titles objectAtIndex:section];
CGRect labelFrame = frame;
labelFrame.size.height = 30.f;
labelFrame.size.width -= 20.f;
labelFrame.origin.x += 10.f;
UILabel* titleLabel = [[UILabel alloc] initWithFrame:labelFrame];
[titleLabel setText:selectedTitle];
[titleLabel setTextColor:[UIColor whiteColor]];
[view addSubview:titleLabel];
// Add touch gesture
[self attachTapGestureToView:view];
// Save created view to our class property array
[self saveSectionView:view inSection:section];
return view;
}
Next, we implement method to save our previously created custom section header in class property:
- (void) saveSectionView:(UIView*) view inSection:(NSInteger) section
{
NSInteger sectionCount = [self numberOfSectionsInTableView:[self tblMain]];
if(section < sectionCount)
{
if([[self sectionViews] indexOfObject:view] == NSNotFound)
{
[[self sectionViews] addObject:view];
}
}
}
Add UIGestureRecognizerDelegate to our view controller .h file:
#interface MyViewController : UIViewController<UITableViewDelegate, UITableViewDataSource, UIGestureRecognizerDelegate>
Then we create method attachTapGestureToView:
- (void) attachTapGestureToView:(UIView*) view
{
UITapGestureRecognizer* tapAction = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onTap:)];
[tapAction setDelegate:self];
[view addGestureRecognizer:tapAction];
}
Above method will add tap gesture recognizer to all of section view we created before. Next we should implement onTap: selector
- (void) onTap:(UITapGestureRecognizer*) gestureRecognizer
{
// Take view who attach current recognizer
UIView* sectionView = [gestureRecognizer view];
// [self sectionViews] is Array containing our custom section views
NSInteger section = [self sectionNumberOfView:sectionView];
// [self tblMain] is our connected IBOutlet table view
NSInteger sectionCount = [self numberOfSectionsInTableView:[self tblMain]];
// If section more than section count minus one set at last
section = section > (sectionCount - 1) ? 2 : section;
[self toggleCollapseSection:section];
}
Above method will invoked when user tap any of our table view section. This method search correct section number based on our sectionViews array we created before.
Also, we implement method to get wihch section of header view belongs to.
- (NSInteger) sectionNumberOfView:(UIView*) view
{
UILabel* label = [[view subviews] objectAtIndex:0];
NSInteger sectionNum = 0;
for(UIView* sectionView in [self sectionViews])
{
UILabel* sectionLabel = [[sectionView subviews] objectAtIndex:0];
//NSLog(#"Section: %d -> %# vs %#", sectionNum, [label text], [sectionLabel text]);
if([[label text] isEqualToString:[sectionLabel text]])
{
return sectionNum;
}
sectionNum++;
}
return NSNotFound;
}
Next, we must implement method toggleCollapseSection:
- (void) toggleCollapseSection:(NSInteger) section
{
if([self isCollapsedSection:section])
{
[self removeCollapsedSection:section];
}
else
{
[self addCollapsedSection:section];
}
[[self tblMain] reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationFade];
}
This method will insert/remove section number to our collapsedSections array we created before. When a section number inserted to that array, it means that the section should be collapsed and expanded if otherwise.
Next we implement removeCollapsedSection:, addCollapsedSection:section and isCollapsedSection:section
- (BOOL)isCollapsedSection:(NSInteger) section
{
for(NSNumber* existing in [self collapsedSections])
{
NSInteger current = [existing integerValue];
if(current == section)
{
return YES;
}
}
return NO;
}
- (void)removeCollapsedSection:(NSInteger) section
{
[[self collapsedSections] removeObjectIdenticalTo:[NSNumber numberWithInteger:section]];
}
- (void)addCollapsedSection:(NSInteger) section
{
[[self collapsedSections] addObject:[NSNumber numberWithInteger:section]];
}
This three method is just helpers to make us easier in accessing collapsedSections array.
Finally, implement this table view delegate so our custom section views looks nice.
- (CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 30.f; // Same as each custom section view height
}
Hope it helps.
I've used a NSDictionary as datasource, this looks like a lot of code, but it's really simple and works very well!
how looks here
I created a enum for the sections
typedef NS_ENUM(NSUInteger, TableViewSection) {
TableViewSection0 = 0,
TableViewSection1,
TableViewSection2,
TableViewSectionCount
};
sections property:
#property (nonatomic, strong) NSMutableDictionary * sectionsDisctionary;
A method returning my sections:
-(NSArray <NSNumber *> * )sections{
return #[#(TableViewSection0), #(TableViewSection1), #(TableViewSection2)];
}
And then setup my data soruce:
-(void)loadAndSetupData{
self.sectionsDisctionary = [NSMutableDictionary dictionary];
NSArray * sections = [self sections];
for (NSNumber * section in sections) {
NSArray * sectionObjects = [self objectsForSection:section.integerValue];
[self.sectionsDisctionary setObject:[NSMutableDictionary dictionaryWithDictionary:#{#"visible" : #YES, #"objects" : sectionObjects}] forKey:section];
}
}
-(NSArray *)objectsForSection:(NSInteger)section{
NSArray * objects;
switch (section) {
case TableViewSection0:
objects = #[] // objects for section 0;
break;
case TableViewSection1:
objects = #[] // objects for section 1;
break;
case TableViewSection2:
objects = #[] // objects for section 2;
break;
default:
break;
}
return objects;
}
The next methods, will help you to know when a section is opened, and how to respond to tableview datasource:
Respond the section to datasource:
/**
* Asks the delegate for a view object to display in the header of the specified section of the table view.
*
* #param tableView The table-view object asking for the view object.
* #param section An index number identifying a section of tableView .
*
* #return A view object to be displayed in the header of section .
*/
- (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
NSString * headerName = [self titleForSection:section];
YourCustomSectionHeaderClass * header = (YourCustomSectionHeaderClass *)[tableView dequeueReusableHeaderFooterViewWithIdentifier:YourCustomSectionHeaderClassIdentifier];
[header setTag:section];
[header addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapGesture:)]];
header.title = headerName;
header.collapsed = [self sectionIsOpened:section];
return header;
}
/**
* Asks the data source to return the number of sections in the table view
*
* #param An object representing the table view requesting this information.
* #return The number of sections in tableView.
*/
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
// Return the number of sections.
return self.sectionsDisctionary.count;
}
/**
* Tells the data source to return the number of rows in a given section of a table view
*
* #param tableView: The table-view object requesting this information.
* #param section: An index number identifying a section in tableView.
* #return The number of rows in section.
*/
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
BOOL sectionOpened = [self sectionIsOpened:section];
return sectionOpened ? [[self objectsForSection:section] count] : 0;
}
Tools:
/**
Return the section at the given index
#param index the index
#return The section in the given index
*/
-(NSMutableDictionary *)sectionAtIndex:(NSInteger)index{
NSString * asectionKey = [self.sectionsDisctionary.allKeys objectAtIndex:index];
return [self.sectionsDisctionary objectForKey:asectionKey];
}
/**
Check if a section is currently opened
#param section the section to check
#return YES if is opened
*/
-(BOOL)sectionIsOpened:(NSInteger)section{
NSDictionary * asection = [self sectionAtIndex:section];
BOOL sectionOpened = [[asection objectForKey:#"visible"] boolValue];
return sectionOpened;
}
/**
Handle the section tap
#param tap the UITapGestureRecognizer
*/
- (void)handleTapGesture:(UITapGestureRecognizer*)tap{
NSInteger index = tap.view.tag;
[self toggleSection:index];
}
Toggle section visibility
/**
Switch the state of the section at the given section number
#param section the section number
*/
-(void)toggleSection:(NSInteger)section{
if (index >= 0){
NSMutableDictionary * asection = [self sectionAtIndex:section];
[asection setObject:#(![self sectionIsOpened:section]) forKey:#"visible"];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationFade];
}
}
Expanding on this answer written in Objective C, I wrote the following for those writing in Swift
The idea is to use sections within the table and set the number of rows in the section to 1 (collapsed) and 3(expanded) when the first row in that section is tapped
The table decides how many rows to draw based on an array of Boolean values
You'll need to create two rows in storyboard and give them the reuse identifiers 'CollapsingRow' and 'GroupHeading'
import UIKit
class CollapsingTVC:UITableViewController{
var sectionVisibilityArray:[Bool]!// Array index corresponds to section in table
override func viewDidLoad(){
super.viewDidLoad()
sectionVisibilityArray = [false,false,false]
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
override func numberOfSections(in tableView: UITableView) -> Int{
return sectionVisibilityArray.count
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat{
return 0
}
// numberOfRowsInSection - Get count of entries
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var rowsToShow:Int = 0
if(sectionVisibilityArray[section]){
rowsToShow = 3 // Or however many rows should be displayed in that section
}else{
rowsToShow = 1
}
return rowsToShow
}// numberOfRowsInSection
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
if(indexPath.row == 0){
if(sectionVisibilityArray[indexPath.section]){
sectionVisibilityArray[indexPath.section] = false
}else{
sectionVisibilityArray[indexPath.section] = true
}
self.tableView.reloadSections([indexPath.section], with: .automatic)
}
}
// cellForRowAtIndexPath - Get table cell corresponding to this IndexPath
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell:UITableViewCell
if(indexPath.row == 0){
cell = tableView.dequeueReusableCell(withIdentifier: "GroupHeading", for: indexPath as IndexPath)
}else{
cell = tableView.dequeueReusableCell(withIdentifier: "CollapsingRow", for: indexPath as IndexPath)
}
return cell
}// cellForRowAtIndexPath
}
// -------------------------------------------------------------------------------
// tableView:viewForHeaderInSection:
// -------------------------------------------------------------------------------
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIView *mView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, 20, 20)];
[mView setBackgroundColor:[UIColor greenColor]];
UIImageView *logoView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 5, 20, 20)];
[logoView setImage:[UIImage imageNamed:#"carat.png"]];
[mView addSubview:logoView];
UIButton *bt = [UIButton buttonWithType:UIButtonTypeCustom];
[bt setFrame:CGRectMake(0, 0, 150, 30)];
[bt setTitleColor:[UIColor blueColor] forState:UIControlStateNormal];
[bt setTag:section];
[bt.titleLabel setFont:[UIFont systemFontOfSize:20]];
[bt.titleLabel setTextAlignment:NSTextAlignmentCenter];
[bt.titleLabel setTextColor:[UIColor blackColor]];
[bt setTitle: #"More Info" forState: UIControlStateNormal];
[bt addTarget:self action:#selector(addCell:) forControlEvents:UIControlEventTouchUpInside];
[mView addSubview:bt];
return mView;
}
#pragma mark - Suppose you want to hide/show section 2... then
#pragma mark add or remove the section on toggle the section header for more info
- (void)addCell:(UIButton *)bt{
// If section of more information
if(bt.tag == 2) {
// Initially more info is close, if more info is open
if(ifOpen) {
DLog(#"close More info");
// Set height of section
heightOfSection = 0.0f;
// Reset the parameter that more info is closed now
ifOpen = NO;
}else {
// Set height of section
heightOfSection = 45.0f;
// Reset the parameter that more info is closed now
DLog(#"open more info again");
ifOpen = YES;
}
//[self.tableView reloadData];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:2] withRowAnimation:UITableViewRowAnimationFade];
}
}// end addCell
#pragma mark -
#pragma mark What will be the height of the section, Make it dynamic
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
if (indexPath.section == 2) {
return heightOfSection;
}else {
return 45.0f;
}
// vKj
This action will happen in your didSelectRowAtIndexPath, when you will try to hide or show number of cell in a section
first of all declare a global variable numberOfSectionInMoreInfo in .h file and in your viewDidLoad set suppose to numberOfSectionInMoreInfo = 4.
Now use following logic:
// More info link
if(row == 3) {
/*Logic: We are trying to hide/show the number of row into more information section */
NSString *log= [NSString stringWithFormat:#"Number of section in more %i",numberOfSectionInMoreInfo];
[objSpineCustomProtocol showAlertMessage:log];
// Check if the number of rows are open or close in view
if(numberOfSectionInMoreInfo > 4) {
// close the more info toggle
numberOfSectionInMoreInfo = 4;
}else {
// Open more info toggle
numberOfSectionInMoreInfo = 9;
}
//reload this section
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:1] withRowAnimation:UITableViewRowAnimationFade];
//vKj
Some sample code for animating an expand/collapse action using a table view section header is provided by Apple at Table View Animations and Gestures.
The key to this approach is to implement
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
and return a custom UIView which includes a button (typically the same size as the header view itself). By subclassing UIView and using that for the header view (as this sample does), you can easily store additional data such as the section number.
I have done the same thing using multiple sections .
class SCTierBenefitsViewController: UIViewController {
#IBOutlet private weak var tblTierBenefits: UITableView!
private var selectedIndexPath: IndexPath?
private var isSelected:Bool = false
override func viewDidLoad() {
super.viewDidLoad()
tblTierBenefits.register(UINib(nibName:"TierBenefitsTableViewCell", bundle: nil), forCellReuseIdentifier:"TierBenefitsTableViewCell")
tblTierBenefits.register(UINib(nibName:"TierBenefitsDetailsCell", bundle: nil), forCellReuseIdentifier:"TierBenefitsDetailsCell")
tblTierBenefits.rowHeight = UITableViewAutomaticDimension;
tblTierBenefits.estimatedRowHeight = 44.0;
tblTierBenefits.tableFooterView = UIView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
extension SCTierBenefitsViewController : UITableViewDataSource{
func numberOfSections(in tableView: UITableView) -> Int {
return 7
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (isSelected && section == selectedIndexPath?.section) ? 2 : 1
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 0.01
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return nil
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let cell:TierBenefitsTableViewCell = tableView.dequeueReusableCell(withIdentifier: "TierBenefitsTableViewCell")! as! TierBenefitsTableViewCell
cell.selectionStyle = .none
cell.contentView.setNeedsLayout()
cell.contentView.layoutIfNeeded()
return cell
case 1:
let cell:TierBenefitsDetailsCell = tableView.dequeueReusableCell(withIdentifier: "TierBenefitsDetailsCell")! as! TierBenefitsDetailsCell
cell.selectionStyle = .none
return cell
default:
break
}
return UITableViewCell()
}
}
extension SCTierBenefitsViewController : UITableViewDelegate{
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if indexPath.row == 0 {
if let _selectedIndexPath = selectedIndexPath ,selectedIndexPath?.section == indexPath.section {
tblTierBenefits.beginUpdates()
expandCollapse(indexPath: _selectedIndexPath, isExpand: false)
selectedIndexPath = nil
}
else{
tblTierBenefits.beginUpdates()
if selectedIndexPath != nil {
tblTierBenefits.reloadSections([(selectedIndexPath?.section)!], with: .none)
}
expandCollapse(indexPath: indexPath, isExpand: true)
}
}
}
private func expandCollapse(indexPath: IndexPath?,isExpand: Bool){
isSelected = isExpand
selectedIndexPath = indexPath
tblTierBenefits.reloadSections([(indexPath?.section)!], with: .none)
tblTierBenefits.endUpdates()
}
}
I am adding this solution for completeness and showing how to work with section headers.
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var tableView: UITableView!
var headerButtons: [UIButton]!
var sections = [true, true, true]
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
let section0Button = UIButton(type: .detailDisclosure)
section0Button.setTitle("Section 0", for: .normal)
section0Button.addTarget(self, action: #selector(section0Tapped), for: .touchUpInside)
let section1Button = UIButton(type: .detailDisclosure)
section1Button.setTitle("Section 1", for: .normal)
section1Button.addTarget(self, action: #selector(section1Tapped), for: .touchUpInside)
let section2Button = UIButton(type: .detailDisclosure)
section2Button.setTitle("Section 2", for: .normal)
section2Button.addTarget(self, action: #selector(section2Tapped), for: .touchUpInside)
headerButtons = [UIButton]()
headerButtons.append(section0Button)
headerButtons.append(section1Button)
headerButtons.append(section2Button)
}
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section] ? 3 : 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellReuseId = "cellReuseId"
let cell = UITableViewCell(style: .default, reuseIdentifier: cellReuseId)
cell.textLabel?.text = "\(indexPath.section): \(indexPath.row)"
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
return headerButtons[section]
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 44
}
#objc func section0Tapped() {
sections[0] = !sections[0]
tableView.reloadSections([0], with: .fade)
}
#objc func section1Tapped() {
sections[1] = !sections[1]
tableView.reloadSections([1], with: .fade)
}
#objc func section2Tapped() {
sections[2] = !sections[2]
tableView.reloadSections([2], with: .fade)
}
}
Link to gist: https://gist.github.com/pawelkijowskizimperium/fe1e8511a7932a0d40486a2669316d2c
in support to #jean.timex solution, use below code if you want to open one section at any time. create a variable like: var expandedSection = -1;
func toggleSection(_ header: CollapsibleTableViewHeader, section: Int) {
let collapsed = !sections[section].collapsed
// Toggle collapse
sections[section].collapsed = collapsed
header.setCollapsed(collapsed)
tableView.reloadSections(NSIndexSet(index: section) as IndexSet, with: .automatic)
if (expandedSection >= 0 && expandedSection != section){
sections[expandedSection].collapsed = true
tableView.reloadSections(NSIndexSet(index: expandedSection) as IndexSet, with: .automatic)
}
expandedSection = section;
}