How to force localization at compileTime or better at runtime - objective-c

i've got a IOS project with many different images to display depending on the language, and i can't find a way to force loading the correct image at runtime (not switching a language to another).
Actually it seems that the files are loaded before the AppDelegate get a chance to interfer.
if i do in appDelegate (didFinishLaunchingWithOptions:):
[[NSUserDefaults standardUserDefaults] setObject: [NSArray arrayWithObjects: #"es", #"en", nil] forKey:#"AppleLanguages"];
It's already too late, image are already loaded in the wrong language. So in order to accomplish this i have moved the NSUserDefaults into the main() call, and this tricks works. But my questions are: Do Apple will reject my app? And is there a better, clean approach to synchronize a language at compile time (or with plist or something like that)?
Update
it seems that just replacing [[NSUserDefaults standardUserDefaults] setObject: forKey:], further step before calling my nib file did the trick. It's like [NSUserDefaults standardUserDefaults] is called asynchronously. So maybe the setter method was actually evaluated after my nib file was loaded. But can i change the localizable setting via info.plist ?

You can localize the Info.plist (InfoPlist.strings), which lets you set the Default.png for each language.

Related

how to refresh localizable.strings or NSLocalizedString(key, comment) ios during runtime

I'm working on a app that supports multiple language. Everything is going well. But I need to change the language during runtime.
I'm doing it this way -
NSArray* languages = [NSArray arrayWithObjects:#"es",#"en" nil];
[[NSUserDefaults standardUserDefaults] setObject:languages forKey:#"AppleLanguages"];
[[NSUserDefaults standardUserDefaults] synchronize];
It is working well.
NSString *langID = [[NSLocale preferredLanguages] objectAtIndex:0];
This langID is showing the correct language. But the app is not changing its language until its been restarted.
Is there any way to refresh localizable.strings file or NSLocalizedString(key, comment)? Or any other way to do it without restarting?
This has been asked many times already here on StackOverflow (search for things like iOS change language at runtime).
You can't change the language at runtime when using NSLocalizedString. If you really, really need that, you need to use a custom localization system instead. Better yet: simply don't do that: it's unexpected. After all, the user can set his/her preferred languages (and their order) in the system settings.
Someone even asked an Apple engineer and this is the response:
In general, you should not change the iOS system language (via use of the AppleLanguages pref key) from within your application. This goes against the basic iOS user model for switching languages in the Settings app, and also uses a preference key that is not documented, meaning that at some point in the future, the key name could change, which would break your application.
If you want to switch languages in your application, you can do so via manually loading resource files in your bundle. You can use NSBundle:pathForResource:ofType:inDirectory:forLocalization: for this purpose, but keep in mind that your application would be responsible for all loading of localized data.
To change the app language during runtime, I have done this manually by creating two different storyboards. I saved the language preference in NSUserDefaults but NOT with the key AppleLanguages and then called AppDelegate's didFinishLaunchingWithOptions method to select storyboard.
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainEnglish" bundle:nil];
UINavigationController *navigationcontroller=[[UINavigationController alloc]init];
RegistorViewController *registor=[storyboard instantiateViewControllerWithIdentifier:#"Registor"];
[self.window setRootViewController:navigationcontroller];
[self.window makeKeyAndVisible];
[navigationvontroller pushViewController:registor animated:NO];

OSX preferences file - removePersistentDomainForName has different functionality in 10.8?

This code works for one of our developers on 10.7 but not for me on 10.8
working = it deletes the preferences .plist file for the bundle. The dev on 10.7 also has a lockfile whereas I do not. It's not a problem of file access - I tried [resetStandardUserDefault] and that made a new file, but that's not exactly we want to do.
NSString *appDomain = [[NSBundle mainBundle] bundleIdentifier];
[[NSUserDefaults standardUserDefaults] removePersistentDomainForName:appDomain];
I am having trouble locating any info about changes in 10.8 - does this sound familiar to anyone?
I have the same issue and indeed there seems to be no info about any changes, also don't get any errors or warnings. [NSUserDefaults resetStandardUserDefault] doesn't do anything for me. What I do currently is simply overwrite the default values to reset them (give [[NSUserDefaults standardUserDefaults] setValuesForKeysWithDictionary:
[NSDictionary dictionaryWithObjectsAndKeys:...]] the same input as originally given to [[NSUserDefaults standardUserDefaults] registerDefaults:
[NSDictionary dictionaryWithObjectsAndKeys:...]].
Not really an answer, but I don't have enough rep to comment..

How to create Multiple Themes/Skins for iphone apps? [closed]

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 11 years ago.
I have an iphone app ready and approved by the app store. Now I want to create different themes for my app. Can someone please help me out, with info/links/steps on how to create themes for my app?
I want to create a Metal theme for the Boys and a Pink theme for the Girls. Again by theme I mean, the whole app(features and functionality) is gonna stay the same, but depending on who the user is(boy or girl), he/she can choose the theme they wish to see. And when the theme changes, only the images/Background/music will change according to the applied theme.
Thanks a lot!
This is quite difficult as apps don't have the equivalent of a css stylesheet.
First you need to work out what parts of the app you want to skin, and when you want to allow the user to swap skins.
I'm going to assume that you want to change images and font colours, and that it's okay if the user has to relaunch the app to change the skin (that will make things simpler for now).
Create a plist containing all your skinnable images and colours. The plist will be a dictionary with sensible, theme neutral key names for the images and colours (e.g. don't have a colour called "red", call it "primaryHeadingColor"). Images will be file names, and colours can be hex strings, e.g. FF0000 for red.
You'll have one plist for each theme.
Create a new class called ThemeManager and make it a singleton by adding the following method:
+ (ThemeManager *)sharedManager
{
static ThemeManager *sharedManager = nil;
if (sharedManager == nil)
{
sharedManager = [[ThemeManager alloc] init];
}
return sharedManager;
}
The ThemeManager class will have an NSDictionary property called "styles", and in the init method you will load the theme into your styles dictionary like this:
- (id)init
{
if ((self = [super init]))
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *themeName = [defaults objectForKey:#"theme"] ?: #"default";
NSString *path = [[NSBundle mainBundle] pathForResource:themeName ofType:#"plist"];
self.styles = [NSDictionary dictionaryWithContentsOfFile:path];
}
return self;
}
(Note: some people don't like doing a lot of work inside an init method. I've never found it to be an issue, but if you prefer, create a separate method to load the themes dictionary and call it from your app's setup code).
Notice how I'm getting the name for the theme plist from user defaults. That means the user can select a theme in your preferences and save it and the app will load that theme next time it is launched. I've put in a default theme name of "default" if no theme is selected, so make sure you have a default.plist theme file (or change the #"default" in the code to whatever your default theme plist is actually called).
Now that you've loaded your theme you need to use it; I'm assuming your app has various images and text labels. If you're loading and laying those out in code then this part is easy. If you are doing it in nibs then it's a bit trickier but I'll explain how to handle that later.
Now normally you would load an image by saying:
UIImage *image = [UIImage imageNamed:#"myImage.png"];
But if you want that image to be themable, you'll now need to load it by saying
NSDictionary *styles = [ThemeManager sharedManager].styles;
NSString *imageName = [styles objectForKey:#"myImageKey"];
UIImage *image = [UIImage imageNamed:imageName];
That will look in your theme file for the themed image that matches the key "myImageKey" and will load it. Depending on which theme file you've loaded you'll get a different style.
You'll be using those three lines a lot so you may want to wrap them up in a function. A great idea would be to create a category on UIImage that declares a method called something like:
+ (UIImage *)themeImageNamed:(NSString *)key;
Then to use it you can just replace any calls to [UIImage imageNamed:#"foo.png"]; with [UIImage themeImageNamed:#"foo"]; where foo is now the theme key instead of the actual image name.
Okay, so that's it for theming your images. To theme your label colours, suppose you're currently setting your label colours by saying:
someLabel.color = [UIColor redColor];
You would now replace that with:
NSDictionary *styles = [ThemeManager sharedManager].styles;
NSString *labelColor = [styles objectForKey:#"myLabelColor"];
someLabel.color = [UIColor colorWithHexString:labelColor];
Now you may have noticed that UIColor doesn't have a method "colorWithHexString:" - you'll have to add that using a category. You can Google for "UIColor with hex string" solutions to find code to do that, or I've written a handy category that does that and a bit more here: https://github.com/nicklockwood/ColorUtils
If you've been paying attention you'll also be thinking that instead of writing those three lines over and over, why not add a method to UIColor called:
+ (UIColor *)themeColorNamed:(NSString *)key;
Just like we did with UIImage? Great idea!
So that's it. Now you can theme any image or label in your app. You could use the same trick to set the font name, or any number of other potentially themable visual properties.
There's just one tiny thing we've forgotten...
If you've built most of your views as nibs (and I see no reason why you wouldn't) then these techniques aren't going to work because your image names and font colours are buried inside impenetrable nib data and aren't being set in your source code.
There are a few approaches to solve this:
1) You could make duplicate themed copies of your nibs and then put the nib names in your theme plist and load them from your theme manager. That's not too bad, just implement the nibName method of your view controllers like this:
- (NSString *)nibName
{
NSDictionary *styles = [ThemeManager sharedManager].styles;
return [styles objectForKey:NSStringFromClass([self class])];
}
Notice my neat trick of using the class name of the view controller as the key - that will save you some typing because you can just make a base ThemeViewController with that method and have all your themable view controllers inherit from it.
This approach does mean maintaining multiple copies of each nib though, which is a maintenance nightmare if you need to change any screens later.
2) You could make IBOutlets for all of the imageViews and labels in your nibs, then set their images and colors in code in your viewDidLoad method. That's probably the most cumbersome approach, but at least you don't have duplicate nibs to maintain (this is essentially the same problem as localising nibs btw, and pretty much the same solution options).
3) You could create a custom subclass of UILabel called ThemeLabel that automatically sets the font color using the code above when the label is instantiated, then use those ThemeLabels in your nib files instead of regular UILabels by setting the class of the label to ThemeLabel in Interface Builder. Unfortunately if you have more than one font or font colour, you'll need to create a different UILabel subclass for each different style.
Or you could be devious and use something like the view tag or accessibilityLabel property as the style dictionary key so that you can have a single ThemeLabel class and set the accessibility label in Interface Builder to select the style.
The same trick could work for ImageViews - create a UIImageView subclass called ThemeImageView that, in the awakeFromNib method replaces the image with a theme image based on the tag or accessibilityLabel property.
Personally I like option 3 best because it saves on coding. Another advantage of option 3 is that if you wanted to be able to swap themes at runtime, you could implement a mechanism where your theme manager reloads the theme dictionary, then broadcasts an NSNotification to all the ThemeLabels and ThemeImageViews telling them to redraw themselves. That would probably only take about an extra 15 lines of code.
Anyway, there you have a complete iOS app theming solution. You're welcome!
UPDATE:
As of iOS 5, it's now possible to set custom attributes by keyPath in Interface Builder, meaning that it's no longer necessary to create a view subclass for each themable property, or abuse the tag or accessibilityLabel for selecting styles. Just give your UILabel or UIImageView subclass a string property to indicate which theme key it should use from the plist, and then set that value in IB.
UPDATE 2:
As of iOS 6, there is now a limited skinning system built into iOS that allows you to use a property called the UIAppearance proxy to skin all instances of a given control class at once (there's a good tutorial about the UIAppearance APIs here). It's worth checking if this is sufficient for your skinning needs, but if not, the solution I outlined above still works well, and can be used instead, or in combination with UIAppearance.

Push button's with NSGraphiteControlTint

I want to change button tint from Aqua to Graphite. Why this code doesn't work?
[[myButton cell] setControlTint:NSGraphiteControlTint];
I don't know how to do it for one your control. I think something was changed in setControlTint work rules in OS 10.6 and later. But it's only hypothesis.
In any case you can try to use this code
[[NSUserDefaults standardUserDefaults] setInteger:NSGraphiteControlTint forKey:#"AppleAquaColorVariant"
But remember that it should be insert before NSApplicationMain(argc, (const char **)argv); You can also create subclass for your application class and change default settings in init method.
You can also set graphite theme for concrete window:
NSColorSpace* space = [NSColorSpace genericGrayColorSpace];
[_window setColorSpace:space];
It's very possible that the specific NSCell subclass you're using doesn't make use of the controlTint (and for NSButtonCell I don't think every buttonType supports the controlTint), I don't think all . With the direction Apple is taking in its recent UI I wouldn't be surprised if this eventually becomes deprecated.
If you do need a cell with a different tint, you could always subclass it and implement it directly.

AppDelegate int value keeps getting reset

In my project AppDelegate file, I have an int declared called correctAnswersCountR1. So in the app they take a small quiz and that variable keeps track of how many correct answers they got. Now somewhere else in the project I use this variable like so:
int r1score=appDelegate.correctAnswersCountR1;
The problem is that apparently if I exit the app and come back, the value isn't remembered, and is set back to its default value. How can I store this number so that it is remembered if the user closes the app and comes back?
You can use NSUserDefaults to store your value:
**Saving**
//Do this right before the app exits
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
// saving an NSInteger
[prefs setInteger:42 forKey:#"integerKey"];
// This is suggested to synch prefs, but is not needed
[prefs synchronize];
**Retrieving**
//Do this when your app is loaded again
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
// getting an NSInteger
NSInteger myInt = [prefs integerForKey:#"integerKey"];
You could set up core data for your project, though unless you plan on storing more information persistently for your app Core Data might be a bit of overkill. You could also try writing it to a file and just reading it on load.
Edit: For this specific scenario Oscar's answer seems more appropriate, though depending on where you plan on going with your app, CoreData or a file may be a good choice.
You'll want to do some research about NSUserDefaults.
Here is the apple documentation
On another note it's not considered best practice to store data in the AppDelegate
You have to use a preferences storing mechanism. I'm not all too familiar with the iPhone toolkit, but on Mac OS X and Cocoa there's NSUserDefaults. When your app launches, you would load the value from the user defaults (preferences) through [[NSUserDefaults standardUserDefaults] intForKey:#"count"], and whenever the value changes, you would call [[NSUserDefaults standardUserDefaults] setInt:answerCount... forKey:#"count"].
I'm pretty sure that you want to write the value into a file to make it persistent.
This is because an application which moved to the background might be terminated for example if the system measures out that it needs some more memory.
So it kills the application and restarts it when the user switches over.