I need some UI advice.
I have a view that needs to load data formatted like this:
{
"heading": "This is a header",
"content": "This is some detailed content about the header"
},
{
"heading": "This is another headline.",
"content": " These are more details about the headline. "
}
Here are the parameters: On load, it should just display the headings in a table view. Tapping a heading will expand that cell and load the the content or details about it. Here is a rough sketch:
On Load:
Heading 1
Heading 2
Heading 3
Heading 4
On Tap of Heading 2:
Heading 1
Heading 2
Content for Heading 2 get displayed here
Heading 3
Heading 4
There also needs to be a bar button item that will either expand or collapse all cells. Which would like this:
Collapse All:
Heading 1
Heading 2
Heading 3
Heading 4
Expand All:
Heading 1
Content for Heading 1 get displayed here
Heading 2
Content for Heading 2 get displayed here
Heading 3
Content for Heading 3 get displayed here
Heading 4
Content for Heading 4 get displayed here
I used some weird parent/child logic to get the individual cells to expand, but I think I went down a dark path because now I am trying to implement the expand/collapse all and I am stuck.
Does anyone know of any open source code that does this type of accordion table view and/or any suggestion on how to setup a view controller to do this? I have seen a few libraries expand and collapse individual cells, but being able to do all of them is getting tricky.
this is how I do it, perhaps a slightly simpler, although definitely similar approach :)
#import "ViewController.h"
//dont worry, the header is empty except for import <UIKit/UIKit.h>, this is a subclass on UIViewController
#interface ViewController ()<UITableViewDataSource, UITableViewDelegate>
#property (weak, nonatomic) UITableView *tableView;
#end
#implementation ViewController
{
//ivars
BOOL sectionIsOpen[4]; //we will use this BOOL array to keep track of the open/closed state for each section. Obviously I have the number of sections fixed at 4 here, but you could make a more dynamic array with malloc() if neccesary..
}
- (void)viewDidLoad {
[super viewDidLoad];
UITableView *tv = [[UITableView alloc]initWithFrame:self.view.bounds style:UITableViewStyleGrouped];
tv.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
tv.dataSource = self;
tv.delegate = self;
[self.view addSubview:tv];
self.tableView = tv;
// Do any additional setup after loading the view, typically from a nib.
}
#pragma mark - UITableViewDataSource
-(NSInteger )numberOfSectionsInTableView:(UITableView *)tableView{
return 4;
}
-(NSInteger )tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return ((sectionIsOpen[section]) ? [self numberOfRowsInSection:section] : 0);
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section{
//put your switch() here...
return [NSString stringWithFormat:#"I am section %i", (int)section ];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellId = #"cellID";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellId];
}
//etc etc decorate your cell...
cell.textLabel.text = [NSString stringWithFormat:#"cell %i / %i", (int)indexPath.section, (int)indexPath.row ];
return cell;
}
#pragma mark - UITableViewDelegate
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{
const CGRect fr = CGRectMake(0, 0, 320.0, 40.0 );
UIButton *btn = [[UIButton alloc]initWithFrame:fr];
[btn setTitle:[self tableView:tableView titleForHeaderInSection:section] forState:UIControlStateNormal ];
[btn setTag:section];
[btn addTarget:self action:#selector(sectionOpenToggle:) forControlEvents:UIControlEventTouchUpInside];
// add an image, colour etc if you like
return btn;
}
#pragma mark - tableViewHelpers
//the number of rows in sectionX when it is open...
-(NSInteger )numberOfRowsInSection:(NSInteger )section{
return section + 1;
}
//opening/closing a section
-(void )setSection:(NSInteger )section toOpen:(BOOL )open{
if (open != sectionIsOpen[section]) {
//build an array of indexPath objects
NSMutableArray *indxPths = [NSMutableArray array];
for (NSInteger i = 0; i < [self numberOfRowsInSection:section]; i ++) {
[indxPths addObject: [NSIndexPath indexPathForRow:i inSection:section ]
];
}
[self.tableView beginUpdates];
if (open) {
[self.tableView insertRowsAtIndexPaths:indxPths withRowAnimation:UITableViewRowAnimationFade];
//nb there is a large ENUM of tableViewRowAnimation types to experiment with..
}else{
[self.tableView deleteRowsAtIndexPaths:indxPths withRowAnimation:UITableViewRowAnimationFade];
}
sectionIsOpen[section] = open;
[self.tableView endUpdates];
}
}
-(void )sectionOpenToggle:(id )sender{
[self setSection:[sender tag] toOpen: !sectionIsOpen[[sender tag]] ];
}
// open/close all sections.
-(void )setAllSectionsOpen:(BOOL )open{
for (NSInteger i = 0; i < [self numberOfSectionsInTableView:self.tableView]; i ++) {
[self setSection:i toOpen:open];
}
}
//these two for your convenience, hook up to navbar items etc..
-(IBAction)openAllSections:(id)sender{
[self setAllSectionsOpen:YES];
}
-(IBAction)closeAllSections:(id)sender{
[self setAllSectionsOpen:NO];
}
#end
I'm not a native english speaker, so if some of my explanations aren't clear enough, tell me, and I'll try to rephrase them.
Anyway, could be better ways to do it, but that was just at the top of my head,
The below code might look scary and complicated, but I personally think it is really
straight-forward,
I've just added a lot of comments, explaining each step, so the code actually not as
long/complicated/messy as it might appear at first glance.
From your example above, it seems to me that your data source is an NSArray which contains NSDictionary objects,
And that each NSDictionary contains one header (section), and only one content (row) for that section,
So the below example is set to handle just that- a table view, with multiple sections, and one row on each section,
Which its data source is NSArray which contains NSDictionary objects.
Since I don't know if your current data source is mutable or not, in my example I will first create an mutable copy of it, and will use it throughout the code.
Throughout the code I will assume self.tableView is your table view, and self.dataArray is the array of dictionaries you've posted above,
I also assume that you've already set the table view's delegate and dataSource to be self, either in code or in storyboard.
// Here we define the height of each header,
// I've arbitrarily chosen 50 points. You can change it as you like.
// The reason I've declare it like this, is that I'm using its
// height to also create a UIView for the header, so this way
// if you want to change the height, you need to only change it once.
#define kHeaderHeight 50
-(void)viewDidLoad {
...
...
// The method below will be called to create an mutable copies of the
// dictionaries in your data source, plus add them another object
// which will indicate in our code if the correspond header
// should be expanded or collapsed
[self createDataSource];
// The below line is not mandatory, but I personally like to add it
// So collapsed sections won't have the row's 'bounds' under them.
self.tableView.tableFooterView = [[UIView alloc] initWithFrame: CGRectZero];
...
...
}
-(void)createDataSource {
// Here we are basically going to create a temporary mutable array,
// then we are going to iterate through self.dataArray array,
// Make a mutable copy of every dictionary in it, Add a BOOL value
// it that indicates if the row is expanded or not, add the new mutable
// dictionary to the temporary array, and then make self.dataArray
// point to an immutable copy of the new array we've created
NSMutableArray *array = [[NSMutableArray alloc] init];
for(int i = 0; i < [self.dataArray count]; i++) {
NSMutableDictionary *dict = [self.dataArray[i] mutableCopy];
[dict setObject:#(NO) forKey:#"expanded"];
[array addObject:dict];
}
self.dataArray = [array copy];
}
// Now we will set the height of each header.
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
return kHeaderHeight;
}
// Here we create a custom view for the header, so we can make its title clickable,
// That will expand/collapse each section
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
// Here we are creating a view for the header, and use our defined header
// height to set its height appropriately.
UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, kHeaderHeight)];
// Then create a button
// I've arbitrarily chosen a size of 100x20 and created a frame to be placed in the
// middle of the above header
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(header.frame.size.width/2 - 50, header.frame.size.height/2 - 10, 100, 20)];
NSString *headerTitle = [self.dataArray[section] objectForKey:#"heading"];
[button setTitle:headerTitle forState:UIControlStateNormal];
// We set the button tag to correspond to its section for future use
button.tag = section;
// I've arbitrarily chose to set the button colour to gray colour
button.titleLabel.textColor = [UIColor grayColor];
// Then we need to actually add an action to the button
[button addTarget:self action:#selector(updateTableView:) forControlEvents:UIControlEventTouchUpInside];
// Then we need to add the button to the header view itself
[header addSubview:button];
return header;
}
// Here we are just setting the number of sections to correspond to the
// number of items we have in our data array
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [self.dataArray count];
}
// Here we are finally using the BOOl value we've added to our dictionary at the
// beginning of our code. If the "expanded" BOOL value is YES, return 1 row,
// else, the section is collapsed, so return 0 rows
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if([[self.dataArray[section] objectForKey:#"expanded"] boolValue]) {
return 1;
} else {
return 0;
}
}
// Here is just a simple method to create the cells that will use our dataArray
// as their source for their title
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *identifier = #"cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
}
cell.textLabel.text = [self.dataArray[indexPath.row] objectForKey:#"content"];
return cell;
}
// This method being called when clicking on the table view's headers
// it uses the button tag we've set earlier, to determine which section we're trying
// to collape/expand, it retrieves the current "expanded" bool value, and then store
// the opposite value back in our dictionary (so if table was collapsed, meaning
// its "expanded" value is NO, after this will run, it will be "YES", which
// will be evaluated after our next line, which tells the table view to reload
// this specific section).
// NOTE that while our dataArray is immutable array, meaning we can't modify it,
// it points to an mutable dictionaries, so we have no problem modifying them.
-(void)updateTableView:(UIButton *)sender {
BOOL expanded = [[self.dataArray[sender.tag] objectForKey:#"expanded"] boolValue];
[self.dataArray[sender.tag] setObject:#(!expanded) forKey:#"expanded"];
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:sender.tag] withRowAnimation:UITableViewRowAnimationFade];
}
// Connect the below methods to your collapse/expand all buttons.
// What the below method actually does, is iterating thru all of the dictionaries
// in dataArray, changes their value to be corresponding to what we are trying to
// do (expand or collapse all) and then calling reloadSections:withRowAnimation:
// on our table view.
// You might wonder why I just don't call reloadData.
// Although it will do the job, I like the following more, because I personally
// feel it gives more 'smooth' update of the UI
-(void)expandAll:(UIButton *)sender {
for(int i = 0; i < [self.dataArray count]; i++) {
[self.dataArray[i] setObject:#(YES) forKey:#"expanded"];
}
[self.tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.dataArray.count)] withRowAnimation:UITableViewRowAnimationFade];
}
-(void)collapseAll:(UIButton *)sender {
for(int i = 0; i < [self.dataArray count]; i++) {
[self.dataArray[i] setObject:#(NO) forKey:#"expanded"];
}
[self.tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.dataArray.count)] withRowAnimation:UITableViewRowAnimationFade];
}
Good luck mate.
Related
I am working on a feature in an app where the user can tap on some attribute about a car (such as its color) and a list of different colors will be shown.
This is just part of the page. The whole page consists of different views and view controllers all wrapped together in a vertical stack view, but I do not think that is what is causing the animation issues here. This is the overall structure of the page:
UIScrollView
UIStackView
UIViewController
UIView
UITableViewController <-- this is the class being shown in this question
...
But for this question, I am just showing the section of the page where this table is.
So I am having an issue with the animation of the table as can be seen in the gifs later on. The way this section is structured is that it is a UITableViewController subclass that takes in a model (a title and a list of car colors) and it displays a different table section for each model element.
Each section has a section header, which is the part where the user taps. It shows the color and it shows a preview image. When that is tapped, a table row is added to the table for that section and that change is animated. The table view row that is added to the table contains a UICollectionView that lays out content horizontally. This idea came from the WWDC 2010 - Mastering Table Views video.
It is basically something like this. Each section header is the interactive part where you can tap on it. Then the row that is shown in the section has a collection view.
* SectionHeader
* SectionHeader
* Table row containing a UICollectionView
* SectionHeader
The problem I am having is that the animation is behaving in a strange manner, as can be seen in the following images.
The first image shows what happens if a row is expanded that has other rows below it. In this case, it appears as if the other two rows in the table are essentially floating above the content of the new row that is animating in. Additionally, at the bottom of the section, those two rows come in from the bottom as the previous two rows sort of get vertically squished until they are gone.
The second animation here shows the bottom row expanding. This one is closer to what I want, except that the row content still briefly shows at the top of the row above the divider (which is the section footer of the previous section).
Here is my code for the table view controller class.
MyAttributeTableViewController.h
#import <UIKit/UIKit.h>
#import "MyAttribute.h"
#import "MyAttributeTableViewCell.h"
NS_ASSUME_NONNULL_BEGIN
#interface MyAttributeTableViewController : UITableViewController<MyAttributeDelegate>
//this represents each section of the table. It contains a title (color, for example), a selected value (red),
//and a list of possible values that will be used to display the collection view when the section is expanded.
#property (strong, nonatomic) NSArray<MyAttribute *> *modelAttributes;
#end
NS_ASSUME_NONNULL_END
MyAttributeTableViewController.m
#import "MyAttributeTableViewController.h"
#import "MyAttributeTableHeaderView.h"
#import "MyAttributeTableViewCell.h"
#interface MyAttributeTableViewController ()
//this keeps track of which sections are expanded. Initially, all sections start out
//not expanded. Then, upon tap, a section's expanded status is toggled here. This is
//used by the table view's data source to know when to display a row in a section.
#property (strong, nonatomic) NSMutableArray<NSNumber *> *itemsExpanded;
#end
#implementation MyAttributeTableViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = UITableViewAutomaticDimension;
self.tableView.sectionHeaderHeight = UITableViewAutomaticDimension;
self.tableView.estimatedSectionHeaderHeight = UITableViewAutomaticDimension;
self.tableView.estimatedSectionFooterHeight = CGFLOAT_MIN;
//this xib file only contains the UICollectionView that is shown upon expanding the section.
//There is nothing that interesting in this file, but I have some screenshots of the
//attribute inspector for this xib file after this code.
[self.tableView registerNib:[UINib nibWithNibName:#"MyAttributeTableViewCell" bundle:nil] forCellReuseIdentifier:#"MyAttributeTableViewCell"];
[self.tableView registerClass:[MyAttributeTableHeaderView class] forHeaderFooterViewReuseIdentifier:#"AttributeHeaderView"];
[self.tableView invalidateIntrinsicContentSize];
}
- (void)setAttributeOptions:(NSArray<MyAttribute *> *)modelAttributes {
self->_modelAttributes = modelAttributes;
self->_itemsExpanded = [NSMutableArray arrayWithCapacity:[self->_modelAttributes count]];
for (NSUInteger x=0; x<[self->_modelAttributes count]; x++) {
[self->_itemsExpanded addObject:#NO];
}
[self.tableView reloadData];
}
//TODO: is this necessary?
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.preferredContentSize = self.tableView.contentSize;
}
#pragma mark - MyAttributeDelegate
- (void)twister:(MyAttributeTableViewCell *)cell didSelectItemAtIndex:(NSInteger)index {
UITableViewHeaderFooterView *headerView = [self.tableView headerViewForSection:cell.sectionIndex];
if ([headerView isKindOfClass:[MyAttributeTableHeaderView class]]) {
MyAttributeTableHeaderView *twisterHeaderView = (MyAttributeTableHeaderView *)headerView;
twisterHeaderView.twister.selectedSwatchIndex = [NSNumber numberWithInteger:index];
[twisterHeaderView updateAttributeIfNeeded];
}
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return self.modelAttributes.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.itemsExpanded[section].boolValue ? 1 : 0;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
MyAttributeTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyAttributeTableViewCell" forIndexPath:indexPath];
MyAttribute *twister = self.modelAttributes[indexPath.section];
cell.swatches = twister.swatches;
NSNumber *swatchCellHeight = cell.swatchCellHeight;
if (swatchCellHeight) {
return swatchCellHeight.floatValue + 20.0f; //TODO: need to get this from the collection view
}
return 352.0f;
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
if (section == self.modelAttributes.count) {
return CGFLOAT_MIN;
}
return 1.0f;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MyAttributeTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyAttributeTableViewCell" forIndexPath:indexPath];
MyAttribute *twister = self.modelAttributes[indexPath.section];
cell.swatches = twister.swatches;
cell.sectionIndex = indexPath.section;
cell.delegate = self;
if ([twister isSwatchSelected]) {
NSInteger selectedIndex = twister.selectedSwatchIndex.integerValue;
[cell.collectionView selectItemAtIndexPath:[NSIndexPath indexPathForItem:selectedIndex inSection:0] animated:YES scrollPosition:UICollectionViewScrollPositionCenteredHorizontally];
}
return cell;
}
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
MyAttributeTableHeaderView *cell = (MyAttributeTableHeaderView *)[tableView dequeueReusableHeaderFooterViewWithIdentifier:#"AttributeHeaderView"];
if (cell.gestureRecognizers.count == 0) {
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(toggleSection:)];
[cell addGestureRecognizer:tapRecognizer];
}
cell.sectionIndex = section;
cell.expanded = self.itemsExpanded[section].boolValue;
cell.twister = self.modelAttributes[section];
return cell;
}
- (void)toggleSection:(UITapGestureRecognizer *)gesture {
MyAttributeTableHeaderView *destinationView = ((MyAttributeTableHeaderView *)gesture.view);
BOOL expanded = [destinationView toggleExpanded];
self.itemsExpanded[destinationView.sectionIndex] = [NSNumber numberWithBool:expanded];
UIView *viewToLayout = self.tableView;
while ([viewToLayout superview]) {
viewToLayout = viewToLayout.superview;
}
if (expanded) {
[UIView beginAnimations:#"expandTableAnimationId" context:nil];
[UIView setAnimationDuration:1.0f];
[CATransaction begin];
[self.tableView beginUpdates];
[self.tableView insertRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:0 inSection:destinationView.sectionIndex]] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
[viewToLayout layoutIfNeeded];
[CATransaction commit];
[UIView commitAnimations];
} else {
[UIView beginAnimations:#"collapseTableAnimationId" context:nil];
[UIView setAnimationDuration:1.0f];
[CATransaction begin];
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:#[[NSIndexPath indexPathForRow:0 inSection:destinationView.sectionIndex]] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
[viewToLayout layoutIfNeeded];
[CATransaction commit];
[UIView commitAnimations];
}
}
#end
MyAttributeTableViewCell
Does anyone know what I am doing wrong or how to get these animations to look correct (no doubling of rows and no collection view showing up above the row when it is animating in)? Or if you know a better way to handle this that might be less complicated, I am open to that as well. I am just trying to have a list of expandable sections, where each section has a collection view of selectable items.
I created one tableview with 2 section and i displayed the array of the data in the tableview .. now i want to expand and collapse the section…
i am just a beginner please give any sample code for expand and collapse the tableview section…
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [arraytable count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellitem = #"simpletableitem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellitem];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellitem];
}
cell.textLabel.text = [arraytable objectAtIndex:[indexPath row]];
return cell;
}
This is my cellForRowAtIndexPath and arraytable is my array i give it in view did load
arraytable = #[#"hari12",#"narayanan",#"Praba",#"Deepak",#"Sailesh",#"Ram charan"];
Here's a sample code I've made to achieve what you want.
You can use this as a general guideline, since there are plenty of ways to achieve this, other than what I've done.
I've noticed from your example above that you have a table view with 2 sections, so this is what I'll use for the sample code.
There are maybe better ways to implement the below, but that was at the top of my head, and I think it's pretty simple and straightforward.
I've also included explanations as comments in the code below.
Note that you'll probably need to change some variables' names to fit your code (such as self.tableView, if you use something else) and section1DataArray, section2DataArray
// I've declared 2 BOOL properties to hold whether each section is visible or not
#interface ViewController ()
#property (nonatomic) BOOL section1visible, section2visible;
#end
-(void)viewDidLoad {
// After creating the table view, I've set the footer view frame to CGRectZero,
// so when a table view is collapsed you won't have the table columns 'bounds'
self.tableView.tableFooterView = [[UIView alloc] initWithFrame: CGRectZero];
}
// Then I've created a custom header for each table since I've wanted to make the header
// name a button which collapse/expand the table view.
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
// Here I've set the height arbitrarily to be 50 points
return 50;
}
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
// Change the below frame to fit your needs. In my example, I've used a table view
// to be at the width of the screen, and the height of 50 points (as we've set above)
UIView *header = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 50)];
// Then create a button
// I've arbitrarily chosen a size of 100x20 and created a frame to be placed in the
// middle of the above header
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(header.frame.size.width/2 - 50, header.frame.size.height/2 - 10, 100, 20)];
// Now I'm setting the tag (for later use) and title of each button
if(section == 0) { // First section
[button setTitle:#"Section 1" forState:UIControlStateNormal];
button.tag = 0;
} else { // Else, second section, since we only have two
[button setTitle:#"Section 2" forState:UIControlStateNormal];
button.tag = 1;
}
// I've arbitrarily chose to set the button colour to gray colour
button.titleLabel.textColor = [UIColor grayColor];
// Then we need to actually add an action to the button
[button addTarget:self action:#selector(updateTableView:) forControlEvents:UIControlEventTouchUpInside];
// Then we need to add the button to the header view itself
[header addSubview:button];
return header;
}
// Then we need to update our tableView:numberOfRowsInSection: to check for our BOOL value
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if(section == 0 && self.section1visible) {
return [self.section1DataArray count];
} else if (section == 1 && self.section2visible) {
return [self.section2DataArray count];
} else {
return 0;
}
}
// Then we need to create the actual method the button calls
-(void)updateTableView:(UIButton *)sender {
// Check the button tag, to identify which header button was pressed
NSInteger section = sender.tag;
if(section == 0) {
self.section1visible = !self.section1visible;
} else { // section == 1
self.section2visible = !self.section2visible;
}
// Use an animation to update the UI to create a 'smooth' expand/collapse
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:section] withRowAnimation:UITableViewRowAnimationFade];
}
Good luck mate.
I have a regular view controller (not a table view controller) and on that view I have a tableview. My table view cells are custom and I just made them through storyboards (didn't do anything in the code) but when I run my application, the tableview is blank. Any ideas as to why tho is happening to me? I have looked at other things on here but all of these other scenarios have to do with the person using an NSArray to fill out the tableview in the code, but mine is custom so I am not doing that. Thanks for any help. And before you mark this duplicate, please actually read this.
my code is as follows:
#interface TTViewController ()
{
NSArray *messageComponents;
}
#end
#implementation TTViewController
#synthesize dateTimePicker, messageSetupTableView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.title = #"Message Setup";
messageComponents = [[NSArray alloc] initWithObjects:#"Recipient",#"Message", #"Date",#"haha", nil];
messageSetupTableView.backgroundColor = [UIColor groupTableViewBackgroundColor];
messageSetupTableView.alpha = 0.9;
}
#pragma mark -
#pragma mark Table view data source
-(NSInteger) numberOfSectionsInTableView:(UITableView *) tableView{
return [messageComponents count];
}
-(NSInteger)tableView:(UITableView *) tableVew numberOfRowsInSection:(NSInteger)section{
return 1;
}
// Customize the appearance of table view cells.
- (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];
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
}
}
// Configure the cell.
cell.textLabel.text = [messageComponents objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:[messageComponents objectAtIndex:indexPath.row]];
cell.backgroundColor = [UIColor redColor];
cell.textLabel.textColor = [UIColor whiteColor];
UIColor *selectedColor = [UIColor blueColor];
UIView *myBackgroundColor = [[UIView alloc] init];
[myBackgroundColor setBackgroundColor:selectedColor];
[cell setSelectedBackgroundView:myBackgroundColor];
return cell;
I want my table view to have a date picker in one section, a textview in the other, and a button and a few text fields in the other. Thanks
A couple of suggestions:
First make sure that UITableView's delegate and datasource are mapped or not either from IB or programmatically.
Put a break point on cellForRowAtIndexPath delegate function and see if it comes in this functions, and make sure you are using this function to create your custom cells.
Double check your have at least one row in your tableView using something similar:
- (NSInteger)numberOfRowsInSection:(NSInteger)section
{
return 5; // for test pass value 5 so tableView should aware to display 5 rows.
}
Hope it helps!
I have a table view of custom cells and some buttons in each cell.Clicking on any of the button inside the cell will reveal another custom view below that cell.Next click on the same button will collapse the view and need this same for all cells.I tried with insertrow method on the button click but in vain.How can i do this with using only the table view delegates.
This is what i tried:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"CustomCell_For_Dashboard";
CustomCellFor_Dashboard *customCell = (CustomCellFor_Dashboard *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (customCell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomCellFor_Dashboard" owner:self options:nil];
customCell = [nib objectAtIndex:0];
}
[customCell.howyoulfeelBtn addTarget:self action:#selector(buttonclicked:) forControlEvents:UIControlEventTouchUpInside];
customCell.nameLabel.text = #"test";
customCell.imgView.image = [UIImage imageNamed:#"Default.png"];
// customCell.prepTimeLabel.text = [prepTime objectAtIndex:indexPath.row];
return customCell;
}
-(void)buttonclicked:(id)sender{
NSIndexPath *indexPath = [myTable indexPathForCell:sender];
[myTable beginUpdates];
NSIndexPath *insertPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
[myTable insertRowsAtIndexPaths:[NSArray arrayWithObject:insertPath] withRowAnimation:UITableViewRowAnimationTop];
}
can anyone help me?
I got the same task on one project with just one thing different: There were no buttons, just tapping on cell will expand or collapse it.
There are several things you should edit in your code. First, the button method code will look something like this:
- (void) collapseExpandButtonTap:(id) sender
{
UIButton* aButton = (UIButton*)sender; //It's actually a button
NSIndexPath* aPath = [self getIndexPathForCellWithButtonByMagic:aButton];
//expandedCells is a mutable set declared in your interface section or private class extensiont
if ([expandedCells containsObject:aPath])
{
[expandedCells removeObject:aPath];
}
else
{
[expandedCells addObject:aPath];
}
[myTableView beginEditing];
[myTableView endEditing]; //Yeah, that old trick to animate cell expand/collapse
}
Now the second thing is UITableViewDelegate method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([expandedCells containsObject:indexPath])
{
return kExpandedCellHeight; //It's not necessary a constant, though
}
else
{
return kNormalCellHeigh; //Again not necessary a constant
}
}
Key thing here is to determine if your cell should be expanded/collapsed and return right height in delegate method.
Going off of what #eagle.dan.1349 said, this is how to do it on the clicking of the cell. In storyboard, you also need to set the table cell to clip subviews, otherwise the content that would be hidden will show.
.h
#property (strong, nonatomic) NSMutableArray *expandedCells;
.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.expandedCells containsObject:indexPath])
{
[self.expandedCells removeObject:indexPath];
}
else
{
[self.expandedCells addObject:indexPath];
}
[tableView beginUpdates];
[tableView endUpdates];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
CGFloat kExpandedCellHeight = 150;
CGFloat kNormalCellHeigh = 50;
if ([self.expandedCells containsObject:indexPath])
{
return kExpandedCellHeight; //It's not necessary a constant, though
}
else
{
return kNormalCellHeigh; //Again not necessary a constant
}
}
Saw this post and just wanted to give my 2 cents as my solution to this is very similar to the chosen answer (the tapping of a whole area).
Many people architect this by using just cells alone, but I believe there is a way to build this that might align better with what people are trying to achieve:
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.
Link: https://github.com/fuzz-productions/FZAccordionTableView
I also had a same situation and my solution was to put a button on top of the Section Title with viewForHeaderInSection method.
noOfRows defines how many rows are there in each section and button.tag keeps which button of section is pressed.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
UIButton *btnSection = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, tableView.frame.size.height)];
btnSection.tag = section;
[btnSection setTitle:[sectionArray objectAtIndex:section] forState:UIControlStateNormal];
[btnSection addTarget:self action:#selector(sectionButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
return btnSection;
}
- (void)sectionButtonTapped:(UIButton *)button {
sectionIndex = button.tag;
if (button.tag == 0) {
noOfRows = 3;
} else if (button.tag == 1) {
noOfRows = 1;
} else if (button.tag == 2) {
noOfRows = 2;
}
[self.tableView reloadData];
}
Hope this will help you..
I am parsing XML that gets the first 25 items in my MySQL database using PHP - LIMIT and GET. When I click on the "Load More" cell that I append to the bottom of my tableview, it successfully gets the next 25 items, but only loads the first 40 and leaves off the last 10. Each time I click on the "Load more" cell it add 25 to my range (ie 0-25,25-50), but it seems that my range caps at 65 and the display caps at 40.
Here is my load more function thats not working:
-(void) getNewRange{
int currentRange = [allItems count];
int newRange = currentRange + 25;
if(newRange > [xmlParser total]){
NSLog(#"evaluating as greater than the total, which is 837");
newRange = [xmlParser total];
}
NSString *range = [[NSString alloc] initWithFormat:#"?range=%d&range2=%d",currentRange,newRange];
NSString *newUrl =[[NSString alloc] initWithFormat:#"http://localhost/fetchAllTitles.php%#",range];
XMLParser *tempParser = [[XMLParser alloc] loadXMLByURL:newUrl];
[allItems addObjectsFromArray:[tempParser people]];
NSMutableArray *newCells = [[NSMutableArray alloc] initWithCapacity:25];
for(int i=currentRange;i<newRange;i++){
NSLog(#"%d",i);
NSIndexPath *indexpath=[NSIndexPath indexPathForRow:i inSection:0];
[newCells addObject:indexpath];
}
NSLog(#"%#",newUrl);
[self.tableView insertRowsAtIndexPaths:newCells withRowAnimation:UITableViewRowAnimationAutomatic];
}
I'm getting closer, but I get this new error:
*** Assertion failure in -[_UITableViewUpdateSupport _computeRowUpdates], /SourceCache/UIKit_Sim/UIKit-1912.3/UITableViewSupport.m:386
Read up on how you can Reuse your table view's cells.
Your data does not need to be 'owned' by the cell.
UITableView isn't a class to contain your data, and you shouldn't try to directly micromanage what cells it displays. As another poster stated, read up on how to use it. What you should do is:
-(void)loadNewData
{
NSIndexPath *index;
XMLParser *tempParser = [[XMLParser alloc] loadXMLByURL:newUrl];
NSArray *people=[tempParser people];
for(id *person in people)
{
[self.dataArray addObject:person];
indexPath=[NSIndexPath indexPathForRow:[self.dataArray indexForObject:person] inSection:0];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [AnArray count];//use whatever array stores your data
}
//If you've subclassed the cell, adjust appropriately.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell=[tableView dequeueReusableCellWithIdentifier:#"cell"];
if (!cell) {
cell=[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
//Customize the cell
return cell;
}
The table view will take care of all the logic involved in displaying the cells, if you let it. This way, you only have a limited number of cells taking up memory at any given time, and you don't have to handle that -- the table view automagically handles reusing the cells, and knowing how many are needed as a buffer before / after.
you should not set numberOfRowsInSection inside your method. The number of rows should get returned from the tableView's datasource method - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section. Just return [allItems count] there.