具有本地化字符串的核心数据排序描述符

Posted

技术标签:

【中文标题】具有本地化字符串的核心数据排序描述符【英文标题】:Core data sort descriptor with localized string 【发布时间】:2013-01-23 13:48:27 【问题描述】:

我需要一些建议。我对核心数据比较陌生。

我有一个原则上运行良好的核心数据模型。它有一个实体“RoomType”。该实体只有一个 String 类型的属性和一个与实体“Room”的关系(多)(其中逆是单关系)。关系无所谓。让我抓狂的是字符串和 sortDescriptor。

数据库内容为英文。该表是一些设置表,在安装应用程序时动态填充,用户永远不会更改。 对于任何德语和第三语言,我都需要翻译这些数据。我为此目的使用 NSLocalizedString 宏,它运行良好 - 除了按其翻译值对数据进行排序。

(对于未来的版本,我将允许用户添加记录。但是那些手动添加的记录不需要翻译。)

这是自动生成的 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

没有什么不寻常的,我会说。

这是我的视图控制器中的 fetchedResultsController getter 方法:

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

数据被送入选择器:

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


请记下数据库内容本地化的 NSLocalzedString。 这是 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
[...]

如您所见,英语中的原始数据已正确排序,但德语翻译(在“-”之后)未正确排序。

到目前为止很明显。但是我该如何解决呢?

有没有什么聪明的方法可以按数据库内容的本地化值进行排序? 我的意思是另一种方法,而不是将数据复制到数组中并随后对该数组进行排序。

【问题讨论】:

【参考方案1】:

Core Data 获取请求(对于基于 SQLite 的存储)只能对持久属性进行排序,并且也不能使用基于 Objective-C 的排序描述符。因此(据我所知)没有办法让获取的结果控制器返回根据诸如NSLocalizedString 之类的函数排序的对象。 (唯一的方法是将翻译后的字符串作为附加属性存储在核心数据实体中。)

但如果数据是完全静态的(如您所说),您实际上并不需要获取结果控制器。您可以使用executeFetchRequest 获取对象,然后根据您的需要对结果数组进行排序:

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

(获取结果控制器的优点是它监视其关联的托管对象上下文中对象的更改,并将结果集中的更改报告给其委托,通常是表视图。但在您的情况下,对象不会改变。)

【讨论】:

感谢您的回答,尽管这不是我所希望的。由于 executeFetchRequest 似乎更直接一些,我可能会走那条路。目前数据是相当静态的,但它不会保持完全静态。在未来的版本中,用户应该能够添加更多自定义房间类型(不需要翻译类型名称)。但是你为什么说“如果数据是完全静态的”? executeFetchRequest 有什么缺点吗?在适当的时候重新提出该请求有什么问题吗? (cont. ...) 或者我应该将房间类型的数组保存在全局某个地方,然后在实际发生更改时才更新它? @HermannKlecker:我的意思是:如果数据是静态的,那么使用获取的结果控制器没有任何优势,因此您不妨使用“普通”executeFetchRequest 并对获取的结果进行排序对象。您可以(例如)在视图控制器的viewDidLoad 中获取数据并对其进行排序,如果发生更改则重新获取/排序。

以上是关于具有本地化字符串的核心数据排序描述符的主要内容,如果未能解决你的问题,请参考以下文章

带有比较器的核心数据排序描述符

使用 NSDate 的核心数据排序描述符,使用 Swift 的 iOS

核心数据 - 父/子层次结构的排序描述符

核心数据和 NSSortDescriptor 未根据基于 NSString 的第三个描述符排序

NSFetchedResultsController 按部分具有不同的排序描述符?

使用两个属性,使用排序描述符对获取的结果控制器数据进行排序