Reuse error tableView with cell design programmatically - ios

I have a problem with my tableView who I managed specially, i need to delete and add row really often. My cell are designed programmatically. I update my array who depend my cells and called self.tableView.reloadData() but this don't remove the cells I need and update the tableView like my array.
Cause to the reuse and my design of cell (programmatically) I need to check if the cell is always design or not. And the problem come from here.
When I called tableView.reloadData() my data are not properly reload, so I need to delete All view in the cells: indicate that the cells are not design, to design the new cell ... Of course I can just update the visible cells (with tableView.visibleCells), so this work but how can I update my other not-visible cells ?
Maybe I have an architecture problem? If so, what is the best way to delete and insert a row in the TableView with a indexPath defined? Or, how programmatically design the cell only one time?
Code:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return user.lobbySurvey.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:"Celll") as! CardCell
for survey in user.lobbySurvey{
let index = user.lobbySurvey.index(where: {
//get the current index is nedeed else the cells reuse lazy
$0 === survey
})
if indexPath.row == index{
var surveyState : UserSurvey.state
surveyState = survey.state
switch surveyState{
case .selectSurvey:
cell.drawCard(statutOfCard: .selectSurvey)
case .goSurvey:
cell.drawCard(statutOfCard: .goSurvey(picture: survey.picture))
case .surveyEnded:
print("survey Ended")
case .surveyWork:
print("survey in progress to vote")
case .surveyWaiting:
cell.drawCard(statutOfCard: .surveyWaiting(selfSurveyId: survey.id, timeLeft: survey.timeLeft, picture: survey.picture))
case .buyStack:
cell.drawCard(statutOfCard: .buyStack(supView : self.view))
}
}
}
cell.delegate = self
cell.delegateCard = self
cell.layer.backgroundColor = UIColor.clear.cgColor
cell.backgroundColor = .clear
tableView.backgroundColor = .clear
tableView.layer.backgroundColor = UIColor.clear.cgColor
return cell
}

