How to distribute compiled iOS frameworks using Swift Package Manager
There is a new way to distribute binaries. Fully supported by Apple!
Do you want to distribute your iOS framework to other developers compiled? You may want to hide you source code. Maybe you want to save integrators from having to compile it.
The release of Xcode 12 beta adds a new way for you to distribute your compiled iOS framework — that is using Swift Package Manager. However Swift Package Manager does not directly support distribution of frameworks themselves but rather frameworks wrapped inside of an XCFramework.
I assume you are already familiar with iOS frameworks, how to build these and XCFrameworks. Furthermore you must be familiar with iOS development, git version control and Github. Another pre-requisite is to have your Github account connected to Xcode.
Note Xcode 12.0 beta is required for this post.
Publishing compiled iOS frameworks for Swift Package Manager
In this section we’ll start by downloading an existing iOS framework project. Next we’ll build the framework and wrap it in an XCFramework. Then we’ll create a package specification. Finally we’ll publish the package specification and XCFramework to Github.
The steps we’ll take:
- Retrieve the starter pack
- Building the XCFramework
- Create git repository to host the XCFramework
- Add the XCFramework to git repository
- Create Package specification
- Publish package specification to the git repository
- Push copy of the git repository to Github
1. Retrieve the starter pack
Let’s start by downloading the starter Xcode project. Open the Termainl app and execute the following commands:
curl https://github.com/anuragajwani/swiftpm-xcframework-distribution/archive/starter.zip -o swiftpm-xcframework-tut-starter.zip -L -s
unzip -q swiftpm-xcframework-tut-starter.zip
The starter pack contains a iOS framework project and an iOS app project. The framework contains a single Login screen. The app currently display a Login button that doesn’t do anything. In the last step we’ll present the Login screen from the framework when the user taps the login button.
2. Building the XCFramework
The starter pack’s framework project also includes a script to build an XCFramework. Knowledge of how to build XCFrameworks from frameworks is not required for this post.
Let’s build the XCFramework. Execute the following command:
The script above builds the framework for iOS devices and simulators and then wrap these into an XCFramework. Upon successful execution the script will output a file named
MyFramework.xcframework inside the build directory where the script is located.
3. Create git repository to host the XCFramework
Next we’ll create a git repository to host the XCFramework. In this tutorial we’re using git to version and track our XCFramework releases. Execute the following commands:
git init --bare
git clone ~/MyFrameworkDistribution.git ~/MyFrameworkDistribution
git add README.md
git commit -m "Initial commit"
git push origin -u master
4. Add the XCFramework to git repository
Next we’ll add the XCFramework to the local git repository. Execute the following commands:
cp -r ~/swiftpm-xcframework-distribution-starter/MyFramework/MyFramework.xcframework ~/MyFrameworkDistribution/
git add --all
git commit -m "Added XCFramework"
5. Create Package specification
Next we’ll create the Package specification inside the git repository. The package specification is a file that lets Swift Package Manager know how to consume the package — in this case the XCFramework. Execute the following command:
cat > ~/MyFrameworkDistribution/Package.swift <<-EOF
let package = Package(
The most important bit to note for this post is:
This was specifically added in Swift 5.3 and is the one that allows publishing and consuming compiled source using Swift Package Manager. Here we are saying that the target is compiled, the name of the target and where it lives within our repository.
6. Publish package specification to the git repository
Next we need to publish the package specification to the local git repository. Execute the following commands:
git add Package.swift
git commit -m "Added package specification"
Finally we need to tag this release of our XCFramework. The tag is how Swift Package Manager on the integrators app will find the version the integrator desires to install. Let’s say the integrator wishes to install version
1.1.0 of you XCFramework. Swift Package Manager will look through the tags and use version that matches the tag
For this first release of our XCFramework we’ll tag it
1.0.0. Execute the following command:
git tag -a 1.0.0 -m "Version 1.0.0"
7. Push copy of the git repository to Github
Next we’ll push a copy of our git repository to Github. For such we’ll need to create a new repository on Github. Click on the link below:
Build software better, together
You can't perform that action at this time. You signed in with another tab or window. You signed out in another tab or…
Next name the repository as
MyFrameworkDistribution. You can make your repo private if you like. Click on Create Repository.
Next let’s push a copy of our local git repository to the Github repository just created. Back in Terminal execute the following commands (remember to replace
<YOUR_GITHUB_USERNAME> with your Github username):
git remote add github firstname.lastname@example.org:<YOUR_GITHUB_USERNAME>/MyFrameworkDistribution.git
git push github -u master
git push github --tags
And that is it. Our XCFramework is now published and ready to be consumed by Swift Package Manager!
Consuming XCFramework using Swift Package Manager
In this section we’ll consume the XCFramework we published in the previous section. We’ll be using an iOS app project already included within the starter pack we downloaded in the previous section.
The steps we’ll take:
- Import XCFramework to the app
- Consume the XCFramework code
1. Import XCFramework to the app
First let’s open the app included in the starter pack. Execute the following command:
open -a Xcode-beta ~/swiftpm-xcframework-distribution-starter/SwiftPMXCFrameworkDemo/SwiftPMXCFrameworkDemo.xcodeproj
Note I have saved my copy of Xcode 12.0 beta in my Applications folder.
Next let’s import the XCFramework. From menu select File > Swift Packages > Add Package Dependency…
Next when prompted “Choose Package Repository” search and select
MyFrameworkDistribution. Click Next.
When prompted to “Choose Package Options” leave the options default. XCode already fills out the version to
1.0.0 automatically. Click Next.
Finally when prompted to “Add Package to SwiftPMXCFrameworkDemo:” make sure
MyFramework Package is selected and the target destination (under “Add to Target” column) is
SwiftPMXCFrameworkDemo. Click Finish.
2. Consume the XCFramework code
For the last step of this tutorial we’ll consume the code contained within the XCFramework. Within Xcode open
ViewController.swift and under
import UIKit add the following line of code:
Next within the
loginButtonTapped function add the following lines of code:
let loginViewController = LoginViewController()
self.present(loginViewController, animated: true, completion: nil)
And that is it. Run the code in any simulator or device and see it work! 🎉
In this post we learn’t:
- how to distribute compiled frameworks (as XCFrameworks) through Swift Package Manager
- how to consume compiled frameworks (as XCFrameworks) through Swift Package Manager
For a long time your only option to distribute compiled iOS frameworks was by using an third-party dependency managers such as Cocoapods or Carthage. Furthermore building universal iOS frameworks — one which works with simulators and devices in one artefact — was not supported by Xcode out of the box.
Since Xcode 11.0 Apple gave us the ability to distribute universally compatible frameworks through XCFrameworks. With Xcode 12.0 the Swift community and Apple have brought us the capability of distributing XCFrameworks through SwiftPM.
I have been maintaining and distributing a compiled iOS framework for nearly 4 years now. Until now I have felt that many of the solutions I applied to distribute the compiled framework was hacky and brittle. Every WWDC I wondered what would break next. However its now possible that these will experiences of the past! WWDC 2020 is possibly my favourite WWDC ever ♥️