Override NSUserDefault preferences at build time - ios

I would like to be able to set app preferences at build time in my iOS project. I know that I can create different targets in xcode but I think with the amount of preferences I might end of making I could end up with a nightmarish amount of targets in my project.
An easy example is setting the a default integer for a default called 'amount'. Currently 'amount' is defined in a plist file in my app called 'preferences.plist'. I load that plist file and register defaults with that plist on NSUserDefaults.
NSURL *preferencesFile = [[NSBundle mainBundle] URLForResource:#"preferences" withExtension:#"plist"];
NSDictionary *defaultPreferences = [NSDictionary dictionaryWithContentsOfURL:defaultPreferencesFile];
[[NSUserDefaults standardUserDefaults] registerDefaults:preferences];
I assume I could write a script to modify the preferences.plist file before I build, then build it. However I think might become a nightmare when I need to mod a bunch of different preferences.
End game is to have jenkins build my IPAs. I would like to easily create multiple jenkins builds that will point to the same code based but build my app with different preferences.
Android has flavors, and the ability to set resource values. Does iOS have a something similar that I can use to build these different 'flavors' of apps?

I use a Jenkins build action to inject the appropriate variables into the plist before the Xcode build:
plutil -replace MyBuildBranch -string ${BRANCH} MyProj/MyProj-Info.plist
I then read that value at runtime using something like:
NSBundle * bundle = [NSBundle bundleForClass:[AppDelegate class]];
NSString * myBuildBranch = bundle.infoDictionary[#"MyBuildBranch"]

I don't have enough working experience with Android.
I'd approach this with multiple plists.. One for each flavor..
And i'll try either of the below options --
I'd have Jenkins swap the plist based on the flavor i am trying to build.. the script will pick the right plist for a given flavor
I will define compile-time MACROS for each flavor and load the appropriate plist.. something like this
#ifdef FLAVOUR1
NSURL *preferencesFile = [[NSBundle mainBundle] URLForResource:#"preferences-flavour1" withExtension:#"plist"];
NSDictionary *defaultPreferences = [NSDictionary dictionaryWithContentsOfURL:defaultPreferencesFile];
[[NSUserDefaults standardUserDefaults] registerDefaults:preferences];
#endif
#ifdef FLAVOUR2
NSURL *preferencesFile = [[NSBundle mainBundle] URLForResource:#"preferences-flavour2" withExtension:#"plist"];
NSDictionary *defaultPreferences = [NSDictionary dictionaryWithContentsOfURL:defaultPreferencesFile];
[[NSUserDefaults standardUserDefaults] registerDefaults:preferences];
#endif
#ifdef FLAVOUR2
NSURL *preferencesFile = [[NSBundle mainBundle] URLForResource:#"preferences-flavour3" withExtension:#"plist"];
NSDictionary *defaultPreferences = [NSDictionary dictionaryWithContentsOfURL:defaultPreferencesFile];
[[NSUserDefaults standardUserDefaults] registerDefaults:preferences];
#endif

Related

Access strings file directly without a bundle (from another app)

I'm trying to access a strings file I use in my iOS app project, from a mac console app I'm writing.
Obviously with my ios app I can access this directly via the bundle...
NSString *fname = [[NSBundle mainBundle] pathForResource:#"whatever" ofType:#"strings"];
NSDictionary *d = [NSDictionary dictionaryWithContentsOfFile:fname];
NSString *loc = [d objectForKey:#"LABEL_001"];
However, I want to retain the parsing functionality but access the strings file directly.
I haven't been able to find a method to do this?

iOS: strings.xml equivalent in iOS app dev?

In the app I'm making I have a lot of huge strings. I don't want to hardcode these into my code because it makes the code unbearably messy. When I made a similar android app, it was a simple matter of declaring the string in strings.xml as
<string name="hello_world">Hello World!!</string>
and accessing it in the java file with
getString(R.string.hello_world);
How can I do something like this with iOS?
You could put them into a .plist file and load them using the following code (which assumes a file called strings.plist which has been copied into the app bundle):
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"strings" ofType:#"plist"];
NSDictionary *dict = [[NSDictionary alloc] initWithContentsOfFile:filePath];
NSString *string1 = [dict objectForKey:#"string1"];
However if you want to internationalize your app, then using Apple's Internationalization and Localization technology might be something to look at instead.
EDIT: you'll want to load that file/dictionary only once and keep it around the entire app lifetime; don't use the above code for every string access!

iOS Change app Language doesn't take effect

I am changing my iOS application preferred language dynamically using this setting:
[[NSUserDefaults standardUserDefaults] setObject:[NSArray arrayWithObject:#"ar"] forKey:#"AppleLanguages"];
Then I load a localised resource file from the main NSBundle object, but the loaded file isn't of the new language, it's loaded in the default english language until I restart the application totally then it loads the arabic localisation.
I want to force NSBundle to load the resource file in the new language #"ar" not the language been set when app starts. How?
Your method is a hacky way to get what you need, and requires app restart to take effect.
It is best to use NSLocalizedStringFromTableInBundle instead of NSLocalizedString, and provide the bundle for that language.
NSString* path = [[NSBundle mainBundle] pathForResource:#"ar" ofType:#"lproj"];
NSBundle* ar_bundle = [NSBundle bundleWithPath:path];
NSLocalizedStringFromTableInBundle(#"str", nil, ar_bundle, #"comment");
If you put the bundle in a global scope, you can create a macro for ease:
#define ARLocalizedString(str, cmt) NSLocalizedStringFromTableInBundle(str, nil, ar_bundle, cmt)
I have tried this and its working fine without restarting the app:
//Use this in constants
#ifdef NSLocalizedString
#undef NSLocalizedString
#endif
#define NSLocalizedString(str, cmt) NSLocalizedStringFromTableInBundle(str, nil, newLangbundle, cmt)
newLangbundle --> Define a global variable in .pch and vary it according to language selection using this,
NSString* path = [[NSBundle mainBundle] pathForResource:#"th" ofType:#"lproj"];
newLangbundle = [NSBundle bundleWithPath:path];
You need to synchronize user defaults after changing the language:
[[NSUserDefaults standardUserDefaults] default synchronize]

What is the basic file structure in an iOS application?

I'm trying to load a plist file in my application with
NSBundle* bundle = [NSBundle mainBundle];
NSString* plistPath = [bundle pathForResource:#"CategoryData" ofType:#"plist"];
categoryProps = [[NSArray alloc] initWithContentsOfFile:plistPath];
but the categoryProps array always ends up with 0 objects. I've placed the CategoryData.plist file under the "Supporting Files" dir in my project but I can't figure out how files are arranged in the compiled app.
Can someone point me to docs that describe how the file system of an app is laid out and how to figure out where files are located within the file system?
I forgot to point out that I am using XCode 4 which does not create a resources folder for the project
Your loading code code should work for locating the file within the file system. In a project, I have:
NSString *data = [[NSBundle mainBundle] pathForResource:#"data" ofType:#"plist"];
What I would do is log the plistPath to the console or inspect it in the debugger, then navigate to that location on disk and determine if the plist ends up where you think it does.
Also, locate your application bundle in /Users/<# Username #>/Library/Developer/Xcode/DerivedData/<# Unique Appname #>/Build/Products/Debug-iphonesimulator/<# Appname #>.app, right click on it and select "Show package Contents". Ensure that you see your plist where you think you should.
You need to place your plist file in the Resources folder. Then you will be able to load and use them like this
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *finalPath = [path stringByAppendingPathComponent:#"Info.plist"];
NSDictionary *plistData = [[NSDictionary dictionaryWithContentsOfFile:finalPath] retain];
UPD: In xcode4 you must to place plist files in the "Supporting Files" directory instead of "Resources". And try to use NSDictionary instead of NSArray

Get fileSize of info.plist to prevent piracy

I'm trying to put anti-piracy code in my app. The previous answer to this (which I can't link to because of my member status - sucks) can be easily countered, since the "SignerIdentity" string can be looked for and replaced in the binary using a hex editor.
Instead, checking the fileSize of the info.plist file and comparing it to a reference value sounds more solid (since the info.plist is getting modified here and there when cracking the app). How would I do that? I tried the following but it logs 0.
NSBundle *bundle = [NSBundle mainBundle];
NSDictionary *mainDictionary = [bundle infoDictionary];
NSLog(#"%d", [mainDictionary fileSize]);
You might prevent the noobish crackers from finding references to "SignerIdentity" in your code by applying ROT13 or a similar simple obscuring algorithm
http://en.wikipedia.org/wiki/ROT13
After applying ROT13, "SignerIdentity" would become "FvtareVqragvgl".
Anyway, the answer to your question (how you get the size of the Info.plist file):
NSBundle *bundle = [NSBundle mainBundle];
NSString* bundlePath = [bundle bundlePath];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString* path = [NSString stringWithFormat:#"%#/Info.plist", bundlePath ];
NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:path
error:NULL];
if (fileAttributes != nil) {
NSNumber *fileSize;
if (fileSize = [fileAttributes objectForKey:NSFileSize]) {
NSLog(#"File size: %qi\n", [fileSize unsignedLongLongValue]);
}
}
Also keep in mind that the size (in bytes) of the Info.plist in your Xcode project directory and the Info.plist inside the bundle may differ. You probably want to build the game once, then look at the size of <your app bundle.app>/Info.plist and then update your antipiracy code.
I've never programmed for the iPhone, but couldn't you just take a hash of that file and compare it to a reference, possibly salting the hash value to prevent someone just changing the reference hash to the new one?
that code has still many giveaways:
the string Info.plist is easy to find. NSFileSize is also very suspicious....
As said here Determining if an iPhone is Jail broken Programmatically it looks like some of the most recent cracked apps installed via install0us don't have their info.plist modified. (at least the info.plist does not contain any signeridentity key).
How could we detect the crack in such a case ?

Resources