ICL1901
ICL1901

Reputation: 7778

iOS indexed tableview using core data and sort - index is ok, but data is not

I need some help indexing a UITableView, dividing the data in alphabetic sections. The data model is from a core data graph.

I have the index (A-Z), loaded ok, and the section headers are correct. The issue is that the data is not sorted correctly (i.e. alphabetic).

There is an entity attribute in the model that is an alphabetic index. I've looked at the sqlite file and that is ok.

Here are pieces of relevant code. Please help me understand what I'm missing or messing :)

- (void)viewDidLoad {
    [super viewDidLoad];

    sectionArray = [[NSArray alloc] init];
    self.collation = [UILocalizedIndexedCollation currentCollation];

    if (languageKey == 0) { 
        sectionArray = [NSArray arrayWithArray:[@"|α,ά|β|γ|δ|ε,έ|ζ|η,ή|θ|ι,ί|κ|λ|μ|ν|Ξ|ο,ό|π|ρ|σ|τ|υ,υ|φ|χ|ψ|ω,ώ|#"
                                                componentsSeparatedByString:@"|"]];

    } else {
        sectionArray = [NSArray arrayWithArray:
                        [@"A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|#"
                         componentsSeparatedByString:@"|"]];
    }

    NSManagedObjectContext *context = [self managedObjectContext];
    NSEntityDescription *entityDescription = [NSEntityDescription
                                              entityForName:@"WordEntity" inManagedObjectContext:context];
    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    [request setEntity:entityDescription];
    [request setPredicate:[self predicate]];
    [request setIncludesSubentities:NO];

    filteredWordsArray = [[NSMutableArray alloc] init];
    self.searchResults = [NSMutableArray arrayWithCapacity:[[self.fetchedResultsController fetchedObjects] count]];

    self.searchDisplayController.searchResultsTableView.rowHeight = wordTable.rowHeight;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    if (tableView == self.searchDisplayController.searchResultsTableView)
    {
        return [self.searchResults count];
    }
    else
    {
        return [[[self.fetchedResultsController sections] objectAtIndex:section] numberOfObjects];
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *CellIdentifier = @"WordCell";
    UITableViewCell *cell = (UITableViewCell *)[self.wordTable dequeueReusableCellWithIdentifier:CellIdentifier];

    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }

    WordEntity *word = nil;
    if (tableView == self.searchDisplayController.searchResultsTableView) {
        word = [self.searchResults objectAtIndex:indexPath.row];
        count = self.searchResults.count;
        self.numberWordsLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)count];

    } else {

        word = [self.fetchedResultsController objectAtIndexPath:indexPath];
        self.numberWordsLabel.text = [NSString stringWithFormat:@"%lu", (unsigned long)fullCount];
    }


    if (languageKey == 0) {
        cell.textLabel.text =  word.greekText;
        cell.detailTextLabel.text = word.englishText;
    } else {
        cell.textLabel.text =  word.englishText;
        cell.detailTextLabel.text = word.greekText;
    }

    return cell;
}

/*
 Section-related methods: Retrieve the section titles and section index titles from the collation.
 */


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    long languageKey = [defaults integerForKey:DEFAULT_KEY_LANGUAGE_NUMBER];
    long count = 0;
    if (languageKey == 0) {
        count = 24;
    } else {
        count = 26;
    }
    return count;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return [sectionArray objectAtIndex:section];
}
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return sectionArray;
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    return [collation sectionForSectionIndexTitleAtIndex:index];
}

- (NSFetchedResultsController *)fetchedResultsController {

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] init];

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    long languageKey = [defaults integerForKey:DEFAULT_KEY_LANGUAGE_NUMBER ];
    if (languageKey == 0) {
        sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"greekKey" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)];
       // sectionTitleString = @"greekKey";
    } else {
        sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"englishKey" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)];
       // sectionTitleString = @"englishKey";
    }

    NSArray *sortDescriptors = @[sortDescriptor];
    [fetchRequest setSortDescriptors:sortDescriptors];

      _fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"greekKey" cacheName:nil];
    _fetchedResultsController.delegate = self; 
    return _fetchedResultsController;
}

/*
-(NSArray *)partitionObjects:(NSArray *)array collationStringSelector:(SEL)selector

{
    collation = [UILocalizedIndexedCollation currentCollation];

    NSInteger sectionCount = [[collation sectionTitles] count];   //section count is  from sectionTitles and not sectionIndexTitles
    NSMutableArray *unsortedSections = [NSMutableArray arrayWithCapacity:sectionCount];

    //create an array to hold the data for each section
    for(int i = 0; i < sectionCount; i++)
    {
        [unsortedSections addObject:[NSMutableArray array]];
    }

    //put each object into a section
    for (id object in array)
    {
        NSInteger index = [collation sectionForObject:object collationStringSelector:selector];
        [[unsortedSections objectAtIndex:index] addObject:object];
    }

    sections = [NSMutableArray arrayWithCapacity:sectionCount];

    //sort each section
    for (NSMutableArray *section in unsortedSections)
    {
        [sections addObject:[collation sortedArrayFromArray:section collationStringSelector:selector]];
    }

    return sections;
}
*/

This is what I'm seeing:

enter image description here

Upvotes: 0

Views: 718

Answers (1)

andrewbuilder
andrewbuilder

Reputation: 3791

Try adding a sort descriptor to your NSFetchRequest...

NSSortDescriptor *sortDescriptorSecondary = [[NSSortDescriptor alloc]
    initWithKey:@"word" ascending:YES];
[request setSortDescriptors:@[sectionArray, sortDescriptorSecondary]]; 