You can have an array which is the model of your table:
NSMutableArray *model; (model can have identifier).
You can change this model whenever you want.
With this you can make your table dynamic just calling tableView.reloadData() and make whatever in cellForRow & heightForRow
- (CGFloat) tableView: (UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat height;
YourModel *model = self.model[indexPath.row];
if ([model isKindOfClass:[SomeClassTableViewCellModel class]]) {
height = 50;
} else if([model.identifier isEqualToString:#"Whatever"]){
height = 0.0f;
else{
height = kHeightForNormalCells;
}
return height;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
YourModel *model = self.model[indexPath.row];
cell = [tableView dequeueReusableCellWithIdentifier:model.identifier forIndexPath:indexPath];
if ([cell isKindOfClass:[SomeClass class]]) {
NSLog(#"Configure Cell");
[cell setModel:model];
}
return cell;
}
Moreover, your cell should have a method setModel:
- (void)setModel:(SomeTableViewCellModel *)model {
_model = model;
self.label1.text = [NSString stringWithFormat:#"%#",model.value1];
self.label2.text = [NSString stringWithFormat:#"%#",model.value2];
}
Hope this helps you.

Related

How to detect last visible cell in UITableView

I am new in iOS development and currently working on UITableView. I want to find last visible cells on the screen of device and cells that are at the bottom of the screen must be of blue color, which should fade to green as the cell is scrolled to the top of the screen.
I have gone through these links
Link1
Link2
But could not get success. Can anyone please provide idea how to detect last cells & cell fade animation?
Get last visible cell:
if let lastCell = tableView.visibleCells.last {
// do something with lastCell
}
In Swift 3.0, you can used this tableview method.
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)
{
let intTotalrow = tableView.numberOfRows(inSection:indexPath.section)//first get total rows in that section by current indexPath.
//get last last row of tablview
if indexPath.row == intTotalrow - 1{
// call for last display
}
}
#shallowThought solution will only work if cells are already presented.
But, if you want to know the last cell when cells aren't presented yet and are going to be presented, we can create an extension for UITableView as follow:
func isLastVisibleCell(at indexPath: IndexPath) -> Bool {
guard let lastIndexPath = indexPathsForVisibleRows?.last else {
return false
}
return lastIndexPath == indexPath
}
This way, you can check tableView.isLastVisibleCell(...) multiple times until you have reached actual visible cell.
Try this code, It will work
Initially Declare
int firstIndexRow;
int lastIndexRow;
Write below code inside of ViewDidLoad()
[myTable reloadData]; //Reload because get visible last cell index row
firstIndexRow = 0;
lastIndexRow = (int)[self.myTable.indexPathsForVisibleRows lastObject].row;
NSLog(#"first : %d",firstIndexRow);
NSLog(#"Bottom : %d",lastIndexRow);
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
NSIndexPath *firstVisibleIndexPath = [[self.myTable indexPathsForVisibleRows] objectAtIndex:0];
NSIndexPath *lastObject = [self.myTable.indexPathsForVisibleRows lastObject];
firstIndexRow = (int)firstVisibleIndexPath.row;
lastIndexRow = (int)lastObject.row;
NSLog(#"first : %d",firstIndexRow);
NSLog(#"Bottom : %d",lastIndexRow);
[myTable reloadData];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [myTable dequeueReusableCellWithIdentifier:#"cell"];
if (indexPath.row == firstIndexRow) {
cell.textLabel.textColor = [UIColor blueColor];
}else if (indexPath.row == lastIndexRow) {
cell.textLabel.textColor = [UIColor greenColor];
}else{
cell.textLabel.textColor = [UIColor grayColor];
}
cell.textLabel.text =[namesArray objectAtIndex:indexPath.row];
return cell;
}

UITableView self sizing cell scrolling issues with big difference of cell height

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let json = JSON(data: activityTableView.onlineFeedsData)[indexPath.row] // new
if(json["topic"]["reply_count"].int > 0){
if let cell: FeedQuestionAnswerTableViewCell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier_reply) as? FeedQuestionAnswerTableViewCell{
cell.selectionStyle = .None
cell.tag = indexPath.row
cell.relatedTableView = self.activityTableView
cell.configureFeedWithReplyCell(cell, indexPath: indexPath, rect: view.frame,key: "activityTableView")
// Make sure the constraints have been added to this cell, since it may have just been created from scratch
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.mainScreen().scale
return cell
}
}else{
if let cell: FeedQuestionTableViewCell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as? FeedQuestionTableViewCell{
cell.selectionStyle = .None
cell.tag = indexPath.row
cell.relatedTableView = self.activityTableView
cell.configureFeedCell(cell, indexPath: indexPath, rect: view.frame)
// Make sure the constraints have been added to this cell, since it may have just been created from scratch
cell.layer.shouldRasterize = true
cell.layer.rasterizationScale = UIScreen.mainScreen().scale
return cell
}
}
return UITableViewCell()
}
There is difference of 200-400 in height in each cell,So tried implementing the height calculation in estimatedHeightForRowAtIndexPath .I cant the exact estimate height in this method as bcz of calculation
and caching the height in willDisplayCell method but still the scroll jumps like is it taking time to rendering .
The cell is like the Facebook card type layout with lots of dynamic data with variable height text and images.Can someone help me in this .Thanks
If you're able to calculate each cells height, just use the tableView(_:heightForRowAtIndexPath) instead of estimatedRowHeightAtIndexPath property. estimatedRowHeightAtIndexPath is mostly used in smthg like selfsizing cells etc.
Try this one. I hope this could help you. It's worked for me. Actually this calculates the cell height before it displayed.
Thanks.
NSMutableDictionary *cellHeightsDictionary;
// Put this in viewDidLoad
cellHeightsDictionary = #{}.mutableCopy;
// UITableview delegate Methods
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
NS_AVAILABLE_IOS(7_0)
{
NSNumber *height = [cellHeightsDictionary objectForKey:indexPath];
if (height) return height.doubleValue;
return UITableViewAutomaticDimension;
}
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
[cellHeightsDictionary setObject:#(cell.frame.size.height) forKey:indexPath];
}

How to reload a UITableView after a segmented control changed?

I am trying to control context of tableview with segmented control(which is above tableview). On change segment I want to reload data in my table tableView with different cells ( I have two types of cells and two data arrays for every type). How to clear and reload data with different cell ?
I'm just providing a few lines of code to what #jaydee3 has mentioned.
- (IBAction)segmentControlValueChanged:(UISegmentedControl *)sender
{
//Do something
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.segmentControl.selectedSegmentIndex?[self.dataArray2 count]:[self.dataArray1 count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(self.segmentControl.selectedSegmentIndex){
// Create Type cell for selected Index 1
}
else{
// Create Type cell for selected Index 0
}
}
In your tableView delegate, check what is selected in the segmented control and return the correct cells.
If the segmented control gets changed, call reloadData on the tableView.
1.Below is most important line if you want to change the data in 2 or more tableview.
#IBAction func segmentValueChanged(_ sender: UISegmentedControl)
{
self.<YourTableViewName>.reloadData()
}
2.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var index = <segmentObjectCreated>.selectedSegmentIndex
if index == 0 {
return <table1ArrayList>.count
}
else{
return <table12ArrayList>.count
}
}
3.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = <TableViewObj>.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
var index = <segmentObjectCreated>.selectedSegmentIndex
if index == 0 {
cell.textLabel?.text = <table1ArrayList>[indexPath.row].productName
}
else {
cell.textLabel?.text = <table2ArrayList>[indexPath.row].newLaunch
}
return cell
}
That's it, Happy Coding!

How to let user to modify the text in UITableView cells

I have a question regarding uitable view.
I am implementing an app which is similar to the address book app.I am able to present the table view in editing mode. I want to let the user to edit the text in the cells in editing mode. I know that in order to edit the text in the cells, I need a textfield. I have created a textfield.
My question is:
what should I do in order to present that textfield in the cells.
what are the methods I need to implement in order to present that text field in the table view in editing mode.
Once I am done with editing ,How can I update the data which is in my contacts view controller(contains all the contacts).The saving should persist in the address book. For this question I know that I need to implement some delegate method,But I am not sure how to do that.
Please have a look at the following code,so that you will have an idea about my problem.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
[tableView setSeparatorColor:[UIColor clearColor]];
//[self.tableView setEditing: YES animated: YES];
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
if(isEditingOn) {
if(cell == nil)
cell = [self getCellContentView:CellIdentifier];
UILabel *lblTemp1 = (UILabel *)[cell viewWithTag:1];
UITextField *textfield1=(UITextField*)[cell viewWithTag:2];
if(indexPath.row == 0) {
lblTemp1.text = #"Name";
textfield1.text = myContact.name;
}
else if(indexPath.row == 1) {
lblTemp1.text = #"Phone";
textfield1.text = myContact.phone;
}
else if(indexPath.row == 2) {
lblTemp1.text = #"Email";
textfield1.text = myContact.email;
}
}
else {
if(indexPath.row == 0) {
cell.textLabel.text = myContact.name;
}
else if(indexPath.row == 1) {
cell.textLabel.text = myContact.phone;
}
else if(indexPath.row == 2) {
cell.textLabel.text = myContact.email;
}
}
return cell;
}
- (UITableViewCell *) getCellContentView:(NSString *)cellIdentifier {
CGRect CellFrame = CGRectMake(0, 0, 60, 20);
CGRect Label1Frame = CGRectMake(10, 10, 180, 25);
UILabel *lblTemp;
UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CellFrame reuseIdentifier:cellIdentifier] autorelease];
lblTemp = [[UILabel alloc] initWithFrame:Label1Frame];
lblTemp.tag = 1;
[cell.contentView addSubview:lblTemp];
[lblTemp release];
CGRect TextFieldFrame=CGRectMake(240, 10, 60, 25);
UITextField *textfield;
textfield=[[UITextField alloc]initWithFrame:TextFieldFrame];
textfield.tag=2;
textfield.placeholder = #"";
[cell.contentView addSubview:textfield];
}
This is a really complex question to answer this fully and in-depth with code examples, but I'll try to point you in the right direction.
1) Add a UITextField as a subview of your table cell when you create the cell in the tableView:cellForRowAtIndexPath: method (I assume that's what your getCellContentView: method is for). Set a tag on your UITextField that matches the row index of the cell and make your tableviewcontroller the delegate for the cell. Set the textfield to hidden. (remember to set the tag each time the cell is requested, not just the first time you create it).
2) In the tableView:didSelectRowAtIndexPath: method, grab the cell using tableViewCellForRowAtIndexPath and then show the textfield inside it (you may have to do some view traversal to get it) and call becomeFirstResponder on the textfield.
3) When the user has typed something, your textfielddelegate methods will be fired. You can look at the tag on the textfield to work out which row the field belongs to and then update the dat source with the new text. Then just reload the table to hide the textfield and update the cell content.
If you know how to use custom table cell subclasses then you can make your life a bit easier by creating a custom cell that already contains a textfield and has an property for accessing it, but otherwise the technique will be mostly the same.
Also, tableView:didSelectRowAtIndexPath: won't normally fire when a tableview is in edit mode unless you set tableView.allowsSelectionDuringEditing = YES;
It's better to use 2 UITableViewCells, The first one for view and the last for edit mode.
Also we will depend on the variable rowToEdit which refers to the current editing row. (in my case one cell is allowed to be edited at the same time)
let us begin:
First I depend on accessoryButtonTap action to edit the row:
var rowToEdit: IndexPath? = nil
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
// End edit mode if one cell being in edit mode other than this one
if let row = self.rowToEdit {
// return current edit cell to view mode
self.rowToEdit = nil
self.tableView.reloadRows(at: [row], with: .automatic)
}
self.rowToEdit = indexPath
self.tableView.reloadRows(at: [self.rowToEdit!], with: .automatic)
}
Differentiate between the 2 modes when you will load the cell:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath == self.rowToEdit {
let cellId = "ContactEditTableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath as IndexPath) as! ContactEditTableViewCell
cell.accessoryType = .none
self.configEditCell(cell: cell, indexPath: indexPath)
return cell
} else {
let cellId = "ContactTableViewCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath as IndexPath) as! ContactTableViewCell
self.configCell(cell: cell, indexPath: indexPath)
return cell
}
}
Additional option if you want to change the height based on mode:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath == self.rowToEdit {
return 120
} else {
return 70
}
}
Last option to add Save and Cancel buttons:
I added them to each cell, So I pass a reference to the ContactTable to each cell.
#IBAction func btnSave_click(_ sender: UIButton) {
// save the record
btnCancel_click(sender)
}
#IBAction func btnCancel_click(_ sender: UIButton) {
let tmp = self.tbl.rowToEdit
self.tbl.rowToEdit = nil
self.tbl.tableView.reloadRows(at: [tmp!], with: .automatic)
}

