Remove sections with no rows from UITableView - objective-c

I would like to remove section headers from a UITableView if there are no rows for that section.
I'm using UILocalizedIndexedCollation for my section headers. So when I create the headers, I don't necessarily know what sections will have content.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
//return [customerSections count];
if (tableView == self.searchDisplayController.searchResultsTableView) {
return 1;
}
return [[[UILocalizedIndexedCollation currentCollation] sectionTitles] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//NSLog(#"Section: %i", section);
if (tableView == self.searchDisplayController.searchResultsTableView) {
return self.filteredCustomers.count;
} else {
return [[self.customerData objectAtIndex:section] count];
}
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
// The header for the section is the region name -- get this from the region at the section index.
if (tableView == self.searchDisplayController.searchResultsTableView) {
return nil;//#"Results";
}
return [[[UILocalizedIndexedCollation currentCollation] sectionTitles] objectAtIndex:section];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
//return [customerSections allKeys];
if (tableView == self.searchDisplayController.searchResultsTableView) {
return nil;
}
return [[UILocalizedIndexedCollation currentCollation] sectionIndexTitles];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
return [[UILocalizedIndexedCollation currentCollation] sectionForSectionIndexTitleAtIndex:index];
}

Just wanted to chime in and give my solution to this
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if ([self.myTableView.dataSource tableView:tableView numberOfRowsInSection:section] == 0) {
return nil;
}
return [[self.collation sectionTitles] objectAtIndex:section];
}
based on this answer

I've recently achieved this with this code:
This is the function that returns the title for the section, if there are no rows in that section then don't return a title:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
if ([self.customerData count] > 0 ) {
return [[[UILocalizedIndexedCollation currentCollation] sectionTitles] objectAtIndex:section];
}
return nil;
}

It's an interesting question, with a number of possible solutions.
Working backwards, numberOfSectionsinTableView and numberOfRowsInSection are what need to be updated in order to display the right number of sections. These are partly dependent on the methods UILocalizedIndexedCollation methods.
(Presumably this happens after some sort of user action (a delete or an insert), so note that in commitEditingStyle you should call [self.tableView reloadData).
I am assuming that customerData is an array, where at each index there is a mutable array that corresponds to a section. When the array at a certain customerData index has no data, you want to remove the section for that index.
The solution then, is to calculate everything manually - determine section information based on lives inside your customerData array.
I took a stab at rewriting three of your methods. Good luck!
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
if (tableView == self.searchDisplayController.searchResultsTableView) {
return nil;
}
NSMutableArray *arrayToFilter = [[UILocalizedIndexedCollation currentCollation] sectionIndexTitles];
//This is the key - recalculate index titles based on what's present in customerData
for (int i = [self.customerData count] -1; i >=0 ; i--) {
if (![[self.customerData objectAtIndex:i] count]) {
[self.arrayToFilter removeObjectAtIndex:i];
}
}
return arrayToFilter;
}
//You need to be calculating your table properties based on the number of objects
//rather of the 'alphabet' being used.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
//return [customerSections count];
if (tableView == self.searchDisplayController.searchResultsTableView) {
return 1;
}
//return [[[UILocalizedIndexedCollation currentCollation] sectionTitles] count];
int populatedArrayCounter = 0;
for (int i = 0; i <[self.customerData count]; i++) {
if ([[self.customerData objectAtIndex:i] count]) {
populatedArrayCounter++;
}
}
return populatedArrayCounter;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
//NSLog(#"Section: %i", section);
if (tableView == self.searchDisplayController.searchResultsTableView) {
return self.filteredCustomers.count;
} else {
// Your original line requires changing, because there are customerData array objects with a count of 0, and you don't want any sections like that
// Thus, pick from the set of populated arrays.
NSMutableArray populatedArrays = [[NSMutableArray alloc] init];
for (int i = 0; i <[self.customerData count]; i++) {
if ([[self.customerData objectAtIndex:i] count]) {
[populatedArrays addObject:i];
}
}
return [[populatedArrays objectAtIndex:section] count];;
}
}

I wanted to share my solution in swift
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if self.sections[section].isEmpty
{
return nil
}
else
{
return collation.sectionTitles[section]
}
}

