Simplified Project Configuration Using Multi-Platform Frameworks in Xcode 13

13 January 2022

With the advent of XCFrameworks in 2019, Apple made it simple to deliver a single framework package that catered for multiple platforms. However, producing a multi-platform framework still involved managing different per-platform configurations in the framework's Xcode project. This duplication of configuration could become repetitive and error prone, leading to potentially misconfigured framework versions. Now, thanks to Multi-Platform Frameworks in Xcode 13, this duplication problem has been resolved once and for all.

Converting a Multi-Target Framework Xcode Project to a Single Multi-Platform Framework

To demonstrate how to convert an Xcode project with multiple targets to a single "multi-platform framework" target project, we will rework the Hopoate framework's Xcode project step-by-step, until it contains just one single framework target. Hopoate supports iOS, watchOS, and tvOS, and currently has a separate framework target for each platform:

The Hopoate framework project, configured with one target per supported platform.
The Hopoate framework project, configured with one target per supported platform.

We will begin the process of transforming the project to a multi-platform framework by editing the build settings of the existing "Hopoate iOS" target.

Enable Multi-Platform Builds

The first step we need to take is to set the ALLOW_TARGET_​PLATFORM_SPECIALIZATION build setting to YES:

Setting Allow Multi-Platform Builds to YES.
Setting Allow Multi-Platform Builds to YES.

This allows the Xcode build process to build the target for multiple platforms.

Configure the Supported Platforms

Currently, the target is only configured to support iOS. We need to expand this to include watchOS and tvOS:

Adding watchOS and tvOS to the list of supported platforms.
Adding watchOS and tvOS to the list of supported platforms.

We must make sure to include both the hardware and simulator versions of the supported platforms. Having done this, we are thwarted in our attempts to build the framework for one of these new platform's simulators by Xcode's destination menu:

The Xcode destination menu not showing Apple Watch or Apple TV destinations.
The Xcode destination menu not showing Apple Watch or Apple TV simulator destinations.

This occurs because the target's "Targeted Device Families" setting only contains iPhone and iPad:

The targeted device families setting, containing only iPhone and iPad.
The targeted device families setting, containing only iPhone and iPad.

We need to update this to include Apple Watch and Apple TV:

The targeted device families setting, updated to contain Apple Watch and Apple TV.
The targeted device families setting, updated to contain Apple Watch and Apple TV.
Now we can build for Apple Watch and Apple TV simulators:
The Xcode destination menu showing Apple Watch or Apple TV destinations.
The Xcode destination menu showing Apple Watch and Apple TV simulator destinations.

Add a Universal Framework Umbrella Header and Info.plist

With 3 separate framework targets comes 3 separate umbrella headers and Info.plist files:

The separate umbrella headers and Info.plist files used by the three distinct framework targets.
The separate umbrella headers and Info.plist files used by the three distinct framework targets.

We can replace these with a single umbrella header and Info.plist that works for all of the platforms supported by the framework. For Hopoate this is simple, as the only system framework it replies on is Foundation. However, for frameworks that provide user-interface elements, different framework imports will be needed based on the target platform e.g UIKit on iOS and WatchKit on watchOS. This can be catered for by importing TargetConditionals.h:

#import <TargetConditionals.h>

#if TARGET_OS_IOS
#import <UIKit/UIKit.h>
#elif TARGET_OS_WATCH
#import <WatchKit/WatchKit.h>
#endif

Once we have our unified umbrella header and Info.plist, make sure that the new umbrella header is included as a public header in the framework target, and that the Info.plist entry is updated with the location of the universal Info.plist file. Finally, remove the platform specific umbrella headers and Info.plist files. This nicely simplifies the project sidebar:

The project sidebar with the platform specific files removed.
The project sidebar with the platform specific files removed.

Rename the Framework Target

Our "iOS" target is now configured to build for all of our supported platforms, so we can rename the target to remove the "iOS" suffix:

The multi-platform framework target with the iOS suffix removed.
The multi-platform framework target with the iOS suffix removed.

Rename the Scheme

The build bar still shows "Hopoate iOS" as the target to be built:

The build bar still showing Hopoate iOS.
The build bar still showing Hopoate iOS.
This is because the scheme used to build the framework is still called "Hopoate iOS". We rename it to "Hopoate" to reflect the new target name.

Remove watchOS and tvOS Targets and Schemes

Now that we have a multi-platform framework target configured, we no longer need the watchOS and tvOS specific versions. As such, we can delete the targets and associated schemes from the project. Our target list now looks like this:

The Xcode project's target list, now showing a single framework target.
The Xcode project's target list, now showing a single framework target.

Check the Bundle ID

You may have used different bundle IDs for the different builds of your framework for each of the supported platforms, perhaps suffixing the framework's bundle ID with the platform name. Double check the value of the bundle ID to ensure that this is no-longer the case:

The bundle ID being updated to remove the platform specific suffix.
The bundle ID being updated to remove the platform specific suffix.

Conclusion

Updating your framework's Xcode project configuration to take advantage of multi-platform framework builds is a relatively straightforward process that reduces duplication and the possibility of misconfiguring your framework. For more information, watch WWDC21 session 10210 Explore advanced project configuration in Xcode.