Compiling iOS frameworks for distribution is not a straightforward task. Out of the box Apple offers no option for distributing compiled frameworks. Through Xcode user interface the option to archive a framework is disabled.
What about when you run Product > Build option?
Xcode builds the framework but only for either devices or simulators. But not both.
So how can you build an iOS framework that is compatible for both device and frameworks? The answer is XCFrameworks.
In this post we’ll first cover what XCFramework is. Then we’ll cover why use XCFrameworks over just frameworks. Finally we’ll learn how to build an artefact that supports simulators and devices iOS frameworks in a format called XCFramework.
Here is the outline of this post:
- What is XCFramework?
- Why use XCFramework?
- How to build a universal XCFramework from an iOS framework
For this post I assume you already familiar with creating, compiling and consuming iOS frameworks. Additionally you must be familiar with the basics of Swift and iOS app development.
I have used Swift 5.2 and Xcode 11.5 for this article.
What is XCFramework?
An XCFramework is an artefact that can contain a framework with different build variants.
Let’s say you have a framework that allows integrators to sign-up or sign-in their users using an account the user already own with your company. Let’s say the name of the framework is
SignInWithMyCompany. You want to offer integrators the possibility to sign in their users in their iOS and MacOS apps. You want to offer your company services without sharing your source code. Furthermore integrators will want to test your framework on iOS simulators.
With XCFramework you can include frameworks
SignInWithMyCompany for iOS devices, iOS simulators as well any other Apple platform such MacOS in a single artefact;
Why use XCFramework?
Up until Xcode 11 if you wanted to offer support for iOS and MacOS you’d have to deliver these frameworks separately. Additionally if you wanted to support iOS simulators and iOS devices you would either have to:
- build two variants of your framework; one for simulators and one for devices
- hand roll a universal framework that supports both simulators and devices
The latter was not supported by Apple. The solution requires to work around Xcode. It also required the integrator to remove the simulator code before submitting to the App Store. At different points Apple did not automatically remove simulator code automatically and rejected build containing simulator code.
However with XCFramework you can now include:
- frameworks to support simulators and devices
- frameworks to support different platforms
Additionally soon you’ll be able to deliver XCFrameworks using Swift Package Manager with Swift 5.3.
How to build an XCFramework
In this section we’ll learn how to build an XCFramework that contains an iOS framework with two variants; one for simulators and one for devices.
We’ll start by downloading a starter kit containing an iOS framework project and an iOS app project. Next we’ll build an XCFramework from the iOS framework through the command line. Finally we’ll learn how to consume the XCFramework using the iOS app in the starter kit.
The steps we’ll take:
- Retrieve the starter project
- Build an XCFramework
- Consuming the XCFramework
1. Retrieve the starter project
Let’s start by downloading the starter Xcode project. Open terminal and execute the following commands:
curl https://github.com/anuragajwani/universal-ios-framework-xcframework/archive/starter.zip -o ios_framework.zip -L -s
unzip -q ios_framework.zip
The starter pack contains a framework project and an app project. The framework contains a single Login screen. The app currently displays a Login button that doesn’t do anything. In the last step we’ll present the Login screen from the framework contained in the XCFramework when the user taps the login button.
2. Build an XCFramework
In this section we’ll build the XCFramework for
MyFramework target. In order to build an XCFramework we’ll need the framework built for devices and simulators.
Here are the steps to build an XCFramework:
- Build framework for devices
- Build framework for simulators
- Build XCFramework from the devices and simulators frameworks
First let’s build the framework for devices. Execute the following command:
xcodebuild archive \
-scheme MyFramework \
-sdk iphoneos \
-archivePath "archives/ios_devices.xcarchive" \
Note at the end of command we are setting two build configuration variables.
BUILD_LIBRARY_FOR_DISTRIBUTION tells Xcode to build the framework with a public interface which is forward compatible with newer versions of Swift.
Frameworks already contain a public interface under a folder that ends with
.swiftmodule. However this public interface is a serialised binary format public interface. This means that newer versions of Swift can have a different format which is not compatible with older versions of Swift.
BUILD_LIBRARY_FOR_DISTRIBUTION creates a new file within the
.swiftmodule directory that ends with
.swiftinterface. The file is a plain text swift interface which is forward compatible with newer versions of Xcode.
The second argument,
SKIP_INSTALL, tells Xcode whether to skip the framework installation in the archive. Here we set to
NO. Thus we can conveniently access the built framework inside the archive created instead of searching in Xcode’s default derived data folder.
Next build the framework for simulators. Execute the following command:
xcodebuild archive \
-scheme MyFramework \
-sdk iphonesimulator \
-archivePath "archives/ios_simulators.xcarchive" \
Lastly let’s build the XCFramework. Execute the following command:
xcodebuild -create-xcframework \
-framework archives/ios_devices.xcarchive/Products/Library/Frameworks/MyFramework.framework \
-framework archives/ios_simulators.xcarchive/Products/Library/Frameworks/MyFramework.framework \
And that’s it! You should now find
MyFramework.xcframework under the build folder.
You’ll notice that
MyFramework.xcframework contains two directories;
ios-x86_64-simulator. The directory name includes information about the platform the containing framework supports as well as the device CPU architecture.
arm64 CPU architecture is used on iOS devices where
x86_64 is used on MacOS; the simulator runs on MacOS thus
ios-x86_64 is for simualtors only.
3. Consuming the XCFramework
For the final step of this tutorial we’ll learn how to consume XCFrameworks. We’ll do that through an app already provided within the starter project named
XCFrameworkDemo. Execute the following command:
Next let’s open project navigator. From menu select View > Navigators > Show Project Navigator…
Here we’ll drag and drop
MyFramework.xcframework. Let’s open
MyFramework.xcframework parent folder in Finder. Execute the following command:
open -a Finder ~/universal-ios-framework-xcframework-starter/MyFramework/build
Then drag and drop
MyFramework.xcframework into the
XCFrameworkDemo’s project navigator pane.
When prompted “Choose options for adding these files:” make sure to check “Copy items if needed”. Also make sure
XCFrameworkDemo target is checked. Click Finish.
One last thing before the framework is successfully linked is to make sure that framework gets embedded into the app when the app gets built. On the project navigator select
XCFrameworkDemo project settings (the one with the blue icon), then select the
XCFrameworkDemo app target. Next select the
Finally scroll down to the “Frameworks, Libraries, and Embedded Content” section and make sure the Embed option of
MyFramework.xcframework is set to
Embed & Sign.
Next we’ll consume the functionality contained within the
MyFramework.xcframework. The following steps will consume code contained within
MyFramework. The functionality of the framework itself is not in scope in this tutorial.
XCFrameworkDemo. Then under
import UIKit add the following line:
Next within the
loginButtonTapped function add the following lines of code:
let loginViewController = LoginViewController()
self.present(loginViewController, animated: true, completion: nil)
And that’s it! Run
XCFrameworkDemo app against simulators or devices and see it work.
In this post we learnt:
- how to build frameworks through the command line
- how to build XCFrameworks from frameworks
- how to consume XCFrameworks
You can find the repo in the link below:
Code to follow along my post on how to build XCFramework that support simulators and devices iOS frameworks …
XCFramework presents as a great option to those delivering and consuming multi-platform frameworks. However for those already delivering compiled universal iOS frameworks before Xcode 11 won’t find much motivation to switch over to XCFrameworks. That is until there is Swift Package Manager support. Swift Package Manager support for distributing compiled code seems to only support XCFrameworks and will be included in Swift 5.3.
I am super excited to see this feature released and start distributing XCFrameworks using Swift Package Manager.
As a final note XCFrameworks also support static libraries which is what I will be exploring next and sharing with you.