58

My iPhone application connects to three different servers, say: production, staging and testing. There is a bunch of configuration values that the application uses depending on to which server it connects to, e.g. Facebook App ID, TestFlight team key, etc.

I'd like to have all the settings in GIT and only select which configuration the application supposed to use when compiling or releasing. For example, when testing is selected, Product -> Run in Xcode runs the debug version of the app connecting to testing, and Product -> Archive creates the IPA file with the release version that also connects to testing.

I don't want to create more build configurations than debug and release (because that would mean 6 different combinations of build configurations/run-time configurations). The ideal solution, as I see it, would be that I have three schemes: production, testing and staging, and each scheme selects one of three Info.plist files to use with the application. That would allow me to not only define different run-time settings, but also different application versions or bundle identifiers depending on the back-end server. But it doesn't look like I can configure the Archive action in any other way apart from selecting a different build configuration. Any ideas if that could be achieved in any way?

Edit: To make it a bit more clear, production/staging/testing is the back-end server, not the version of the iOS application. The iOS app comes in two versions: debug/release. In other words I may want to run a debug version of the application connecting to the production server for example to debug a crash caused by JSON returned from that server. I could have named the servers as A, B and C for the sake of clarity.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Greg
  • 8,230
  • 5
  • 38
  • 53
  • Hello @Amiramix, I know this question is asked long time ago but I would appreciate if you can help me. I have the same issue. I need different build environment. And it should have different bundle identifier in order to have separate app for each environment. I just want to change some URL for each environment. How did you solve your problem? – Hamid Oct 16 '14 at 15:56
  • Sorry, I won't be able to help you as I haven't been using the newer version of XCode for a while. From what I remembered I didn't solve this particular problem, I just created a few different applications sharing the same code but with different configuration connecting to different backend servers. – Greg Oct 17 '14 at 14:06

4 Answers4

112

A good way to do this would be with build configurations and C macros. This avoids having to create a separate target for every configuration which is not really the correct use of targets.

First you want to set up the configurations at the project level:

enter image description here

You can create different configurations for debugging, enterprise distribution, and any other type of special build you want.

Next you can define some macro flags for each configuration which will be passed to the compiler. You can then check for these flags at compile time. Find the "Preprocessor flags" build setting at the target level:

enter image description here

If you expand the triangle you can define different values for each of your configurations. You can define KEY=VALUE or just KEY macros here.

enter image description here

In your code, you can check for the existance of these macros, or their value (if there is one). For example:

#ifdef DISABLE_FEATURE_X
    featureXButton.hidden = YES;
#endif

// ...

#if FOOBAR_VISIBLE == 0
    foobarView.hidden = YES;
#elif FOOBAR_VISIBLE == 1
    foorbarView.hidden = NO;
#else
    #error Invalid value for FOOBAR_VISIBLE
#endif

You can pass in string values as well, which must be wrapped with single quotes in the build setting, e.g. DEFAULT_LOCALIZATION_NAME='@"en"'.

You can also configure which configuration is used during Debug and Archive time using the Schemes editor. If you choose "Run" or "Archive" in the Schemes editor you can select the appropriate configuration.

enter image description here

If you need to parameterize entries in the Info.plist file, you can define their value using a custom build setting. Add a custom build setting for your target:

enter image description here

And then give it an appropriate value for your different configurations:

enter image description here

Then in the Info.plist file you can reference this setting:

enter image description here

Note that the one limitation of this approach is that you cannot change the following items:

  • Settings.bundle

Additionally, in older versions of Xcode without asset catalog support, you cannot change the following items:

  • Icon.png
  • Default.png

These cannot be explicitly defined in the Info.plist file or anywhere else, which means you need different targets to change them.

starball
  • 20,030
  • 7
  • 43
  • 238
