Xcode Deployment Configurations for iOS

Nicholas Solter
Posts from Emmerge
Published in
5 min readOct 12, 2015

Like all but the simplest mobile apps, the Emmerge iOS app that I’m currently writing communicates with a backend server. When it came time to share the first version of the app with my team, I wanted a quick way to build for staging or production. Which at first just meant swapping out the server URL. After a few days of manually replacing the server URL in the settings file every time I wanted to build for staging or production instead of my local development environment, I got fed up and turned to Google for help. I was surprised to discover, however, that there doesn’t seem to be a standard way to configure an Xcode project to build for different target environments, with per-environment settings. So, with the help of various stack overflow answers and blogs, I set up my own, fairly clean, solution, which I’d like to share in case others find it useful.

When do you need to worry about different deployment environments?

Any app that communicates with a back-end server (which is all but the simplest apps) needs at the very least to have a configurable server URL. If you use a third-party authentication system such as Facebook, Google, or the like, you’ll need different apps for those services in each environment, and therefore different app IDs configured for your iOS app. Even if your app doesn’t talk to your own backend server, you might want to collect analytics, and so might have a per-environment mixpanel ID, for example.

On to the details of how to set it up

For this simple example, we’ll configure a Development and a Release configuration. The goal is to have one configuration file (property list) for each environment in which you can put per-environment settings, but at runtime have just a “Settings.plist” from which you can fetch the settings.

You can follow along with the source code for a simple project that demonstrates this approach to configurations.

1. Create the Development and Release Configurations

In the Project Navigator, select the project name, and then select the Project in the dropdown and go to the Info tab. Make sure you select the Project in the dropdown, not one of the Targets.

Under “Configurations” click +. Select “Duplicate “Release” Configuration”. Name it “Production”.

Repeat, but duplicate “Debug” and call it “Development”.

Now you can delete Debug and Release.

(Note: if you’re using CocoaPods, see the note on CocoaPods below).

2. Create one property list file for each environment. I called mine “Settings-Development.plist” and “Settings-Production.plist”

3. Add whatever entries to you need to these files. Make sure you use the same Key for the same property in all of them.

4. In the Project Navigator, select the project name, and then select the Target this time (not the Project) in the dropdown.

In the Build Phases section, remove the new plist files from the Copy Bundle Resources section.

5. Add a Run Script Build Phase via Editor -> Add Build Phase -> Add Run Script Build Phase.

6. Copy this script into the new Run Script:

if [ “${CONFIGURATION}” == “Development” ]; thencp -r “${PROJECT_DIR}/Settings-Development.plist” “${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Settings.plist”elif [ “${CONFIGURATION}” == “Production” ]; thencp -r “${PROJECT_DIR}/Settings-Production.plist” “${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/Settings.plist”fi

7. Read the settings in any file at runtime with something like:

var serverUrl: String = ""if let filePath = NSBundle.mainBundle().pathForResource("Settings", ofType: "plist") {    let contentsOfFile = NSDictionary(contentsOfFile: filePath)    serverUrl = contentsOfFile?.objectForKey("ServerUrl") as! String} else {    // no settings!}

8. To select which configuration to run, go to Product -> Scheme -> Edit Scheme. Select “Run” on the left, and pick Development or Production on the right for Build Configuration.

9. You have to change the build configuration independently for the Test, Profile, Analyze, and Archive actions. Specifically, if you’ve run/tested for Production, make sure you change Archive to Production before trying to upload to TestFlight!

A note on CocoaPods

If you were already using CocoaPods in your project before you added the new Development and Production configurations, you might see an error like, “[!] CocoaPods did not set the base configuration of your project because your project already has a custom config set. In order for CocoaPods integration to work at all, please either set the base configurations of the target … in your build configuration.” Or, you might see a linker failure related to your pods.

To fix these issues, go to the Project settings, info tab (the same location as in step 1 above), set the “Based on Configuration File” to “None” for each new configuration, and run “pod install” again to force it to generate configuration files for the new configurations. You may need to repeat this every time you add a new configuration scheme.

Where to go from Here

With this framework, you can add whatever settings you want to your settings files and access the values for your target environment at runtime. You can obviously create more configurations if you need them. For example, many applications have one or more Staging and/or Test environments as well. You can also add different settings files — just remember to create a separate file for each configuration, and to add the new settings file to the Build Phases script to copy it over at build time (see Section 6 above).

Feedback

I’d love to hear your feedback on this approach, particularly any simplifications or alternative mechanisms!

--

--