When developing a software product, you'll end up with multiple environments, for your local development environment, one or many development/staging environments, and a production environment (or multiple depending on your architecture). The same applies to mobile applications.
Whether it's for push notifications that use a sandbox when developing, or simply using different backends for each environment (you might want to develop against your local backend but run the production app against the live environment), chances are, that you want to develop and distribute multiple variations of your app, without too much overhead.
Using Xcode schemes, you can do just that.
Creating an Xcode Scheme
Next to the device selection, the current scheme is displayed. If you haven't set up custom schemes, yet, the default scheme (usually the name of your project) will be shown.
You can click on the current scheme, and then select Manage Schemes
to open the following overlay
In here, you can see a list of your project's schemes, as well as options on whether the scheme should be shown in Xcode, and whether it should be added to your project files to be checked into version control and shared with your teammates.
Clicking the gear at the bottom of the modal will display useful options for Importing/Exporting schemes, as well as duplicating an existing scheme, which we'll make use of to create our staging scheme, which will point to a staging environment.
For the duplicated scheme, you can simply enter a name, confirm and close the settings.
Environment-specific configurations in Xcode
For common settings like the bundle identifier, app icon, or other build and release settings, Xcode offers schema-based configuration out of the box.
Going into the Build Settings
, you can expand any option to set values specific to each environment. This allows you to set custom names, app icons, bundle identifiers, and other environment-specific settings for each scheme, enabling you to distribute your app's variations in a couple of clicks.
If you need custom configuration values (e.g. your application requires a backend URL that depends on the environment), you can make use of the configuration files we're about to create.
Creating environment configuration files
We'll create two new config files, using the Configuration Settings File
type. These files will contain all environment-specific variables like backend URLs or associated domains if you're implementing universal links.
Using your configuration files
Opening your Xcode project settings, you'll see a Configurations
tab.
Clicking on the +
sign will allow you to duplicate the existing debug and release configurations, adding another pair for our staging environment.
Once that's done, you can assign the configuration files by selecting the respective configuration file for each configuration (you shouldn't have None
on the right-hand side after that.
Your final assignment could look something like the following:
Now we just need to make sure each scheme uses the correct configuration.
Going back to Manage Schemes
and selecting our staging scheme, we need to update the Build Configuration
to Debug/Release Staging
for every action on the left. This will make sure your builds, releases, and runs use the matching configuration values.
Adding custom environment configuration
Now, let's get to the fun part: Adding values to your configuration files. Let's go with the example we introduced earlier, and your backend requires a URL that is different for each environment or Xcode scheme in our case.
The first step is to add our new variable BACKEND_URL
to the configuration files, including the respective values.
Note that URLs with protocols have to use a syntax that might look a little weird, but is required in configuration files (https:/$()/...
).
Now, somehow, we need to access this from within our application. While you could use environment variables, a more straightforward way is to go to your Info.plist
file and add
Once that's done, we can edit our ContentView and render the current backend URL to try out if everything works as expected:
import SwiftUI
let backendUrl = Bundle.main.infoDictionary!["BACKEND_URL"] as? String
struct ContentView: View {
var body: some View {
Text(backendUrl != nil ? backendUrl! : "No URL found")
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Note that this is an extremely simplified way to access the variable, and you should probably take measures to react accordingly if a value is missing, or process it (e.g. parsing URLs and making all variables available throughout your app).
And that's it! Switching the current Xcode scheme to staging and rebuilding the preview automatically updates to https://staging.demo.backend
instead.