I ended up removing the unused sectionIndexTitles and creating the sections based on that.
In my NSURLConnection requestDidFinish I used the following.
self.customerData = [self partitionObjects:[self customers] collationStringSelector:#selector(self)];
then had
-(NSArray *)partitionObjects:(NSArray *)array collationStringSelector:(SEL)selector
{
sectionIndexTitles = [NSMutableArray arrayWithArray:[[UILocalizedIndexedCollation currentCollation] sectionIndexTitles]];
UILocalizedIndexedCollation *collation = [UILocalizedIndexedCollation currentCollation];
NSInteger sectionCount = [[collation sectionTitles] count];
NSMutableArray *unsortedSections = [NSMutableArray arrayWithCapacity:sectionCount];
for (int i = 0; i < sectionCount; i++) {
[unsortedSections addObject:[NSMutableArray array]];
}
for (id object in array) {
NSInteger index = [collation sectionForObject:[object objectForKey:#"name"] collationStringSelector:selector];
[[unsortedSections objectAtIndex:index] addObject:object];
}
NSMutableArray *sections = [NSMutableArray array];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSUInteger lastIndex = 0;
NSMutableIndexSet *sectionsToRemove = [NSMutableIndexSet indexSet];
for (NSArray *section in unsortedSections) {
if ([section count] == 0) {
NSRange range = NSMakeRange(lastIndex, [unsortedSections count] - lastIndex);
[sectionsToRemove addIndex:[unsortedSections indexOfObject:section inRange:range]];
lastIndex = [sectionsToRemove lastIndex] + 1;
} else {
NSArray *sortedArray = [section sortedArrayUsingDescriptors:sortDescriptors];
[sections addObject:sortedArray];
}
}
[sectionIndexTitles removeObjectsAtIndexes:sectionsToRemove];
return sections;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
if (self.searchDisplayController.active) {
return 1;
}
return [sectionIndexTitles count];//[[[UILocalizedIndexedCollation currentCollation] sectionTitles] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (self.searchDisplayController.active) {
return self.filteredCustomers.count;
} else {
return [[self.customerData objectAtIndex:section] count];
}
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
if (self.searchDisplayController.active) {
return nil;
}
return [sectionIndexTitles objectAtIndex:section];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
if (self.searchDisplayController.active) {
return nil;
}
return sectionIndexTitles;
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
return index;//[[UILocalizedIndexedCollation currentCollation] sectionForSectionIndexTitleAtIndex:index];
}
And with all the above code this removed the unused letters from the right side of the screen as well as the section headers that had no rows.

Related

UISearchController – Search result show different records

When i type "stefanik" in searchbar the result is one record (but different look), when i tap on this, is the right record, see screenshots.
I think problem is in these methods, but i cant find the problem.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
if (self.searchController.active)
{
return 1;
}
else
{
return [[self.fetchedResultsController sections] count];
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (self.searchController.active)
{
return [self.filteredList count];
}
else
{
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
return [sectionInfo numberOfObjects];
}
}
Have you seen this problem before?
Screenshots
If you are using NSFetchedResultsController apply an NSPredicate to filter the records for example
if (self.searchController.active) {
self.fetchedResultsController.fetchRequest.predicate = [NSPredicate predicateWithBlock: ^BOOL(id obj, NSDictionary *bind){
return [[(Airport *)obj name] rangeOfString:searchText options:NSCaseInsensitiveSearch].location != NSNotFound;
}];
} else {
self.fetchedResultsController.fetchRequest.predicate = nil
}
// reload data
This is more efficient than filtering in code into an extra array

Remove extra section or extra bottom space of tableview

I had create one simple tableview with section in which I was facing one issue that is, after displaying all section properly there are several dummy sections which I want to remove but reason why they had being displayed I couldn't able to found.
Here array of section count and row count under each section is proper.
So can you guys suggest how to remove that extra space and can obtain original table content size?
Below is code I done in my .m file specifically for tableview
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
if (searchEnabled)
{
return 1;
}
else
{
return tableDataArray.count;
}
}
- (UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UILabel *headerView;
[headerView setBackgroundColor:[UIColor colorWithRed:216.0/255.0 green:216.0/255.0 blue:216.0/255.0 alpha:0.5]];
for (int i=0; i<alphabetArray.count; i++)
{
if (section==i)
{
headerView.text= [alphabetArray objectAtIndex:i];
headerView.textColor = [UIColor blackColor];
headerView.font = [UIFont fontWithName:#"Montserrat-Medium" size:12.0];
}
}
return headerView;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
if (searchEnabled)
{
return nil;
}
else
{
NSString *title;
for (int i=0; i<alphabetArray.count; i++)
{
if (section==i)
{
title= [alphabetArray objectAtIndex:i];
}
}
return title;
}
}
-(NSMutableArray *)getArrayOfRowsForSection:(NSInteger)section
{
NSString *rowTitle;
NSString *sectionTitle;
NSMutableArray *rowContainer=[[NSMutableArray alloc]initWithCapacity:0];
for (int i=0; i<alphabetArray.count; i++)
{
if (section==i) // check for right section
{
sectionTitle= [alphabetArray objectAtIndex:i]; //getting section title
for (NSString *title in tableDataArray)
{
rowTitle=[title substringToIndex:1]; //modifying the statement to its first alphabet
if ([rowTitle isEqualToString:sectionTitle]) //checking if modified statement is same as section title
{
[rowContainer addObject:title]; //adding the row contents of a particular section in array
}
}
}
}
return rowContainer;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if (searchEnabled)
{
return [self.searchResult count];
}
else
{
NSMutableArray* rowArray=[[NSMutableArray alloc]init];
rowArray=[self getArrayOfRowsForSection:section];
return rowArray.count;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"cell"];
}
if (searchEnabled)
{
cell.textLabel.text = [self.searchResult objectAtIndex:indexPath.row];
}
else
{
NSMutableArray* rowArray=[[NSMutableArray alloc]init];
rowArray=[self getArrayOfRowsForSection:indexPath.section];
NSString *titleToBeDisplayed=[rowArray objectAtIndex:indexPath.row];
cell.textLabel.text = titleToBeDisplayed;
}
cell.textLabel.textColor = [UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:0.7];
return cell;
}
Its hard to get the reason from the code(Seems like it is ok).Please check what is ur UITableViewStyle in the storyboard.If it is plain then try to make it :
UITableViewStyleGrouped
Try this
In
- (void)viewDidLoad {
self.tblView.backgroundColor=[UIColor clearColor];
}
and in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
cell.backgroundColor=[UIColor clearColor];
}

