Localizing Core Data model properties for display - objective-c

I'm working on an iOS project that uses a large and fairly complex data model. Some of the entities in the model have corresponding detail view controllers, which include table views that should display localized names and the corresponding values of certain properties.
I've looked at some of Apple's documentation for creating a strings file for a managed object model, but most of it seems geared toward displaying error messages generated by the SDK rather than accessing localized property names directly.
I created a strings file ("ModelModel.strings") for my model file ("Model.xcdatamodel"), and verified that it is loading correctly by looking at -localizationDictionary on my NSManagedObjectModel instance. My question is: how should I access the localized entity and property names in my code? Is there a way to get to them via NSEntityDescription, NSPropertyDescription, etc. or do I have to go through the NSManagedObjectModel every time?
I'm new at localization, so maybe the answer is obvious, but if so, feel free to just give me a nudge in the right direction.
Update
Following #ughoavgfhw's answer, I quickly came up with two categories to accomplish what I needed. Gist: https://gist.github.com/910824
NSEntityDescription:
#interface NSEntityDescription (LocalizedName)
#property (nonatomic, readonly) NSString *localizedName;
#end
#implementation NSEntityDescription (LocalizedName)
#dynamic localizedName;
- (NSString *)localizedName {
static NSString *const localizedNameKeyFormat = #"Entity/%#";
NSString *localizedNameKey = [NSString stringWithFormat:localizedNameKeyFormat, [self name]];
NSString *localizedName = [[[self managedObjectModel] localizationDictionary] objectForKey:localizedNameKey];
if (localizedName) {
return localizedName;
}
return [self name];
}
#end
NSPropertyDescription:
#interface NSPropertyDescription (LocalizedName)
#property (nonatomic, readonly) NSString *localizedName;
#end
#implementation NSPropertyDescription (LocalizedName)
#dynamic localizedName;
- (NSString *)localizedName {
static NSArray *localizedNameKeyFormats = nil;
if (!localizedNameKeyFormats) {
localizedNameKeyFormats = [[NSArray alloc] initWithObjects:#"Property/%#/Entity/%#", #"Property/%#", nil];
}
for (NSString *localizedNameKeyFormat in localizedNameKeyFormats) {
NSString *localizedNameKey = [NSString stringWithFormat:localizedNameKeyFormat, [self name], [[self entity] name]];
NSString *localizedName = [[[[self entity] managedObjectModel] localizationDictionary] objectForKey:localizedNameKey];
if (localizedName) {
return localizedName;
}
}
return [self name];
}
#end

There is no direct way to get that information provided by apple, but you could implement it yourself. You just need to add categories to NSEntityDescription, etc. which create the identifier and ask for the localized value from the model, and then treat it as if it were built in.
Here is an example NSEntityDescription implementation. For properties, you would do something similar, but you should use both the entity and property name in case multiple entities have properties with the same name (you may also need to use both the entity and property name as keys in your localization file. I don't know if the model will create them automatically).
#implementation NSEntityDescription (Localization)
- (NSString *)localizedName {
NSString *key = [NSString stringWithFormat:#"Entity/%#", [self name]];
NSDictionary *dictionary = [[self managedObjectModel] localizationDictionary];
NSString *localizedName = [dictionary objectForKey:key];
return (localizedName ? localizedName : [self name]);
}
#end
Here is a reference for the keys used in the localizations.

Related

iOS JSON serialization for NSObject-based classes

I'd like to JSON-serialize my own custom classes. I'm working in Objective-C / iOS5.
I'd like something to do the following:
Person* person = [self getPerson ]; // Any custom object, NOT based on NSDictionary
NSString* jsonRepresentation = [JsonWriter stringWithObject:person ];
Person* clone = [JsonReader objectFromJson: jsonRepresentation withClass:[Person Class]];
It seems that NSJSONSerialization (and several other libraries) require the 'person' class to be based on NSDictionary etc. I want something that will serialize any custom object that I care to define (within reason).
Let's imagine Person.h looks like this:
#import <Foundation/Foundation.h>
#interface Person : NSObject
#property NSString* firstname;
#property NSString* surname;
#end
I'd like the generated JSON for an instance to look similar to the following:
{"firstname":"Jenson","surname":"Button"}
My app uses ARC. I need something that will both serialise and deserialize using objects.
Many thanks.
This is a tricky one because the only data you can put into JSON are straight up simple objects (think NSString, NSArray, NSNumber…) but not custom classes or scalar types. Why? Without building all sorts of conditional statements to wrap all of those data types into those type of objects, a solution would be something like:
//at the top…
#import <objC/runtime.h>
NSMutableDictionary *muteDictionary = [NSMutableDictionary dictionary];
id YourClass = objc_getClass("YOURCLASSNAME");
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(YourClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
NSString *propertyName = [NSString stringWithCString:property_getName(property) encoding:NSUTF8StringEncoding];
SEL propertySelector = NSSelectorFromString(propertyName);
if ([classInstance respondsToSelector:propertySelector]) {
[muteDictionary setValue:[classInstance performSelector:propertySelector] forKey:propertyName];
}
}
NSError *jsonError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:muteDictionary options:0 error:&jsonError];
This is tricky, though because of what I stated before. If you have any scalar types or custom objects, the whole thing comes tumbling down. If it's really critical to get something like this going, I'd suggest looking into investing the time and looking at Ricard's links which allow you to see property types which would assist on the conditional statements needed to wrap the values into NSDictionary-safe objects.
Now you can solve this problem easily using JSONModel. JSONModel is a library that generically serialize/deserialize your object based on Class. You can even use non-nsobject based for property like int, short and float. It can also cater nested-complex JSON.
Deserialize example. By referring to your example, in header file:
#import "JSONModel.h"
#interface Person : JSONModel
#property (nonatomic, strong) NSString* firstname;
#property (nonatomic, strong) NSString* surname;
#end
in implementation file:
#import "JSONModelLib.h"
#import "yourPersonClass.h"
NSString *responseJSON = /*from somewhere*/;
Person *person = [[Person alloc] initWithString:responseJSON error:&err];
if (!err)
{
NSLog(#"%# %#", person.firstname, person.surname):
}
Serialize Example. In implementation file:
#import "JSONModelLib.h"
#import "yourPersonClass.h"
Person *person = [[Person alloc] init];
person.firstname = #"Jenson";
person.surname = #"Uee";
NSLog(#"%#", [person toJSONString]);
maybe this can help JLObjectStrip.
its the same as what jacob said but it iterates even to the property of the class. this will give you dictionary/array then just use sbjson/jsonkit or what ever you prefer to construct your json string.
Try this one BWJSONMatcher
It's really simple as well as convenient.
...
NSString *jsonString = #"{your-json-string}";
YourValueObject *dataModel = [YourValueObject fromJSONString:jsonString];
NSDictionary *jsonObject = #{your-json-object};
YourValueObject *dataModel = [YourValueObject fromJSONObject:jsonObject];
...
YourValueObject *dataModel = instance-of-your-value-object;
NSString *jsonString = [dataModel toJSONString];
NSDictionary *jsonObject = [dataModel toJSONObject];
...
What i do for my objects is i have a method called "toDict" that return a nsdictionary. IN this method i set all attributes i need/want into the dictionary for example
[user setObject:self.first_name forKey:#"first_name"];
[user setObject:self.last_name forKey:#"last_name"];
[user setObject:self.email forKey:#"email"];

Accessing NSDictionary in Singleton Class w/o Creating Copies

I have a singleton as follows, which creates an instance of NSDictionary to hold my data. Here is the .h:
#interface FirstLast : NSObject
#property (strong, nonatomic, readonly) NSArray *firstArray;
#property (strong, nonatomic, readonly) NSArray *lastArray;
#property (strong, nonatomic, readonly) NSDictionary *fl;
+ (FirstLast *) firstLast;
- (NSDictionary *) tempDic;
#end
Here is the .m
#implementation FirstLast
#synthesize firstArray = _firstArray;
#synthesize lastArray = _lastArray;
#synthesize fl = _fl;
+ (FirstLast *)firstLast {
static FirstLast *singleton;
static dispatch_once_t once;
dispatch_once(&once, ^{
singleton = [[FirstLast alloc] init];
NSLog(#"FirstLast instantiated");
});
return singleton;
}
- (NSDictionary *) tempDic{
_firstArray = [[NSArray alloc] initWithObjects:#"Bob", #"Joe", #"Sally", #"Sue", nil];
_lastArray = [[NSArray alloc] initWithObjects:#"Jones", #"Johnson", #"Thompson", #"Miller", nil];
_fl = [NSDictionary dictionaryWithObjects:_firstArray
forKeys:_lastArray];
NSLog(#"tempDic just made _fl at this address");
NSLog(#"%p", _fl);
return _fl;
}
#end
All of this works fine. In the view controller I instantiate all this for the first time (works fine too):
NSLog(#"VC is setting up tempDic");
[[WordsClues wordsClues] tempDic];
When I try to gain access to tempDic elsewhere, like this:
NSInteger rIndex = arc4random_uniform(4) + 1;
NSString *fname = [[[FirstLast firstLast].tempDic allValues] objectAtIndex:rIndex];
it works fine, but, when I repeat this process, each time I'm creating a new tempDic. I know this because the NSLog giving the address gives a different answer each time. I really want to access the existing dictionary, which is what I thought my singleton was going to accomplish. Clearly I'm either not accessing tempDic correctly or I misunderstand what the singleton can do for me or I have the tempDic set up wrong. The goal is to get a random value from a single copy of tempDic and not write local copies of tempDic all over the place. Thanks.
Why do you recreate the dictionary in -tempDic at all?
I.e. move the dictionary instantiation code to init and then just return _fl; in tempDic.
No worries -- we've all been there [new].
In your FirstLast class, implement the init method as something like:
- init
{
self = [super init];
if ( self ) {
_fl = ... create your dictionary here ...;
}
return self;
}
Then change -tempDic to:
- (NSDictionary*)tempDic {
return _fl;
}
I would highly recommend that you read a good intro to Objective-C book. I'm a purist and, thus, would recommend going to the source for the information, but there are lots of books available.
The questions you are asking are more in line with "What is object oriented programming and how does Objective-C work?".
To answer your question; FirstLast is a class and the singleton pattern makes sure there is exactly one instance of that class. By moving the creation of the dictionary to the init method -- which is called only once and who stores a reference to the created dictionary in an instance variable -- you avoid creating multiple dictionary instances.
Every time you call tempDic, you create a new copy of it. What you should do is add you code for creating the dictionary to your alloc instance, and then just retrieve it in your getter.
Alternativly you can do this
- (NSDictionary *) tempDic{
if( _fl == nil )
{
_firstArray = [[NSArray alloc] initWithObjects:#"Bob", #"Joe", #"Sally", #"Sue", nil];
_lastArray = [[NSArray alloc] initWithObjects:#"Jones", #"Johnson", #"Thompson", #"Miller", nil];
_fl = [NSDictionary dictionaryWithObjects:_firstArray
forKeys:_lastArray];
NSLog(#"tempDic just made _fl at this address");
NSLog(#"%p", _fl);
}
return _fl;
}

Singleton class DataLoader - set values from Core Data in AppDelegate, however cannot access DataLoader variables in other classes

I have been wracking my brain, and a lot of searching, but no luck so far.
Basically, I have setup Core Data to load data from an sqlite db in my AppDelegate class into shared variables in a singleton class called DataLoader.
I also have a main class called GameScene, which will also use DataLoader data.
What I am trying to do is as follows:
load data from DB (using the background thread) and store results in DataLoader singleton class variables (specifically an NSMutableArray)
In this way the data is loaded while the scenes transition to main menu area
In the GameScene class I try to access the data stored in the DataLoader singleton, and use this data in the game.
My issue is that although I can see that I store objects in the AppDelegate class, I don't seem able to return the objects in the GameScene class. Usually ends in an EXC_BAD_ACCESS error.
I am really looking for some example of something similar or if you have any ideas.
If you need code samples, let me know.
Thanks,
Pras.
*EDIT - Code Snippets*
DataLoader.h
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface DataLoader : NSObject {
}
//setup singleton
+(DataLoader *) sharedDataLoader;
#property (nonatomic, retain) NSMutableArray * veHint;//2 characters
DataLoader.m
#import "DataLoader.h"
#import <CoreData/CoreData.h>
#implementation DataLoader
#synthesize veHint;
static DataLoader * myDataLoader = nil;
+(DataLoader *) sharedDataLoader{
if(myDataLoader == nil){
myDataLoader = [[[DataLoader alloc]init]retain];
}
return myDataLoader;
}
-(id) init
{
if((self = [super init])){
veHint = [NSMutableArray arrayWithCapacity:10];
}
return self;
}
AppDelegate.mm
#import "AppDelegate.h"
#import "GameScene.h"
#import "RootViewController.h"
#import "Word.h"
#import "DataLoader.h"
-(void) applicationDidFinishLaunching:(UIApplication*)application
{
//other standard stuff here...
//my load data method using data loader
[self getRandomData];
//other standard stuff here...
// Run the intro Scene
[[CCDirector sharedDirector] runWithScene: [GameScene node]];
}
//other standard methods for Core Data
//Random data loader
-(void) fetchRandomData: (NSString *) searchInteger{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSLog(#"fetching random data for word size: %#", searchInteger);
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Word" inManagedObjectContext:managedObjectContext];
[request setEntity:entity];
//setup predicate
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY %K == %#", #"size", searchInteger];
[request setPredicate:predicate];
NSError *error = nil;
NSArray *fetchResults = [managedObjectContext executeFetchRequest:request error:&error];
NSLog(#"fetch results count = %i", [fetchResults count]);
[request release];
NSMutableArray * results = [NSMutableArray arrayWithCapacity:10];
NSMutableArray * fetchedIndexes = [NSMutableArray arrayWithCapacity:10];
//get the total count, randomize selection of 10 results from the list
int j = 0;
while (j<10) {
int index = arc4random() % [fetchResults count];
//get only unique values
if ([fetchedIndexes containsObject:[NSNumber numberWithInt:index]] == false) {
[fetchedIndexes addObject:[NSNumber numberWithInt:index]];
Word * word = (Word *) [fetchResults objectAtIndex:index];
//NSLog(#"j = %i, index = %i, wordsize = %#, wordstring = %#",j, index, word.size, word.wordString);
[results addObject:word];
j++;
}
}
//set dataloader
[[DataLoader sharedDataLoader] setVeHint:results];
[pool drain];
}
Also, need to mention that the EXC_BAD_ACCESS is on the [pool drain] call in AppDelegate.mm.
Thanks in advance for your help.
The problem is probably somewhere in the code (I think it is memory/scope-related, like too early release etc.). It really shouldn't matter how you access your data as long as you're keeping every piece of it properly "memory managed".
I can show you how I do this kind of data management.
I usually name it DataManager:
DataManager.h
#import <Foundation/Foundation.h>
#interface DataManager : NSObject {
NSDate *someDate;
}
#property (nonatomic, retain) NSDate *someDate;
+(DataManager*)sharedInstance;
- (void)loadSettings;
- (void)saveSettings;
#end
DataManager.m
#import "DataManager.h"
static DataManager *dataManagerInstance;
#implementation DataManager
#synthesize someDate = _someDate;
// singleton method
+(DataManager*)sharedInstance {
if(!dataManagerInstance) {
dataManagerInstance = [[DataManager alloc] init];
}
return dataManagerInstance;
}
- (void)saveSettings {
// save your data
}
- (void)loadSettings {
// load your data
}
I hope you got the idea… maybe you post some code snippets so we can find what's going wrong with your singleton ;-)
Update: OK, so you added code snippets and the problem is obviously in this line:
[[DataLoader sharedDataLoader] setVeHint:results];
You are setting veHint to be results, so you are overwriting your private variable of DataLoader. You shouldn't do it that way, since results is released and DataLoader gets lost of the reference.
You should either write another setter method, which makes a copy of results and puts the elements in veHint
or
retain results after passing it to DataLoader like this:
[[DataLoader sharedDataLoader] setVeHint:[results retain]];
However, this solution (and also yours) creates a memory leak, since you retained veHint in DataLoader and overwrite it in your Delegate (you completely lose the reference to veHint).
Core Data is not thread safe by default. If you are trying to use it on multiple threads, make sure you are familiar with Apple's guidelines.

NSDictionary add -objectAtIndex by category

I'm writing custom patch for Quartz Composer and I work with structures as input. Structure are NSDictionary in Obj-C so I use NSDictionary's methods to deal with these structures. Depending on how I build these structures key can be NSString type or NSNumber.
Usually QC doesn't give named structures and keys are "0"..."n" or 0...n so I've decided to add -objectAtIndex in a NSDictionary category :
NSDictionary+Utils.h
#import <Foundation/Foundation.h>
#interface NSDictionary (Utils)
- (id)objectAtIndex:(NSInteger)index;
#end
NSDictionary+Utils.m
#import "NSDictionary+Utils.h"
#implementation NSDictionary (Utils)
- (id)objectAtIndex:(NSInteger)index
{
NSString* key = [NSString stringWithFormat:#"%d", index];
return [self objectForKey:key];
}
#end
It works perfectly for one of my project in which I build a structure using Kineme Struc Maker patch.
In another project I use the Queue patch provided by apple. Keys are not NSString type but NSNumber type. I've rewritten the code to be compliant with NSNumber keys :
NSDictionary+Utils.m
#implementation NSDictionary (Utils)
- (id)objectAtIndex:(NSInteger)index
{
NSNumber* key = [NSNumber numberWithInteger:index];
return [self objectForKey:key];
}
#end
Unfortunately it always gives a null value but using :
[self.inputSoundStruct objectForKey:[NSNumber numberWithInteger:0]]
Why does this line of code works but not objectAtIndex ? I must be missing something but what ?

Pass array from one Objective-C class to another

Im attempting to pass an array that is created in one class into another class. I can access the data but when I run count on it, it just tells me that I have 0 items inside the array.
This is where peopleArray's data is set up, it's in a different class than the code that is provided below.
[self setPeopleArray: mutableFetchResults];
for (NSString *existingItems in peopleArray) {
NSLog(#"Name : %#", [existingItems valueForKey:#"Name"]);
}
[peopleArray retain];
This is how I get the array from another class, but it always prints count = 0
int count = [[dataClass peopleArray] count];
NSLog(#"Number of items : %d", count);
The rest of my code:
data.h
#import <UIKit/UIKit.h>
#import "People.h"
#class rootViewController;
#interface data : UIView <UITextFieldDelegate>{
rootViewController *viewController;
UITextField *firstName;
UITextField *lastName;
UITextField *phone;
UIButton *saveButton;
NSMutableDictionary *savedData;
//Used for Core Data.
NSManagedObjectContext *managedObjectContext;
NSMutableArray *peopleArray;
}
#property (nonatomic, assign) rootViewController *viewController;
#property (nonatomic, retain) NSManagedObjectContext *managedObjectContext;
#property (nonatomic, retain) NSMutableArray *peopleArray;
- (id)initWithFrame:(CGRect)frame viewController:(rootViewController *)aController;
- (void)setUpTextFields;
- (void)saveAndReturn:(id)sender;
- (void)fetchRecords;
#end
data.m(some of it at least)
#implementation data
#synthesize viewController, managedObjectContext, peopleArray;
- (void)fetchRecords {
[self setupContext];
// Define our table/entity to use
NSEntityDescription *entity = [NSEntityDescription entityForName:#"People" inManagedObjectContext:managedObjectContext];
// Setup the fetch request
NSFetchRequest *request = [[NSFetchRequest alloc] init];
[request setEntity:entity];
// Define how we will sort the records
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"Name" ascending:NO];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];
[sortDescriptor release];
// Fetch the records and handle an error
NSError *error;
NSMutableArray *mutableFetchResults = [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if (!mutableFetchResults) {
// Handle the error.
// This is a serious error and should advise the user to restart the application
}
// Save our fetched data to an array
[self setPeopleArray: mutableFetchResults];
for (NSString *existingItems in peopleArray) {
NSLog(#"Name : %#", [existingItems valueForKey:#"Name"]);
}
[peopleArray retain];
[mutableFetchResults release];
[request release];
//NSLog(#"this is an array: %#", eventArray);
}
login.h
#import <UIKit/UIKit.h>
#import "data.h"
#class rootViewController, data;
#interface login : UIView <UITextFieldDelegate>{
rootViewController *viewController;
UIButton *loginButton;
UIButton *newUser;
UITextField *entry;
data *dataClass;
}
#property (nonatomic, assign) rootViewController *viewController;
#property (nonatomic, assign) data *dataClass;
- (id)initWithFrame:(CGRect)frame viewController:(rootViewController *)aController;
- (BOOL)textFieldShouldReturn:(UITextField *)theTextField;
#end
login.m
#import "login.h"
#import "data.h"
#interface login (PrivateMethods)
- (void)setUpFromTheStart;
- (void)loadDataScreen;
-(void)login;
#end
#implementation login
#synthesize viewController, dataClass;
-(void)login{
int count = [[dataClass peopleArray] count];
NSLog(#"Number of items : %d", count);
}
Is it the same object? If so, what you have should work. Check to see how you are getting the dataClass instance -- if you alloc a new one, you don't get the array from the other object.
Edit: From your comments below, it appears that you are having some confusion on the difference between classes and objects. I will try to explain (I'm going to simplify it):
A class is what you write in Xcode. It's the description that lets your application know how to create and access objects at run-time. It is used to figure out how much memory to allocate (based on instance variables) and what messages can be sent, and what code to call when they are. Classes are the blueprints for creating objects at runtime.
An object only exists at run-time. For a single class, many objects of that class can be created. Each is assigned its own memory and they are distinct from each other. If you set a property in one object, other objects don't change. When you send a message to an object, only the one you send it to receives it -- not all objects of the same class.
There are exceptions to this -- for example if you create class properties (with a + instead of a - at the beginning), then they are shared between all objects -- there is only one created in memory, and they all refer to the same one.
Also, since everything declared with a * is a pointer -- you could arrange for all pointer properties to point to the same data. The pointer itself is not shared.
Edit (based on more code): dataClass is nil, [dataClass peopleArray] is therefore nil, and then so is the count message call. You can send messages to nil, and not crash, but you don't get anything useful.
I don't see how the login object is created. When it is, you need to set its dataClass property.
Try running the code in the debugger, setting breakpoints, and looking at variables.
From the code, it looks like you are passing a mutable array.
[self setPeopleArray: mutableFetchResults];
Probably the items of the array are removed somewhere in your calling class / method. Or the array is reset by the class from which you get the mutableFetchResults in the first place.