UITableView set to static cells. Is it possible to hide some of the cells programmatically?

UITableView set to static cells.
Is it possible to hide some of the cells programmatically?
To hide static cells in UITable:
Add this method:
In your UITableView controller delegate class:
Objective-C:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell* cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
if(cell == self.cellYouWantToHide)
return 0; //set the hidden cell's height to 0
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
Swift:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var cell = super.tableView(tableView, cellForRowAtIndexPath: indexPath)
if cell == self.cellYouWantToHide {
return 0
}
return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
}
This method will get called for each cell in the UITable. Once it calls it for the cell you want to hide, we set its height to 0. We identify the target cell by creating an outlet for it:
In the designer, create an outlet for the cell(s) you want to hide. The outlet for one such cell is called "cellYouWantToHide" above.
Check "Clip Subviews" in the IB for the cells you want to hide. The cells you are hiding need to have ClipToBounds = YES. Otherwise the text will pile up in the UITableView.
You are looking for this solution :
StaticDataTableViewController 2.0
https://github.com/xelvenone/StaticDataTableViewController
which can show/hide/reload any static cell(s) with or without animation!
[self cell:self.outletToMyStaticCell1 setHidden:hide];
[self cell:self.outletToMyStaticCell2 setHidden:hide];
[self reloadDataAnimated:YES];
Note to always use only (reloadDataAnimated:YES/NO)
(dont call [self.tableView reloadData] directly)
This doesn't use the hacky solution with setting height to 0 and allows you to animate the change and hide whole sections
The best way is as described in the following blog
http://ali-reynolds.com/2013/06/29/hide-cells-in-static-table-view/
Design your static table view as normal in interface builder –
complete with all potentially hidden cells. But there is one thing you
must do for every potential cell that you want to hide – check the
“Clip subviews” property of the cell, otherwise the content of the
cell doesn’t disappear when you try and hide it (by shrinking it’s
height – more later).
SO – you have a switch in a cell and the switch is supposed to hide
and show some static cells. Hook it up to an IBAction and in there do
this:
[self.tableView beginUpdates];
[self.tableView endUpdates];
That gives you nice animations for the cells appearing and
disappearing. Now implement the following table view delegate method:
- (float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 1 && indexPath.row == 1) { // This is the cell to hide - change as you need
// Show or hide cell
if (self.mySwitch.on) {
return 44; // Show the cell - adjust the height as you need
} else {
return 0; // Hide the cell
}
}
return 44;
}
And that’s it. Flip the switch and the cell hides and reappears with a
nice, smooth animation.
My solution goes into a similar direction as Gareth, though I do some things differently.
Here goes:
1. Hide the cells
There is no way to directly hide the cells. UITableViewController is the data source which provides the static cells, and currently there is no way to tell it "don't provide cell x".
So we have to provide our own data source, which delegates to the UITableViewController in order to get the static cells.
Easiest is to subclass UITableViewController, and override all methods which need to behave differently when hiding cells.
In the simplest case (single section table, all cells have the same height), this would go like this:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [super tableView:tableView numberOfRowsInSection:section] - numberOfCellsHidden;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Recalculate indexPath based on hidden cells
indexPath = [self offsetIndexPath:indexPath];
return [super tableView:tableView cellForRowAtIndexPath:indexPath];
}
- (NSIndexPath*)offsetIndexPath:(NSIndexPath*)indexPath
{
int offsetSection = indexPath.section; // Also offset section if you intend to hide whole sections
int numberOfCellsHiddenAbove = ... // Calculate how many cells are hidden above the given indexPath.row
int offsetRow = indexPath.row + numberOfCellsHiddenAbove;
return [NSIndexPath indexPathForRow:offsetRow inSection:offsetSection];
}
If your table has multiple sections, or the cells have differing heights, you need to override more methods. The same principle applies here: You need to offset indexPath, section and row before delegating to super.
Also keep in mind that the indexPath parameter for methods like didSelectRowAtIndexPath: will be different for the same cell, depending on state (i.e. the number of cells hidden). So it is probably a good idea to always offset any indexPath parameter and work with these values.
2. Animate the change
As Gareth already stated, you get major glitches if you animate changes using reloadSections:withRowAnimation: method.
I found out that if you call reloadData: immediately afterwards, the animation is much improved (only minor glitches left). The table is displayed correctly after the animation.
So what I am doing is:
- (void)changeState
{
// Change state so cells are hidden/unhidden
...
// Reload all sections
NSIndexSet* reloadSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self numberOfSectionsInTableView:tableView])];
[tableView reloadSections:reloadSet withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView reloadData];
}
In the designer, create an outlet for the cell(s) you want to hide. For example you want to hide 'cellOne', so in viewDidLoad() do this
cellOneOutlet.hidden = true
now override the below method, check which cell status is hidden and return height 0 for those cell(s). This is one of many ways you can hide any cell in static tableView in swift.
override func tableView(tableView: UITableView, heightForRowAtIndexPathindexPath: NSIndexPath) -> CGFloat
{
let tableViewCell = super.tableView(tableView,cellForRowAtIndexPath: indexPath)
if tableViewCell.hidden == true
{
return 0
}
else{
return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
}
}
I came up with an alternative that actually hides sections and doesn't delete them. I tried #henning77's approach, but I kept running into problems when I changed the number of sections of the static UITableView. This method has worked really well for me, but I'm primarily trying to hide sections instead of rows. I am removing some rows on the fly successfully, but it is a lot messier, so I've tried to group things into sections that I need to show or hide. Here is an example of how I'm hiding sections:
First I declare a NSMutableArray property
#property (nonatomic, strong) NSMutableArray *hiddenSections;
In the viewDidLoad (or after you have queried your data) you can add sections you want to hide to the array.
- (void)viewDidLoad
{
hiddenSections = [NSMutableArray new];
if(some piece of data is empty){
// Add index of section that should be hidden
[self.hiddenSections addObject:[NSNumber numberWithInt:1]];
}
... add as many sections to the array as needed
[self.tableView reloadData];
}
Then implement the following the TableView delegate methods
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
if([self.hiddenSections containsObject:[NSNumber numberWithInt:section]]){
return nil;
}
return [super tableView:tableView titleForHeaderInSection:section];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if([self.hiddenSections containsObject:[NSNumber numberWithInt:indexPath.section]]){
return 0;
}
return [super tableView:tableView heightForRowAtIndexPath:[self offsetIndexPath:indexPath]];
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if([self.hiddenSections containsObject:[NSNumber numberWithInt:indexPath.section]]){
[cell setHidden:YES];
}
}
Then set the header and footer height to 1 for hidden sections because you can't set the height to 0. This causes an additional 2 pixel space, but we can make up for it by adjusting the height of the next visible header.
-(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
CGFloat height = [super tableView:tableView heightForHeaderInSection:section];
if([self.hiddenSections containsObject:[NSNumber numberWithInt:section]]){
height = 1; // Can't be zero
}
else if([self tableView:tableView titleForHeaderInSection:section] == nil){ // Only adjust if title is nil
// Adjust height for previous hidden sections
CGFloat adjust = 0;
for(int i = (section - 1); i >= 0; i--){
if([self.hiddenSections containsObject:[NSNumber numberWithInt:i]]){
adjust = adjust + 2;
}
else {
break;
}
}
if(adjust > 0)
{
if(height == -1){
height = self.tableView.sectionHeaderHeight;
}
height = height - adjust;
if(height < 1){
height = 1;
}
}
}
return height;
}
-(CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
if([self.hiddenSections containsObject:[NSNumber numberWithInt:section]]){
return 1;
}
return [super tableView:tableView heightForFooterInSection:section];
}
Then, if you do have specific rows to hide you can adjust the numberOfRowsInSection and which rows are returned in cellForRowAtIndexPath. In this example here I have a section that has three rows where any three could be empty and need to be removed.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger rows = [super tableView:tableView numberOfRowsInSection:section];
if(self.organization != nil){
if(section == 5){ // Contact
if([self.organization objectForKey:#"Phone"] == [NSNull null]){
rows--;
}
if([self.organization objectForKey:#"Email"] == [NSNull null]){
rows--;
}
if([self.organization objectForKey:#"City"] == [NSNull null]){
rows--;
}
}
}
return rows;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
return [super tableView:tableView cellForRowAtIndexPath:[self offsetIndexPath:indexPath]];
}
Use this offsetIndexPath to calculate the indexPath for rows where you are conditionally removing rows. Not needed if you are only hiding sections
- (NSIndexPath *)offsetIndexPath:(NSIndexPath*)indexPath
{
int row = indexPath.row;
if(self.organization != nil){
if(indexPath.section == 5){
// Adjust row to return based on which rows before are hidden
if(indexPath.row == 0 && [self.organization objectForKey:#"Phone"] == [NSNull null] && [self.organization objectForKey:#"Email"] != [NSNull null]){
row++;
}
else if(indexPath.row == 0 && [self.organization objectForKey:#"Phone"] == [NSNull null] && [self.organization objectForKey:#"Address"] != [NSNull null]){
row = row + 2;
}
else if(indexPath.row == 1 && [self.organization objectForKey:#"Phone"] != [NSNull null] && [self.organization objectForKey:#"Email"] == [NSNull null]){
row++;
}
else if(indexPath.row == 1 && [self.organization objectForKey:#"Phone"] == [NSNull null] && [self.organization objectForKey:#"Email"] != [NSNull null]){
row++;
}
}
}
NSIndexPath *offsetPath = [NSIndexPath indexPathForRow:row inSection:indexPath.section];
return offsetPath;
}
There are a lot of methods to override, but what I like about this approach is that it is re-usable. Setup the hiddenSections array, add to it, and it will hide the correct sections. Hiding the rows it a little trickier, but possible. We can't just set the height of the rows we want to hide to 0 if we're using a grouped UITableView because the borders will not get drawn correctly.
Turns out, you can hide and show cells in a static UITableView - and with animation. And it is not that hard to accomplish.
Demo project
Demo project video
The gist:
Use tableView:heightForRowAtIndexPath: to specify cell heights dynamically based on some state.
When the state changes animate cells showing/hiding by calling tableView.beginUpdates();tableView.endUpdates()
Do not call tableView.cellForRowAtIndexPath: inside tableView:heightForRowAtIndexPath:. Use cached indexPaths to differentiate the cells.
Do not hide cells. Set "Clip Subviews" property in Xcode instead.
Use Custom cells (not Plain etc) to get a nice hiding animation. Also, handle Auto Layout correctly for the case when cell height == 0.
More info in my blog (Russian language)
As per Justas's answer, but for Swift 4:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cell = super.tableView(tableView, cellForRowAt: indexPath)
if cell == self.cellYouWantToHide {
return 0
}
return super.tableView(tableView, heightForRowAt: indexPath)
}
Yes, it's definitely possible, although I am struggling with the same issue at the moment. I've managed to get the cells to hide and everything works ok, but I cannot currently make the thing animate neatly. Here is what I have found:
I am hiding rows based on the state of an ON / OFF switch in the first row of the first section. If the switch is ON there is 1 row beneath it in the same section, otherwise there are 2 different rows.
I have a selector called when the switch is toggled, and I set a variable to indicate which state I am in. Then I call:
[[self tableView] reloadData];
I override the tableView:willDisplayCell:forRowAtIndexPath: function and if the cell is supposed to be hidden I do this:
[cell setHidden:YES];
That hides the cell and its contents, but does not remove the space it occupies.
To remove the space, override the tableView:heightForRowAtIndexPath: function and return 0 for rows that should be hidden.
You also need to override tableView:numberOfRowsInSection: and return the number of rows in that section. You have to do something strange here so that if your table is a grouped style the rounded corners occur on the correct cells. In my static table there is the full set of cells for the section, so there is the first cell containing the option, then 1 cell for the ON state options and 2 more cells for the OFF state options, a total of 4 cells. When the option is ON, I have to return 4, this includes the hidden option so that the last option displayed has a rounded box. When the option is off, the last two options are not displayed so I return 2. This all feels clunky. Sorry if this isn't very clear, its tricky to describe. Just to illustrate the setup, this is the construction of the table section in IB:
Row 0: Option with ON / OFF switch
Row 1: Displayed when option is ON
Row 2: Displayed when option is OFF
Row 3: Displayed when option is OFF
So when the option is ON the table reports two rows which are:
Row 0: Option with ON / OFF switch
Row 1: Displayed when option is ON
When the option is OFF the table reports four rows which are:
Row 0: Option with ON / OFF switch
Row 1: Displayed when option is ON
Row 2: Displayed when option is OFF
Row 3: Displayed when option is OFF
This approach doesn't feel correct for several reasons, its just as far as I have got with my experimentation so far, so please let me know if you find a better way. The problems I have observed so far are:
It feels wrong to be telling the table the number of rows is different to what is presumably contained in the underlying data.
I can't seem to animate the change. I've tried using tableView:reloadSections:withRowAnimation: instead of reloadData and the results don't seem to make sense, I'm still trying to get this working. Currently what seems to happen is the tableView does not update the correct rows so one remains hidden that should be displayed and a void is left under the first row. I think this might be related to the first point about the underlying data.
Hopefully someone will be able to suggest alternative methods or perhaps how to extend with animation, but maybe this will get you started. My apologies for the lack of hyperlinks to functions, I put them in but they were rejected by the spam filter because I am a fairly new user.
Okay, after some trying, I have a non common answer.
I am using the "isHidden" or "hidden" variable to check if this cell should be hidden.
create an IBOutlet to your view controller.
#IBOutlet weak var myCell: UITableViewCell!
Update the myCell in your custom function, example you may add it in viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
self.myCell.isHidden = true
}
in your delegate method:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cell = super.tableView(tableView, cellForRowAt: indexPath)
guard !cell.isHidden else {
return 0
}
return super.tableView(tableView, heightForRowAt: indexPath)
}
This will reduce your logic in the delegate method, and you only need to focus on your business requirement.
Simple iOS 11 & IB/Storyboard Compatible Method
For iOS 11, I found that a modified version of Mohamed Saleh's answer worked best, with some improvements based on Apple's documentation. It animates nicely, avoids any ugly hacks or hardcoded values, and uses row heights already set in Interface Builder.
The basic concept is to set the row height to 0 for any hidden rows. Then use tableView.performBatchUpdates to trigger an animation that works consistently.
Set the cell heights
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath == indexPathOfHiddenCell {
if cellIsHidden {
return 0
}
}
// Calling super will use the height set in your storyboard, avoiding hardcoded values
return super.tableView(tableView, heightForRowAt: indexPath)
}
You'll want to make sure cellIsHidden and indexPathOfHiddenCell are set appropriately to your use case. For my code they're properties on my table view controller.
Toggling the cell
In whatever method controls the visibility (likely a button action or didSelectRow), toggle the cellIsHidden state, inside a performBatchUpdates block:
tableView.performBatchUpdates({
// Use self to capture for block
self.cellIsHidden = !self.cellIsHidden
}, completion: nil)
Apple recommends performBatchUpdates over beginUpdates/endUpdates whenever possible.
The above answers that hide/show cells, change rowHeight, or mess with Auto layout constraints didn't work for me because of Auto layout issues. The code became intolerable.
For a simple static table, what worked best for me was to:
Create an outlet for every cell in the static table
Create an array only with the outlets of cells to show
Override cellForRowAtIndexPath to return the cell from the array
Override numberOfRowsInSection to return the count of the array
Implement a method to determine what cells need to be in that array, and call that method whenever needed, and then reloadData.
Here is an example from my table view controller:
#IBOutlet weak var titleCell: UITableViewCell!
#IBOutlet weak var nagCell: UITableViewCell!
#IBOutlet weak var categoryCell: UITableViewCell!
var cellsToShow: [UITableViewCell] = []
override func viewDidLoad() {
super.viewDidLoad()
determinCellsToShow()
}
func determinCellsToShow() {
if detail!.duration.type != nil {
cellsToShow = [titleCell, nagCell, categoryCell]
}
else {
cellsToShow = [titleCell, categoryCell]
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return cellsToShow[indexPath.row]
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellsToShow.count
}
I found a solution for animate hiding cells in static table.
// Class for wrapping Objective-C block
typedef BOOL (^HidableCellVisibilityFunctor)();
#interface BlockExecutor : NSObject
#property (strong,nonatomic) HidableCellVisibilityFunctor block;
+ (BlockExecutor*)executorWithBlock:(HidableCellVisibilityFunctor)block;
#end
#implementation BlockExecutor
#synthesize block = _block;
+ (BlockExecutor*)executorWithBlock:(HidableCellVisibilityFunctor)block
{
BlockExecutor * executor = [[BlockExecutor alloc] init];
executor.block = block;
return executor;
}
#end
Only one additional dictionary needed:
#interface MyTableViewController ()
#property (nonatomic) NSMutableDictionary * hidableCellsDict;
#property (weak, nonatomic) IBOutlet UISwitch * birthdaySwitch;
#end
And look at implementation of MyTableViewController. We need two methods to convert indexPath between visible and invisible indexes...
- (NSIndexPath*)recoverIndexPath:(NSIndexPath *)indexPath
{
int rowDelta = 0;
for (NSIndexPath * ip in [[self.hidableCellsDict allKeys] sortedArrayUsingSelector:#selector(compare:)])
{
BlockExecutor * executor = [self.hidableCellsDict objectForKey:ip];
if (ip.section == indexPath.section
&& ip.row <= indexPath.row + rowDelta
&& !executor.block())
{
rowDelta++;
}
}
return [NSIndexPath indexPathForRow:indexPath.row+rowDelta inSection:indexPath.section];
}
- (NSIndexPath*)mapToNewIndexPath:(NSIndexPath *)indexPath
{
int rowDelta = 0;
for (NSIndexPath * ip in [[self.hidableCellsDict allKeys] sortedArrayUsingSelector:#selector(compare:)])
{
BlockExecutor * executor = [self.hidableCellsDict objectForKey:ip];
if (ip.section == indexPath.section
&& ip.row < indexPath.row - rowDelta
&& !executor.block())
{
rowDelta++;
}
}
return [NSIndexPath indexPathForRow:indexPath.row-rowDelta inSection:indexPath.section];
}
One IBAction on UISwitch value changing:
- (IBAction)birthdaySwitchChanged:(id)sender
{
NSIndexPath * indexPath = [self mapToNewIndexPath:[NSIndexPath indexPathForRow:1 inSection:1]];
if (self.birthdaySwitch.on)
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
else
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
Some UITableViewDataSource and UITableViewDelegate methods:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
int numberOfRows = [super tableView:tableView numberOfRowsInSection:section];
for (NSIndexPath * indexPath in [self.hidableCellsDict allKeys])
if (indexPath.section == section)
{
BlockExecutor * executor = [self.hidableCellsDict objectForKey:indexPath];
numberOfRows -= (executor.block()?0:1);
}
return numberOfRows;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
indexPath = [self recoverIndexPath:indexPath];
return [super tableView:tableView cellForRowAtIndexPath:indexPath];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
indexPath = [self recoverIndexPath:indexPath];
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// initializing dictionary
self.hidableCellsDict = [NSMutableDictionary dictionary];
[self.hidableCellsDict setObject:[BlockExecutor executorWithBlock:^(){return self.birthdaySwitch.on;}] forKey:[NSIndexPath indexPathForRow:1 inSection:1]];
}
- (void)viewDidUnload
{
[self setBirthdaySwitch:nil];
[super viewDidUnload];
}
#end
Answer in swift:
Add the following method in your TableViewController:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return indexPathOfCellYouWantToHide == indexPath ? 0 : super.tableView(tableView, heightForRowAtIndexPath: indexPath)
}
if the tableView tries to draw the cell you wish to hide, then it won't display it because its height will be set to 0pt thanks to the method above, everything else stays unaltered.
Please note that indexPathOfCellYouWantToHide can be changed at anytime :)
In > Swift 2.2, I've combined few answers here.
Make an outlet from storyboard to link to your staticCell.
#IBOutlet weak var updateStaticCell: UITableViewCell!
override func viewDidLoad() {
...
updateStaticCell.hidden = true
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row == 0 {
return 0
} else {
return super.tableView(tableView, heightForRowAtIndexPath: indexPath)
}
}
I want to hide my first cell so I set the height to 0 as described above.
In addition to #Saleh Masum solution:
If you get auto-layout errors, you can just remove the constraints from the tableViewCell.contentView
Swift 3:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let tableViewCell = super.tableView(tableView, cellForRowAt: indexPath)
if tableViewCell.isHidden == true
{
tableViewCell.contentView.removeConstraints(tableViewCell.contentView.constraints)
return 0
}
else{
return super.tableView(tableView, heightForRowAt: indexPath)
}
}
This solution depends on the flow of your app. If you want to show/hide the cell in the same view controller instance this may not be the best choice, because it removes the constraints.
Swift 4:
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
var height = super.tableView(tableView, heightForRowAt: indexPath)
if (indexPath.row == HIDDENROW) {
height = 0.0
}
return height
}
For the easiest scenario when you hide cells at the very bottom of table view, you could adjust tableView's contentInset after you hide cell:
- (void)adjustBottomInsetForHiddenSections:(NSInteger)numberOfHiddenSections
{
CGFloat bottomInset = numberOfHiddenSections * 44.0; // or any other 'magic number
self.tableView.contentInset = UIEdgeInsetsMake(self.tableView.contentInset.top, self.tableView.contentInset.left, -bottomInset, self.tableView.contentInset.right);
}
This is new way to do this using https://github.com/k06a/ABStaticTableViewController
NSIndexPath *ip = [NSIndexPath indexPathForRow:1 section:1];
[self deleteRowsAtIndexPaths:#[ip] withRowAnimation:UITableViewRowAnimationFade]
Solution from k06a (https://github.com/k06a/ABStaticTableViewController) is better because it hides whole section including cells headers and footers, where this solution (https://github.com/peterpaulis/StaticDataTableViewController) hides everything except footer.
EDIT
I just found solution if you want to hide footer in StaticDataTableViewController. This is what you need to copy in StaticTableViewController.m file:
- (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section {
if ([tableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
return nil;
} else {
return [super tableView:tableView titleForFooterInSection:section];
}
}
- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section {
CGFloat height = [super tableView:tableView heightForFooterInSection:section];
if (self.originalTable == nil) {
return height;
}
if (!self.hideSectionsWithHiddenRows) {
return height;
}
OriginalSection * os = self.originalTable.sections[section];
if ([os numberOfVissibleRows] == 0) {
//return 0;
return CGFLOAT_MIN;
} else {
return height;
}
//return 0;
return CGFLOAT_MIN;
}
Surely you can. First, return to your tableView number of cells you want to show then call super to achieve certain cell from your storyboard and return it for tableView:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.mode.numberOfCells()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = super.tableView(tableView, cellForRowAtIndexPath: self.mode.indexPathForIndexPath(indexPath))
return cell
}
If your cells has different hieght return it too:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return super.tableView(tableView, heightForRowAtIndexPath: self.mode.indexPathForIndexPath(indexPath))
}
I got a better way to hide static cells and even sections dynamically without any hacks.
Setting the row height to 0 can hide a row, but that doesn't work if you want to hide an entire section which will hold some spaces even you hide all the rows.
My approach is to build a section array of static cells. Then the table view contents will be driven by the section array.
Here is some sample code:
var tableSections = [[UITableViewCell]]()
private func configTableSections() {
// seciton A
tableSections.append([self.cell1InSectionA, self.cell2InSectionA])
// section B
if shouldShowSectionB {
tableSections.append([self.cell1InSectionB, self.cell2InSectionB])
}
// section C
if shouldShowCell1InSectionC {
tableSections.append([self.cell1InSectionC, self.cell2InSectionC, self.cell3InSectionC])
} else {
tableSections.append([self.cell2InSectionC, self.cell3InSectionC])
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return tableSections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tableSections[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return tableSections[indexPath.section][indexPath.row]
}
This way, you can put all of your configuration code together without having to write the nasty code to calculate number of rows and sections. And of course, no 0 heights anymore.
This code is also very easy maintain. For example, if you want to add/remove more cells or sections.
Similarly, you can create a section header title array and section footer title array to config your section titles dynamically.

Resources