Mike Weller
  • 45,401
  • 15
  • 131
  • 151
  • 1
    That's not a good solution, because as I said, I would need to create 6 different build configurations: production debug, production release, staging debug, staging release, testing debug, testing release. Then maintaining all the build configuration switches becomes a nightmare. I also don't buy your argument that adding new targets is wrong. In this case you propose to create new build configurations to select appropriate runtime settings. Build configurations are for the build phase, not runtime phase. Why should it be a better solution then? – Greg May 08 '12 at 12:34
  • 6
    Build configurations were designed with exactly this use case in mind. You have different configurations of your app, and you can choose to change specific settings for specific configurations. I don't see how else you want to achieve this. And adding multiple targets is far more complex. You end up with duplicated configuration settings and Info.plist values. The six examples you gave are also confused. "Production Debug" is a bit of a contradiction. – Mike Weller May 08 '12 at 12:39
  • 1
    Production Debug means a debug version of the application connecting to production server. Build configurations are to be used for creating separate build settings, e.g. debug vs release. Not to build functionally the same application connecting to different back-end servers. Creating separate targets makes more sense, e.g. app connecting to production as a different target than app connecting to staging with possibly different bundle identifier and version (to tell these apps apart). – Greg May 08 '12 at 12:46
  • Hmm. In that case I must have misunderstood your requirements. If your three different versions are distinct and can run alongside one another on the same device, they should probably be different targets. The best way to configure them would be with a simple plist file. Sorry for the confusion. If you set up multiple targets, you just choose the appropriate one from the Scheme drop down. – Mike Weller May 08 '12 at 12:54
  • I don't have the requirement to run the different versions on the same device, however I did mention that "I don't want to create more build configurations than debug and release". Multiple build configurations, as you suggested, is what we have in our project currently and it doesn't work when it comes to switching between different back-ends. Many thanks for your effort and I'm sorry if my requirements were not clear enough. – Greg May 08 '12 at 13:05
  • 1
    Being able to change the default.png and settings (app name, appID) is key for something like a staging build. To avoid confusion you really need a distinct difference in the name of the app, as well as the icon... for those reasons changing the target is a better idea. – Kendall Helmstetter Gelner Feb 22 '13 at 18:21
  • 1
    This debate is long dead, but I think Mike is correct here. If you're connecting to different servers via an API (I can't think of any other way to do it), then all you need to do is specify a different URL for each config. As for having some visual indicator that you're in a prod-debug build, I'd argue that you need a permanent reminder on screen (e.g. A red status bar) and that only changing the default.png isn't enough. – gargantuan Aug 21 '14 at 16:36
  • 4
    You *always could* change `CFBundleIdentifier` and `CFBundleDisplayName` per configuration using this approach. Now, with asset catalogs, you can change the app icon and loading image per config, too. There's no reason to use separate targets anymore. OP's restriction on having `Debug` and `Release` is arbitrary and precludes using the best solution to solve the problem. Avoiding additional configurations just moves the complexity someplace else; a place where you have to duplicate settings and repeat yourself much more than with what @mike-weller proposes. –  Jan 23 '15 at 05:06
  • 3
    Can this be done in Swift? There's no such thing as Apple LLVM Compiler > Preprocessor Macros under Targets, only at Project level – Christopher Francisco May 20 '15 at 16:12
  • In combination with Configurations you can also use `xcconfig` files to share settings across Configurations :) – Sajjon Dec 05 '16 at 16:54
10

I would suggest using different build targets for each environment. I successfully used this model before. In your project's settings you can duplicate the current target and change the build settings as needed. There's an Info.plist File property that will let you change the default plist for that target.

After that, you can create a scheme for each environment that will use the according target.

You can get a step further and use different bundle id for each target and different names. That will allow you to install both the staging and the production builds on the same device for example.

The only downside in this is that you have more work when you want to update provisioning profiles.

adig
  • 4,009
  • 1
  • 23
  • 23
  • 21
    Targets should only really be used for distinct products. If you are building the same conceptual product with different configurations or settings, using targets isn't really the best solution. – Mike Weller May 08 '12 at 11:48
  • 3
    @MikeWeller I beg to differ. As I said, I used this technique before without any problems. And you obviously didn't mention any downsides or an real alternative. What's the point of your comment ? – adig May 08 '12 at 11:54
  • 2
    Targets are a better idea to separate testing builds because you can have different AppID's, and thus allow them to run alongside production builds. – Kendall Helmstetter Gelner Feb 22 '13 at 18:23
  • 2
    If you parameterize `CFBundleIdentifier` you *can* have each different configuration of your app installed on a device simultaneously. In fact, @mike-weller showed exactly that in his answer. You can have different display names. With newer Xcode using an asset catalog you can have a different app icon. Everything you could accomplish with multiple targets you can now do with a single target and keep things more DRY. –  Jan 23 '15 at 04:47
  • Check out this great tutorial on how to do exactly what @adig is talking about. http://www.appcoda.com/using-xcode-targets/ – Atticus Jun 02 '16 at 13:28
2

Here's a much easier solution if the concerned libs allow to set the keys in code, meaning that you can have production value in your plist file, but change them in your AppDelegate (or whichever file they are first used in).

Works with facebook, twitter and google sdk at the moment.

Ex:

#ifdef DEBUG
  // Facebook
  [FBSettings setDefaultAppID:@"SandboxID"];
  // Fabric / TwitterKit - must be called above [Fabric with:@[TwitterKit]];
  [[Twitter sharedInstance] startWithConsumerKey:@"SandboxKey" consumerSecret:@"SandboxIDSecret"];
#endif

Same in Swift, just use #if instead of #ifdef.

Note about Facebook This worked with version 3 of their SDK, I'm not sure it's possible with later versions.

Arnaud
  • 17,268
  • 9
  • 65
  • 83
  • This answer is wrong, because you need to add a Url Scheme for the Facebook SDK login and redirection, and you can only add it threw the info.plist. Therefor it cannot be done threw the libs. – 6rod9 Oct 01 '15 at 21:23
  • @6rod9 Did you try it? See the [documentation](https://developers.facebook.com/docs/reference/ios/3.5/constants/FBSettings/): defaultAppId "will be read from the application's plist if not explicitly set". I assume it also sets the Url Scheme by adding "fb" in front of it if you set it manually. – Arnaud Oct 02 '15 at 08:35
0

It is probably very low tech but I just have a method called apiURL() that returns the URL of the API I want. I have localhost, stage, and production and I simply uncomment the one I want. It's worked well for me so far. I've only forgotten to switch it back a few times. Oops.

Dan
  • 1,238
  • 2
  • 17
  • 32