Modular iOS

How to build universal iOS frameworks using XCFrameworks

Anurag Ajwani
7 min readJun 1, 2020
Vector graphics sourced from https://www.vecteezy.com

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:

  1. What is XCFramework?
  2. Why use XCFramework?
  3. 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; SignInWithMyCompany.xcframework.

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:

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:

  1. Retrieve the starter project
  2. Build an XCFramework
  3. Consuming the XCFramework

1. Retrieve the starter project

Let’s start by downloading the starter Xcode project. Open terminal and execute the following commands:

cd $HOME
curl https://github.com/anuragajwani/universal-ios-framework-xcframework/archive/starter.zip -o ios_framework.zip -L -s
unzip -q ios_framework.zip
cd universal-ios-framework-xcframework-starter/MyFramework

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:

  1. Build framework for devices
  2. Build framework for simulators
  3. 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" \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
SKIP_INSTALL=NO

Note at the end of command we are setting two build configuration variables.

The first 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" \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES \
SKIP_INSTALL=NO

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 \
-output build/MyFramework.xcframework

And that’s it! You should now find MyFramework.xcframework under the build folder.

You’ll notice that MyFramework.xcframework contains two directories; ios-arm64 and 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.

Let’s open XCFrameworkDemo. Execute the following command:

open ~/universal-ios-framework-xcframework-starter/XCFrameworkDemo/XCFrameworkDemo.xcodeproj

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 General tab.

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.

Open ViewController.swift in XCFrameworkDemo. Then under import UIKit add the following line:

import MyFramework

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.

Summary

In this post we learnt:

  • how to build frameworks through the command line
  • how to build XCFrameworks from frameworks
  • how to consume XCFrameworks

Final Notes

You can find the repo in the link below:

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.

Stay tuned! Follow me on Twitter or Medium.

--

--

Anurag Ajwani

Senior iOS Engineer at Travelperk. 7+ years experience with iOS and Swift. Blogging my knowledge and experience.