The beginning of any new or existing iOS application development cycle should start off on the right foot. A solid foundation to support each environment level within the configuration of the XCode project is a great first step to the success of your application. This will allow the development team, continuous integration (CI) services, and QA to easily test/create builds for a particular environment.
- A typical application environment hierarchy may look like:
- Debug – Used by the development team
- Development – Lastest approved development source
- QA / UAT – Latest development source from the previous sprint
- Stable / Production – Tested and approved source for deployment
Many different aspects of the application can change between environments such as:
- API host
- Provisioning Profiles
- Firebase Project
- Network debugging tools
- Development debugging tools
- Project Bundle Ids
- Application versions
- URL Types (Deep / Universal Links, etc)
- Application Features
- Other third-party SDK integrations
Ideally, we would like to specify how each of these aspects of the application can change depending on the selected environment. This post is designed to show you how to set up your iOS application to do this. Let’s get started!
Part 0: Let’s Get Started
For the purposes of this post, we will be starting with a blank XCode (10.1) iOS Single View Application. We will be using Swift for this example. Everything we do here can also be tailored for an existing application.
- Start by opening XCode, and creating a new Xcode project. Use a Single View App as a template.
2. Give the project a Product Name, Team, Organization Name, Organization Identifier, and set the language to Swift.
3. Click Next and save the project in the location of your choice.
Part 1: Configurations
The first part that needs to be taken care of is the creation of different configurations in XCode for each of the environments we want to support. A single environment typically consists of a debug and release configuration depending on the apps needs. To create those configurations we will need to start by defining each configuration.
- Open the project settings by selecting the project blueprint from the Project Navigator, and then click the blueprint under the Project section.
2. Find the section for Configurations. Using the environments we defined in the intro, we will create these configurations:
- Debug – 1 Debug configuration
- Dev – 1 Debug configuration, 1 Release configuration
- QA – 1 Debug configuration, 1 Release configuration
- Stable – 1 Debug configuration, 1 Release configuration
The reason for creating 1 Debug and 1 Release configuration is:
- Debug – Used for running the environment locally for testing by the development team if needed.
- Release – This is the configuration that will archive the application to be built locally or by CI.
The single Debug configuration for local development is set up in order to isolate the configuration that should be used by the development team for bug fixing or new features.
3. Start by renaming the existing Debug and Release configurations to “Dev Debug” and “Dev Release”.
4. Next, create the local debug development configuration. Using the ‘+’ action under configurations. Choose to duplicate ‘Dev Debug’ and rename the configuration to Debug.
5. Now create the configurations for QA and Stable. Duplicate the “Dev Debug” and “Dev Release” configurations and rename them accordingly: “QA Debug”, “QA Release”, “Stable Debug”, “Stable Release”. (Make sure to duplicate the corresponding release and debug configuration. This will preserve the default configuration values for Debug and Release configurations)
Part 2: Schemes
The configurations that we just created now need to be associated with an XCode scheme. For more information on what a scheme is, check out the apple documentation reference here. To do so, we need to manage the schemes of the project.
- Open the scheme manager. Select Product -> Scheme -> Manage Schemes… from the toolbar.
2. By default, only one scheme will show in the list and it was associated with the Debug and Release configurations. We will change this scheme to be the Debug development scheme. Select it from the list and click Edit.
3. For each of the options on the left-hand side, change the Build Configuration to Debug. This will associate all execution types for the Debug scheme to use the Debug configuration. Click ”Manage Schemes” to return to the list of schemes.
4. Next, create a new scheme using the ‘+’ action and name it Dev PROJECT_NAME, where PROJECT_NAME is the name of your project. Click ‘OK’ to create the new scheme.
5. Make sure the scheme is marked as shared in the list of schemes. This will allow other developers to use the scheme and CI will have access to build the scheme.
6. Edit the “Dev PROJECT_NAME” scheme.
7. (Optional) if your project includes Unit and UI Test, add those targets to the list of build targets and select only the test checkbox for each. To add a target, use the ‘+’ action, and select the Unit test and UI test targets. This will allow the scheme to run those test.
8. For each of the build types in the left side panel, change the configuration to the corresponding Debug or Release configuration for that scheme.
9. Repeat steps 4 – 8 for QA and Stable environment
10. Close the scheme manager. Each of the schemes should now be listed under the scheme selector.
At this point, the project is set up with configurations and schemes for each environment. The scheme can easily be changed to set the current environment the project can be run with. Because the schemes are shared, a CI can use these schemes to create a build for that particular environment. The build can also be created locally using the archive utility.
Part 3: Build Settings
The project can easily change environments but we need to actually set up which aspects of the application will change for each. Many of those aspects are defined in build settings and we can see how to change values between environments using the configurations we created for the projects. In particular, let’s look at the build settings for the app target.
- Open the project settings by selecting the project blueprint from the Project Navigator
- Select the Target to configure under the Targets section.
- Select the Build Settings Tab.
4. Notice that certain properties in the list of Build Settings can be expanded to show the value for each configuration. This will be used to set the value we want for each.
5. Scroll down or use the search bar to find the property Product Bundle Identifier.
6. Expand the property to view the value for each configuration.
7. Set the Product Bundle Identifier value for each environment. This will allow a unique build of each environment to be installed to a single device at a time. Which improves the testing capabilities for a QA team.
8. Review the rest of the Build Settings to identify other parts of the application target that the application needs to change based on the environment. Some other settings to look at are:
- Info.plist File
- Identifies a particular plist file for the environment to build with
- Product Name
- Sets the name of the product for QA to more easily identify the environment it was built for.
- Code signing
- Change to provisioning profile associated with a build environment. This can be more easily done in the General Tab of the Target settings.
- Active Compilation Conditions – See next Part
- Custom User Properties
- Define custom variables that, such as a bundle version, that can be used within the Info.plist file to change the version based on the environment. The possibilities here are endless.
Part 4: Active Compilation Conditions
Sometimes the application will need to tailor the source code based on the current environment that is being built. For example, if the application wants to include network debugging or other debugging tools for all debug builds. Another example would be removing logs or other unnecessary code for production builds. To do this, the application will need to know what environment is being built from within the source code.
- Open the Build Setting for the application target (See Part 3, Steps 1 – 3)
- Scroll down or search for Active Compilation Conditions
- Expand the property to see the values for each environment configuration
4. The DEBUG flag is defined by default, but let’s add some additional flags to identify our other environments and the production environment.
5. Double click on the value of the configuration to expand the editor
6. Use the ‘+’ to add a new value. For the Dev Debug configuration, add DEV.
7. Continue to add additional flags as needed to fit the applications needs. For this tutorial, we will use these flags.
8. Now that the Active Complication Conditions have been configured, we can use these within our code to change which parts are executed based on the current environment.
9. Open the AppDelegate.swift file of the application
10. Add this section of code to didFinishLaunchingWithOptions to see how active compilations conditions can change which parts of the code are executed based on the currently selected scheme.
11. Now, set the Scheme you would like to run and see the appropriate console log for that scheme.
These Active compilation conditions can be used in various ways to change the application for the selected environment, such as:
- API Host definitions
- Activating debugging tools
- Auto-filling form data for testing
- Bypassing certain screens
- Selecting different configuration files
The application is now set up with a solid foundation to tailor any aspect of it for each environment. In review, for each environment we: set up Debug and Release configurations, associated those configurations with a scheme, set up specific build settings values for each configuration, and set Active Compilation Flag that can be used within our source code.