Note that when you are using sections in your table view, you must always sort by section first and then other criteria.


UPDATE

In a little more detail...

Private properties to include:

@property (nonatomic, strong) NSArray *sectionArray;

Fetch request to include:

    //  Declare sort descriptors
    NSArray *requestSortDescriptors = nil;
    NSSortDescriptor *sortDescriptorPrimary = nil;
    NSSortDescriptor *sortDescriptorSecondary = nil;

    //  Set sort descriptors...
    //  Primary sort descriptor is your section array - sort by sections first.
    sortDescriptorPrimary = [NSSortDescriptor sortDescriptorWithKey:self.sectionArray ascending:YES];
    //  Secondary sort descriptor is your entity attribute `word` - sort by fetched data second.
    sortDescriptorSecondary = [NSSortDescriptor sortDescriptorWithKey:@"word" ascending:YES];

    //  Set sort descriptor array
    requestSortDescriptors = @[sortDescriptorPrimary, sortDescriptorSecondary];

    //  Apply sort descriptors to fetch request
    [request setSortDescriptors:requestSortDescriptors]; 

Hope this assists, let me know if you require further explanation.


SECOND UPDATE

I neglected to mention how to propagate the section data.

Personally, for data that is to be displayed in a TVC with section headers, for each entity I define an entity attribute that I always for simplicity call sectionIdentifier.

Then as a part of my methods to persist data, I ensure "section" data is allocated to this attribute sectionIdentifier (e.g. in your example A, B, C, etc).

In your case however, and for this particular example, you have established a local variable called sectionArray in your viewDidLoad TVC lifecycle method.

Working with your code in mind, I suggest the following (per above)...

Private properties to include:

@property (nonatomic, strong) NSArray *sectionArray;

Also working with your code in mind, I suggest the following (new)...

Alter your TVC lifecycle method viewDidLoad:

- (void)viewDidLoad {
    [super viewDidLoad];

    self.sectionArray = nil;
    self.collation = [UILocalizedIndexedCollation currentCollation];

    if (languageKey == 0) { 
        self.sectionArray = [NSArray arrayWithArray:[@"|α,ά|β|γ|δ|ε,έ|ζ|η,ή|θ|ι,ί|κ|λ|μ|ν|Ξ|ο,ό|π|ρ|σ|τ|υ,υ|φ|χ|ψ|ω,ώ|#" 
                        componentsSeparatedByString:@"|"]];

    } else {
        self.sectionArray = [NSArray arrayWithArray:[@"A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Y|Z|#"
                        componentsSeparatedByString:@"|"]];
    }

    //  I have commented out code following because I cannot see where you are using it.
    //  Does the compiler not throw up warnings for this fetch request?
    //  
    //  NSManagedObjectContext *context = [self managedObjectContext];
    //  NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"WordEntity" inManagedObjectContext:context];
    //  NSFetchRequest *request = [[NSFetchRequest alloc] init];
    //  [request setEntity:entityDescription];
    //  [request setPredicate:[self predicate]];
    //  [request setIncludesSubentities:NO];

    filteredWordsArray = [[NSMutableArray alloc] init];
    self.searchResults = [NSMutableArray arrayWithCapacity:[[self.fetchedResultsController fetchedObjects] count]];

    self.searchDisplayController.searchResultsTableView.rowHeight = wordTable.rowHeight;
}

Also working with your code in mind, I suggest the following (new)...

Alter your NSFetchedResultsController getter method:

- (NSFetchedResultsController *)fetchedResultsController {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    long languageKey = [defaults integerForKey:DEFAULT_KEY_LANGUAGE_NUMBER ];

    //  Set up fetch request
    NSFetchRequest *fetchRequest = [[NSFetchRequest fetchRequestWithEntityName:@"WordEntity"];
    [fetchRequest setPredicate:[self predicate]];
    [fetchRequest setIncludesSubentities:NO];

    //  Declare sort descriptor variables
    NSArray *requestSortDescriptors = nil;
    NSSortDescriptor *sortDescriptorPrimary = nil;
    NSSortDescriptor *sortDescriptorSecondary = nil;

    //  Set sort descriptors...
    //  Primary sort descriptor is your section array - sort by sections first.
    sortDescriptorPrimary = [NSSortDescriptor sortDescriptorWithKey:self.sectionArray 
                                                          ascending:YES];

    //  Secondary sort descriptor is your entity attribute - sort by fetched data second.
    if (languageKey == 0) {
        sortDescriptorSecondary = [NSSortDescriptor sortDescriptorWithKey:@"greekKey" 
                                                                ascending:YES
                                                                 selector:@selector(localizedCaseInsensitiveCompare:)];     
        // sectionTitleString = @"greekKey";

    } else {
        sortDescriptorSecondary = [NSSortDescriptor sortDescriptorWithKey:@"englishKey" 
                                                                ascending:YES
                                                                 selector:@selector(localizedCaseInsensitiveCompare:)];     
        // sectionTitleString = @"englishKey";

    }

    //  Set sort descriptor array - section first, then table view data
    requestSortDescriptors = @[sortDescriptorPrimary, sortDescriptorSecondary];

    //  Apply sort descriptors to fetch request
    [fetchRequest setSortDescriptors:requestSortDescriptors]; 

    //  Your sectionNameKeyPath in your FRC is self.sectionArray (this may be incorrect - try)
    _fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest 
                                                                    managedObjectContext:self.managedObjectContext 
                                                                      sectionNameKeyPath:self.sectionArray 
                                                                               cacheName:nil];

    _fetchedResultsController.delegate = self; 

    return _fetchedResultsController;
}

Upvotes: 2

Related Questions