Hermann Klecker
Hermann Klecker

Reputation: 14068

Core data sort descriptor with localized string

I need some advise please. I am rather new to core data.

I've got a core data model which is working fine in principle. It has an entity "RoomType". This entity has only one attribute of type String and one relation (many) to an entity "Room" (where the inverse is a one-relation). The relation does not matter. It is the string and the sortDescriptor that drives me crazy.

The data base content is in English language. This table is rather some setup table, which is filled dynamically when the app is installed and will never be changed by the user. For any German and 3rd Language I need to get this data translated. I am using the NSLocalizedString macro for that purpose and it is working well - with the exception of sorting the data by its translated value.

(For future releases I will allow the user to add records. But those manually added records do not need to be translated.)

This is the automatically generated RoomType.h:

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Room;

@interface RoomType : NSManagedObject

@property (nonatomic, retain) NSString * typeName;
@property (nonatomic, retain) NSSet *rooms;
@end

@interface RoomType (CoreDataGeneratedAccessors)

- (void)addRoomsObject:(Room *)value;
- (void)removeRoomsObject:(Room *)value;
- (void)addRooms:(NSSet *)values;
- (void)removeRooms:(NSSet *)values;

@end

Nothing unusual, I would say.

This is the fetchedResultsController getter method within my view controller:

- (NSFetchedResultsController *)fetchedResultsController
{
    if (_fetchedResultsController != nil) {
        return _fetchedResultsController;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.

    self.managedObjectContext = [(AppDelegate*) [[UIApplication sharedApplication] delegate] managedObjectContext];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"RoomType" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];

    // Edit the sort key as appropriate.
    NSSortDescriptor *sortDescriptor;
    sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"typeName" ascending:YES];
    [fetchRequest setSortDescriptors: @[sortDescriptor]];

    // Set the predicate // No predicate because we want to fetch all items
    //NSPredicate *predicate =
    //[NSPredicate predicateWithFormat:@"suite == %@", self.detailItem];
    //[fetchRequest setPredicate:predicate];

    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:@"Suite"];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;

    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error]) {
        // Replace this implementation with code to handle the error appropriately.
        // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return _fetchedResultsController;
}

The data is fed into a picker:

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)thePickerView {

    return 1;
}

- (NSInteger)pickerView:(UIPickerView *)thePickerView numberOfRowsInComponent:(NSInteger)component {

    int anzahl = [[self.fetchedResultsController fetchedObjects] count];
    return anzahl;
}

- (NSString *)pickerView:(UIPickerView *)thePickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {

    NSManagedObject *object = [[self.fetchedResultsController fetchedObjects] objectAtIndex:row];

    RoomType    *roomType   = (RoomType*) object;
    NSLog(@"%@ - %@", roomType.typeName, NSLocalizedString(roomType.typeName, @"Room-Type"));

    return NSLocalizedString(roomType.typeName, @"Room-Type");  // <-- HERE COMES THE TRANSLATION!
}

- (void)pickerView:(UIPickerView *)thePickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {

    Room *theRoom = (Room*) [self detailItem];

    //Ignore the component cause there is only one.
    theRoom.roomType = [[self.fetchedResultsController fetchedObjects] objectAtIndex:row];

}

Please take a note of the NSLocalzedString where the database content is localized. This is the output of the NSLogs:

2013-01-23 14:38:38.179 Wohnungsprotokoll[1311:c07] attic - Dachboden
2013-01-23 14:38:38.184 Wohnungsprotokoll[1311:c07] balcony - Balkon
2013-01-23 14:38:38.187 Wohnungsprotokoll[1311:c07] bath room - Badezimmer
2013-01-23 14:38:39.659 Wohnungsprotokoll[1311:c07] bed room - Schlafzimmer
2013-01-23 14:38:39.831 Wohnungsprotokoll[1311:c07] cellar - Keller
2013-01-23 14:38:40.789 Wohnungsprotokoll[1311:c07] children room - Kinderzimmer
2013-01-23 14:38:41.043 Wohnungsprotokoll[1311:c07] closet - Kammer
[...]

As you see, the original data in English language is sorted properly but the German translation (following the "-") is not.

Obvious so far. But how do I get around this?

Is there any smart way of getting this sorted by the localized value of the database content? I mean another way than copying the data into an Array and sorting that array afterwards.

Upvotes: 1

Views: 1357

Answers (1)

Martin R
Martin R

Reputation: 539745

A Core Data fetch request (for a SQLite based store) can only sort on persistent attributes, and Objective-C based sort descriptors can also not be used. So there is (as far as I know) no way to make the fetched results controller return the objects sorted according to a function such as NSLocalizedString. (The only way would be to store the translated strings as additional attribute in the Core Data entity.)

But if the data is completely static (as you said), you don't really need a fetched results controller. You can fetch the objects with executeFetchRequest and then sort the resulting array according to your needs:

NSArray *unsortedRooms = [self.managedObjectContext executeFetchRequest:fetchRequest error:NULL];
NSArray *sortedRooms = [unsortedRooms sortedArrayUsingComparator:
           ^NSComparisonResult(RoomType *rt1, RoomType *rt2) {
               NSString *name1 = NSLocalizedString(rt1.typeName, @"Room-Type");
               NSString *name2 = NSLocalizedString(rt2.typeName, @"Room-Type");
               return [name1 compare:name2];
           }
];

(The advantage of a fetched results controller is that it monitors changes to objects in its associated managed object context, and reports changes in the results set to its delegate, usually a table view. But in your case, the objects do not change.)

Upvotes: 1

Related Questions