2 iOS Apps, 1 Codebase with XCode Scheme

Imagine that you’ve been developing an app for some time and then you realize that you want to monetize the app by creating a pro version or you’ve begun developing an app that has the same codebase but you want to differentiate the app and its features between admin users and members users. Copying and pasting your codebase between the pro & lite versions will be a waste of your time because, in the end, you will need to modify each codebase and then you will end up with two codebases that will need to be maintained. The solution is to use XCode Scheme, an XCode feature that’s lesser modified but that can help us in this case by maintaining only one codebase for multiple apps. In this tutorial, we’re going to create 2 apps: a pro and a lite app version using XCode Scheme.

Project Configuration

To create a new scheme we will first need to duplicate the Project configuration. Open your app settings, select your project and duplicate Debug and Release to Debug Pro and Release Pro. We need Debug and Release configurations for every scheme, debug will be used for running, debugging, testing and analyzing, while release will be used for profiling and archiving.

Build Settings

Next, while still inside the app settings, select your app target and choose the Build Settings tab. Press + and choose Add User-Defined Setting.

Name a new setting BUNDLE_ID_SUFFIX. Fill the value for Debug Pro and Release Pro with .pro. We will append this setting into the existing Product Bundle Identifier to create two apps, one for regular/lite and one for pro.

Add another User-Defined Setting and name it BUNDLE_DISPLAY_NAME. Fill the value for Debug and Release with your app name and fill the values for Debug Pro and Release pro with your app name pro or whatever name that you want to use. We will use this setting for our app name.

Now scroll to the top, find Product Bundle Identifier and change its value to <your_app_id>$(BUNDLE_ID_SUFFIX). Press Enter and your product bundle identifiers should change into your_app_id appended with the BUNDLE_ID_SUFFIX that we defined above.

Below Product Bundle Identifier you’ll find Product Name, double-click in the value and change it to $(BUNDLE_DISPLAY_NAME). All product names will change to the BUNDLE_DISPLAY_NAME that we defined above.

Next, we want a different icon for the Regular version than the Pro version. To achieve this, select Asset Catalog App Icon Set Name and change its value to AppIcon$(BUNDLE_ID_SUFFIX), when you press Enter then  your app icon value for each configuration will be changed into AppIcon and AppIcon.pro

Manage Schemes

Next, click on the active scheme beside Stop Button and choose Manage Schemes.

Select the default scheme, click on the gear button at the bottom and choose Duplicate.

Name the new scheme with <app_name> Pro for clarity’s sake or any name that you choose. Inside Run, Test and Analyze, change the build configuration to Debug Pro. While inside Profile and Analyze, change the build configuration to Release Pro. Check the Shared checkbox at the bottom of the dialog. The final scheme configuration should look like this:

Now you should see 2 schemes when clicking on an active scheme button.

To differentiate the between app icons, open Assets.xcassets and add a new iconset named AppIcon.pro . After you have added the Pro iconset, the xcassets icons should look like this:

Getting User-Defined Settings in Code

For the next step, if you’re using Cocoapods as dependency manager, run pod install to build & integrate the new schemes with the Cocoapods libraries. Now, inside our app, we need a way to distinguish features between the Regular and Pro versions. One way to do this is using our app bundle identifier and using enum class with our bundle identifier as its raw value.

enum AppType: String {
    case pro = "com.projectbox.bookinglayout.pro"
    case lite = "com.projectbox.bookinglayout"
}

Next, create a global variable and initialized it inside didFinishLaunchingWithOptions in AppDelegate.swift

var appType: AppType = .lite

class AppDelegate: UIResponder, UIApplicationDelegate {
    ...
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {

        if let productId = Bundle.main.bundleIdentifier { 
            appType = AppType(rawValue: productId.lowercased()) ?? .lite 
        }
        return true
    }
}

Now, whenever you run your app using the pro scheme, it also creates new app inside your device/simulator.

To archive this app in XCode, select the scheme that you want to archive and use Product → Archive. If you’re using Fastlane Gym to archive your app, use this configuration:
gym(scheme: "MyApp Pro", output_name: "MyApp Pro", export_xcargs: "-allowProvisioningUpdates")

You can clone this XCode workspace to learn how to use XCode scheme.