Build configuration for Lite/Full apps in Xcode - iphone

I'm developing a Lite version of an existing iPhone app and I'm trying to figure out the best way to conditionally compile the full and lite versions. Ideally I'd like to use a LITE preprocessor define but is there a good way to set this so I can choose a different item from the scheme menu and just compile whichever version I want?
I don't want to duplicate my "full" target because I'm concerned I'll change a setting in one version and not copy it to the other one. I looked at using an aggregate target but it doesn't run when I press Command-R.
What's the best way to achieve this without duplication?

Sounds like your using xcode4? You could do this by defining a new configuration (beyond the standard ones of Debug & Release).
Then, add your own preprocessor macro for the additional configuration.
Finally, add a new scheme to select the new configuration.

In the end I decided it was best to resolve this without using build configurations. My app uses in-app-purchase to support upgrading to the full, non-lite, version. As such, I want to have every feature compiled and ready to go.
I now manually update the bundle identifier if I want to switch between full and lite versions, and check this when the app runs. I have to change the bundle identifier anyway when I submit either version, so this way I change one file and there's no duplication. Within the app I call the method below to enable/disable lite features.
+ (BOOL)isLiteVersion {
static BOOL haveCheckedLiteStatus = NO;
if (haveCheckedLiteStatus == NO) {
BOOL isLiteApp = [[[NSBundle mainBundle] bundleIdentifier] rangeOfString:#"lite"].location != NSNotFound;
isLite = isLiteApp && [[NSUserDefaults standardUserDefaults] boolForKey:kUpgraded] == NO;
haveCheckedLiteStatus = YES;
}
return isLite;
}

Related

Change an App Setting when updating the iPhone application to a new Version

I want to change the URL that my application connects to, to a new sever. The problem is, the URL value is saved in a settings file. When I update the application to the new version, the old file is read from the device, and overwrites my settings. What I want is to use the new URL the first time the updated version is launched. After that, I am happy to read the URL from the file.
Is there any way I can determine this is the first time after an update when I lauch the application?
Thanks!
As another user, you can get the current version by reading the CFBundleVersion of your app's bundle. The problem with this approach is that a user might not install "version 1" of your app. Instead, I suggest putting something like the following in your app's didFinishLaunching method:
#define kSettings [NSUserDefaults standardUserDefaults];
if(![kSettings objectforKey:#"isFirstRun"]){
// You could check the version here
// and do some initial setting up.
[kSettings setBool:NO forKey:#"isFirstRun"];
}
Then, for each subsequent version, you can add another if block with another flag to check for that version, like so:
if(![kSettings objectForKey:#"isFirstRunForVersionX"]){
// Do some version specific set up here.
[kSettings setBool:NO forKey:#"isFirstRunForVersionX"];
}
I've successfully used this approach in several of my apps.
To determine it it's the first launch after the update, you could retrieve the version number with this piece of code:
NSString* v = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"];
And compare it to a previous value you have saved.
You can store flag in NSUSerDefault. So You need to check first time if isFirstTime==0 then do your code and make isFirstTime=1; So it only runs first time.

How to check if not available methods are used if deployment target < base sdk?

I would like to know how you check that your code do not call not available methods when the deployment target is inferior to base SDK ?
It is possible to run the application on a device with the SDK equal to deployment target, but I search a way more 'automatic'. Any idea ?
Regards,
Quentin
The easiest way to do this is to use the __IPHONE_OS_VERSION_MAX_ALLOWED preprocessor define.
You do this by adding
__IPHONE_OS_VERSION_MAX_ALLOWED=__IPHONE_4_2
or something similar to your "Preprocessor Macros" option in Build Settings of your target. You can look up versions available in <Availability.h>.
Unfortunately if you add this define it will cause mismatch errors with your precompiled header. So, to fix that you need to turn off the "Precompile Prefix Header" option in your build settings as well.
Once you do this you'll get a bunch of errors for classes that don't exist on your targeted SDK (for instance NSOrderedSet doesn't exist in iOS 4.2). If you're trying to go back pre-iOS 4 you'll probably get so many errors that the compiler bails--I don't know of a workaround for this. In any case, ignore the errors about missing classes in the UIKit headers, and go to the bottom of the error list; there you should find an error for each time you use a method or class that isn't included in the SDK pointed to by __IPHONE_OS_VERSION_MAX_ALLOWED. Make sure each of these methods is enclosed in an
if( [targetObject respondsToSelector:#selector(thePossiblyMissingSelector:)]
and you should be safe. Classes that may be missing should be tested as well
if ([NSOrderedSet class] != nil)
These settings aren't something you want to accidentally forget to flip back however. To make this an automatic option for testing, do the following:
Create a new build configuration called something like "Old SDK Testing".
Define __IPHONE_OS_VERSION_MAX_ALLOWED and the precompiled head option only for this configuration (hit the disclosure arrow beside each line in Build Settings to access per configuration settings).
Duplicate your current Scheme and set its name to something like "Old SDK Check".
Set the Build Configuration of the Run item in this new scheme to the build configuration you created in step 1.
Select the new Scheme and build.
Notes:
I make no guarantee that this will catch any/all of your issues.
Anything outside of UIKit will not be caught by this check.
This is not a substitute for testing your code on the versions of iOS you
plan to support.
use NSClassFromString();
Class cls = NSClassFromString(#"YourClass");
if (cls == nil)
is this you are looking for?
best way to do that which i found: compile code with old SDK :) link which can help
I think this question is releated with next
I belive that someday Apple allow to compile project for old SDK by simple defining #define __IPHONE_OS_VERSION_MAX_ALLOWED __IPHONE_3_0
upd: I found solution here
4.3 5.0 and 5.1 SDK just fail to compile after trying to redefine this macro
Are you looking for something like
- (BOOL)respondsToSelector:(SEL)aSelector
If you have an instance of a class, you can use the following to see if it understands the method you want to call:
if ([mipmapBrowserView respondsToSelector:#selector(setBackgroundColor:)]) {
// set the background layer since IKImageView supports it
}
Here, mipmapBrowserView is an instance of IKImageView, which was first introduced in Mac OS X 10.5. The setBackgroundColor: method of IKImageView was only added in 10.6, however, so I need to check before I call it. This allows me to build against the 10.6 SDK, and take advantage of the new features, yet still support OS X 10.5 as well. While this example involves OS X rather than iOS, the same method (pun intended?) works in iOS as well.
Note that things are slightly different when you are subclassing a class, and you want to know whether the superclass responds to a certain selector:
"You cannot test whether an object inherits a method from its superclass by sending respondsToSelector: to the object using the super keyword. This method will still be testing the object as a whole, not just the superclass’s implementation. Therefore, sending respondsToSelector: to super is equivalent to sending it to self. Instead, you must invoke the NSObject class method instancesRespondToSelector: directly on the object’s superclass...."

Graceful degradation on iPhone

How do I write a program for iPhone (Objective C++) that runs on OS 2.0 but takes advantage of 3.0 features if they're available?
Example: copy&paste (class UIPasteboard). Nice feature to have, but I don't want to kill backward compatibility. Do I compile against SDK v. 3 or v. 2? If the latter, how do I create an instance of UIPasteboard, considering it's not declared in the headers? If the former, won't some C-linkage functions cause "unresolved reference" upon loading under OS 2.0?
Edit your Target's build settings like this:
Set the Base SDK to the version whose APIs you want to use (e.g. 3.0 or 3.1).
Set the Deployment Target to the lowest OS version you want to support (e.g. 2.2.1).
When building, you compile against the Base SDK. For all symbols that defined in your Base SDK but not available in your Deployment Target, you need to add runtime checks to your code to check for their availability. Examples:
Check if UIPasteboard is available:
Class PasteboardClass = NSClassFromString(#"UIPasteboard");
if (PasteboardClass != nil) {
// UIPasteboard is available
}
Check if a specific method is available:
if ([UITableViewCell respondsToSelector:#selector(initWithStyle:reuseIdentifier:)]) {
// ...
}
I didn't try this but i would recommend building against the most recent (3.x) SDK release. So you get any class and method definitions that might be available on the target device.
And in your application you have to check the OS release your application runs on. Based on the target OS you have to decide which Class and Method you should use. After all it is a big mess of conditional code, probably with a lot of additional code to provide missing functionality (i.e. direct access to SQLite instead of using Core-Data). In my experience that should not lead to problems, because most type information is erased at runtime.

Creating a localized iPhone app but allowing the user to change language for the application

I'm working on a localized app and everything is working fine. The problem is I want to allow the user to specifically select the lenguage for the specific app, in the app settings folder. This should users that their phone is set to one language (e.g. french) to set the app to work in English.
I'm currently using NSLocalizedString to get localized string but looking through all variation of the macro I can't find one that will let me specify the language.
Any ideas on how to do it?
There are three issues here:
Strings
Other resources (including NIBs)
System messages
The last is almost certainly not fixable, so we will leave it be. They're going to show up in the device language.
The other two are solvable, but you will need to do more things by hand. For strings, instead of creating a single Localizable.strings and then localizing it, create completely separate tables (English.strings, French.strings, etc.) Then, use NSLocalizedStringFromTable(), passing the language as the table.
For NIBs, there are two approaches. You can put each set of localized NIBs into its own Bundle and then pass that Bundle rather than nil to -initWithNibName:bundle:. Alternately, you can hand-load the NIBs after finding them with [NSBundle -pathForResource:ofType:inDirectory:forLocalization:].
There is a better way to do this. You can force the language like so:
[[NSUserDefaults standardUserDefaults] setObject: [NSArray arrayWithObjects:#"en", nil] forKey:#"AppleLanguages"];
And undo this setting by:
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"AppleLanguages"];
NB. you will normally have to restart the app for this to take affect.
Consider if you need to call [[NSUserDefaults standardUserDefaults] synchronize];
I agree there is little need to allow the user to specify a language. However the one exception is being able to override the language and set it to the developer's native language. If the user can speak the developer's language (e.g. English for me) then they may wish to use the App in that language, if the translations are incorrect.
I reference this answer: How to force NSLocalizedString to use a specific language (the answer doesn't actually work for me, but following the ideas in the comments did. The undo stuff I worked out.
The trick to use specific language by selecting it from the app is to force the NSLocalizedString to use specific bundle depending on the selected language ,
here is the post i have written for this http://learning-ios.blogspot.com/2011/04/advance-localization-in-ios-apps.html
and here is the code of one sample app https://github.com/object2dot0/Advance-Localization-in-ios-apps
The correct "User experience" is for the user to select their language via the system preference panel; not your app (or your app's settings panel, etc.). There is no way to override this per-app and we wouldn't want any app changing the system wide setting.

String value in Settings.bundle empty after update

For QA purposes I display our app's build version in the application' settings view as a PSTitleValueSpecifier. I set our app's current build number as the DefaultValue and I update it for every new build.
My problem is, whenever I deploy a new build of our application on a dev phone via Xcode, the version value doesn't update but remains with the previous build. The only way to update the version's value is to delete the app from the dev phone and then deploy the new build. Obviously, this approach does not please our QA department since they then loose all of their persisted data.
I have tried, cleaning the project, re-compiling and deploying but I get the same behavior.
Has anyone else experienced this issue? Any idea on how to work around it/fix it?
It is because this value is stored using NSUserDefaults which is stored on the disk and only removed when the app is removed. You can explicitly set it in code the same way you would any other user default. You could just create a field in your Info.plist that you change when you want to send a new build. Read that value from the plist on startup and then write it to your user defaults with:
NSString *value = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"build_version"];
[[NSUserDefaults standardDefaults] setObject:value forKey#"build_version"];
[[NSUserDefaults standardDefaults] synchronize];
Of course, you replace "build_version" with whatever your key name is in your settings file. It's a hack, but it might satisfy your QA people.
For argument sake, it is actually reasonable to expect that you have to completely remove the previous version before seeing the version number update, however I realize not all QA people are reasonable. ;-)