When developing apps that connect to a server, mostly we have a staging server and a production server. As such we need two app versions, one the dev version that connects to the staging server and the other that connects to the prod. These apps could have different configs to load, especially if it's based on Firebase. Firebase provides one GoogleService-Info.plist file for each project. Below demonstrates how we will use this as a use case to demonstrate working with multiple app configurations, schemas and Xcode config file all using a single target.

  1. First, download the Firebase config for both dev and prod to Config folder under the project root and name them as GoogleService-Dev-Info.plist and GoogleService-Prod-Info.plist respectively.

Firebase config plist

  1. Under the Xcode configuration for the project, click on the Project > Info and under Configurations, we need to add one each for Dev Debug and Prod Release. For that, from Editor choose Add Configuration > Duplicate "Debug" Configuration and name it Dev Debug and follow the same for prod, but use Duplicate "Release" Configuration option.

Xcode config

  1. Now under the Build Settings tab, we will add an ENV flag. For that click the plus icon next to Levels in the header menu and choose Add User-Defined Settings with the following values.
Prod Debug PROD_DBG

User defined settings

Env vars

  1. Next click on the app name under the Target section and choose Build Phases and add a new Run Script Phase with the following content.
if [ "$ENV" == "DEV_DBG" ]; then
    echo "Dev debug config found"
    cp -r "${PROJECT_DIR}/Config/GoogleService-Dev-Info.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
    echo "Firebase staging plist copied."
elif [ "$ENV" == "PROD_RELEASE" ]; then
    echo "Prod release config found."
    cp -r "${PROJECT_DIR}/Config/GoogleService-Prod-Info.plist" "${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.app/GoogleService-Info.plist"
    echo "Firebase production plist copied."

Run script

This will copy the right plist and place it under the `.app` folder with plist renamed to `GoogleService-Info.plist`. This makes the call to `[FIRApp configure];` to work without having to manually load the plist and specify the credential info.
  1. Next, we will create multiple schemes, one for building the staging version and the other for production. For that, click on the target next to the Run/Stop, button and choose New Scheme. For the target, choose the target app and let's name the schemes as Dev Debug FBApp and Prod Release FBApp.

dev schema

  1. We need to set the right configuration for each scheme. For that click Edit scheme, and for the debug scheme, under the Run section, for Build Configuration, choose Dev Debug and for the production scheme, choose Prod Release. Under Manage schemes, make sure these schemes have Shared enabled.

Scheme config

With this, we have Firebase setup working with multiple environments. However, if we use other Firebase options like FirebaseAuth, we need to add additional settings like URL Type so that after the user sign-in, the invoking the app works. For this, we need to create a new xcconfig as described next. From File > New File > File, choose Configuration Settings File and name it as DevConfig.xcconfig and another with ProdConfig.xcconfig. Do not add the xcconfig file to any target as it's a build config file and we don't want it to be shipped with the app bundle. The content of the dev config follows.
APP_BUNDLE_ID = org.example.app.FBAppDev
APP_FIREBASE_URL_SCHEME = com.googleusercontent.apps.25135051042-2utrt0j007lp7vf23p73fd306trszqn2
#include "Pods/Target Support Files/Pods-FBApp/Pods-FBApp.dev debug.xcconfig"
Note that in case of URLs in the xcconfig, we need to escape it like APP_SERVER_URL_LOCAL = http:/$()/localhost:4000/.

Xcode Configuration Settings file

Here we define a variable APP_FIREBASE_URL_SCHEME with value taken from the reverse client ID of the dev plist. We will give a different bundle ID so that we can have both staging and prod versions of the app installed, without one overwriting the other.
  1. We need to specify the xcconfig file for each project configuration we created before (in step 2). Under the Project > Configurations, for Dev Debug, expand and choose the DevConfig for the Based on Configuration File value. Same for the prod configuration name.

Dev xcconfig

  1. If the project uses Cocoapods, first close the workspace, delete the xcworkspace file, the Pods directory and the Podfile.lock and run pod install which will install the dependencies as usual and generate the Pod xcconfig files for each configuration we have, but does not set it as we have already set a config. So we include the corresponding Pod config in the xcconfig using #include.
  1. For the final step, we need to parameterize the variables in the Info.plist file as follows.
Bundle identifier $(APP_BUNDLE_ID)
Bundle name $(APP_NAME)

We now have a project setup with different configurations with a single app target.