want to check only one tablecell at a time in TableView

In the source code below I am able to change my checkmark object value, but at the time of reload data in cellForRowAtIndexPath method it shows old data:
//TableView
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection: (NSInteger)section
{
return [self.tableData count];
}
//CellforRowAtIndexpath showing old table data
- (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];
}
Recipe *obj = nil;
obj = self.tableData[indexPath.row];
cell.textLabel.text = obj.name;
NSLog(#"Reload Data %#", obj.checkmark);
if ([obj.checkmark integerValue] == 1)
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Recipe *recipe = nil;
recipe = [self.tableData objectAtIndex:indexPath.row];
if ([recipe.checkmark integerValue] == 1)
{
recipe.checkmark=#"0";
}
else
{
//recipe.checkmark=#"1";
for (int i=0; i<[self.tableData count]; i++) {
if (i==indexPath.row) {
recipe.checkmark=#"1";
}
else{
recipe.checkmark=#"0";
}
NSLog(#"PK ! %#", recipe.checkmark);
}
}
//[self.tableData addObject:recipe.checkmark];
[self.myTable reloadData];
[self.myTable reloadData];
}
The good news is normally this issue is in the cell for row logic, but it looks like you have that portion right. I believe the issue is in your cell for row logic because you are not updating tableData correctly. See extra comments below.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Recipe *recipe = nil;
recipe = [self.tableData objectAtIndex:indexPath.row];
if ([recipe.checkmark integerValue] == 1)
{
recipe.checkmark=#"0";
}
else
{
//This only gets called if current checked cell is already checked
for (int i=0; i<[self.tableData count]; i++) {
if (i==indexPath.row) {
recipe.checkmark=#"1";
}
else{
//This is still the recipe for the cell currently selected
recipe.checkmark=#"0";
}
NSLog(#"PK ! %#", recipe.checkmark);
}
}
//[self.tableData addObject:recipe.checkmark];
[self.myTable reloadData];
[self.myTable reloadData];
}
Something like this should fix your issue without changing too much of your code or structure.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Recipe *recipe = [self.tableData objectAtIndex:indexPath.row];
if ([recipe.checkmark integerValue] == 1)
{
recipe.checkmark=#"0";
}
else
{
recipe.checkmark=#"1";
}
//remove all old checkmarks
for (int i=0; i<[self.tableData count]; i++)
{
//Only update recipes that are not in this row
if (i!=indexPath.row)
{
recipe = [self.tableData objectAtIndex:i];
recipe.checkmark=#"0";
}
}
[self.myTable reloadData];
}
Another things to consider is instead of using a string that you are converting to an integer that is acting like a BOOL just change recipe's property checkmark to a BOOL. I hope that helps and good luck.

