UISearchController – Search result show different records - objective-c

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

Related

EXC_BAD_ACCESS with NSOutlineView and stringWithFormat

I am experiencing a bad access error when an item of my NSOutlineView is expanded. When NSStrings are allocated with stringWithFormat:, there is an EXC_BAD_ACCESS error when expanding the outline. When they are replaced with strings in the form of #"string", there is no error.
I assume something is releasing with ARC, but I don't know how to keep it from happening. What doesn't look right here?
-(NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item {
if(!item)
return [_characterList count];
else if( [item isKindOfClass:[Character class]] )
return 3;
return 0;
}
-(BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item {
if( [item isKindOfClass:[Character class]] )
return YES;
return NO;
}
-(id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item {
if (!item)
return (Character*)[_characterList objectAtIndex:index];
else {
NSLog(#"%#", item);
Character *characterItem = (Character*)item;
switch (index) {
case 0:
return [NSString stringWithFormat:#"Api key: %#", [characterItem apiKey]];
break;
case 1:
return [NSString stringWithFormat:#"Access Mask: %#", [characterItem mask]];
break;
case 2:
return #"Last Updated: today";
break;
default:
break;
}
}
return nil;
}
-(id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item {
if([item isKindOfClass:[Character class]])
return [(Character*)item name];
else
return item;
return nil;
}
The solution I have come up with (but don't particularly like). Replace outlineView:objectValueForTableColumn:byItem: with the following code.
-(NSView*)outlineView:(NSOutlineView *)outlineView viewForTableColumn:(NSTableColumn *)tableColumn item:(id)item {
NSTableCellView *cell = [outlineView makeViewWithIdentifier:#"characterColumn" owner:self];
if([item isKindOfClass:[Character class]]) {
[cell.textField setStringValue:[item name]];
} else if([item isKindOfClass:[NSString class]]) {
[cell.textField setStringValue:item];
}
return cell;
}
Basically what this does is exactly what I'd expect the cell code to do, but it appears to retain things properly. Any insight from the masses?
EDIT: Here's the deal. NSOutlineView's dataSource delegate methods are a little bit more particular about ownership. It's not something that you have to deal with usually with a vanilla NSTableView, since there are not multiple levels of item. Basically, you need to create all of the objects for display elsewhere and make sure they are managed in memory elsewhere, because NSOutlineViewDataSource isn't going to do any of that for you.

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;
}

Remove sections with no rows from UITableView

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.

About sectioning your UITableView

I'm working on a tableview, which displays more than one line of text in its cell. I want to have two sections, which I've already done. But is it possible to display another array in the second section, not the same? T
Here's the code I use:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0) {
return MIN([titles count], [subtitles count]);
}
else {
return 1;
}
}
Of course you can do this.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0) {
return MIN([titles count], [subtitles count]);
}
else {
return MIN([titles2 count], [subtitles2 count]);
}
}
Just make sure to switch arrays in all UITableViewDataSource and UITableViewDelegate methods.
But I would suggest nested arrays. It makes the code shorter, because when you have more than 10 sections your code will become long and ugly.
And with nested arrays you only have the long ugly code in viewDidLoad or whereever you initialize your array;
titles = [NSArray arrayWithObjects:titles0, titles1, nil];
subtitles = [NSArray arrayWithObjects:subtitles0, subtitles1, nil];
.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return MIN([[titles objectAtIndex:section] count], [[subtitles objectAtIndex:section]count]);
}

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];
}
}