NSFetchedResultsController with indexed UITableViewController and UILocalizedIndexedCollation

I have a table that uses an NSFetchedResultsController. This gets me an index with the headers that are present
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [[_fetchedResultsController sections] count];
}
-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [[[_fetchedResultsController sections] objectAtIndex:section] name];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSInteger numberOfRows = 0;
NSArray *sections = _fetchedResultsController.sections;
if(sections.count > 0)
{
id <NSFetchedResultsSectionInfo> sectionInfo = [sections objectAtIndex:section];
numberOfRows = [sectionInfo numberOfObjects];
}
return numberOfRows;
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
return [_fetchedResultsController sectionIndexTitles];
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
return [_fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}
but I want to use UILocalizedIndexCollation to get the complete alphabet.
How do I wire up these methods to the NSFetchedResultsController?
I think I need to get the index titles from the current Collation
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
return [[UILocalizedIndexedCollation currentCollation] sectionIndexTitles];
}
but I am lost on how to write this method
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
//How do I translate index here to be the index of the _fetchedResultsController?
return [_fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}
I think you have to traverse the fetched results controller sections and find a matching section for the given title, for example:
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
NSInteger section = 0;
for (id <NSFetchedResultsSectionInfo> sectionInfo in [_fetchedResultsController sections]) {
if ([sectionInfo.indexTitle compare:title] >= 0)
break;
section++;
}
return section;
}
For section index titles that to not have a matching section, you have to decide if you want to jump to a "lower" or "higher" section. The above method jumps to the next higher section.
The solution I went with, after inspiration from Martin.
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
NSInteger localizedIndex = [[UILocalizedIndexedCollation currentCollation] sectionForSectionIndexTitleAtIndex:index];
NSArray *localizedIndexTitles = [[UILocalizedIndexedCollation currentCollation] sectionIndexTitles];
for(int currentLocalizedIndex = localizedIndex; currentLocalizedIndex > 0; currentLocalizedIndex--) {
for(int frcIndex = 0; frcIndex < [[_fetchedResultsController sections] count]; frcIndex++) {
id<NSFetchedResultsSectionInfo> sectionInfo = [[_fetchedResultsController sections] objectAtIndex:frcIndex];
NSString *indexTitle = sectionInfo.indexTitle;
if([indexTitle isEqualToString:[localizedIndexTitles objectAtIndex:currentLocalizedIndex]]) {
return frcIndex;
}
}
}
return 0;
}

shouldReloadTableForSearchString doesn't show filtered result

I have been trying for hours but couldn't figure out why it doesn't show the filtered result. It always shows all the results. I'm not sure what I am doing wrong.
- (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString
{
[self filterContentForSearchText:searchString scope:
[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]];
// Return YES to cause the search result table view to be reloaded.
return YES;
}
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
/*
Update the filtered array based on the search text and scope.
*/
[self.filteredListContent removeAllObjects]; // First clear the filtered array.
//for loop here
NSLog(#"%i", [filteredListContent count]);
//filteredListContent contains correct number of filtered items
}
It works. I wasn't using the filtered result array in numberOfRowsInSection method
was:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.persons count];
}
changed to:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (tableView == self.searchDisplayController.searchResultsTableView)
{
return [self.filteredListContent count];
}
else
{
return [self.persons count];
}
}