Modular iOS
Distributing compiled universal iOS static libraries as XCFrameworks via Cocoapods
Learn how to distribute and consume static libraries as XCFrameworks using Cocoapods
Until recently building and distributing compiled iOS static libraries that were compatible with the iOS devices and simulators was hard. There was no official way of building and distributing universal compiled static libraries. That is until Apple introduced XCFrameworks.
With XCFrameworks we can wrap multiple build variants of static libraries in a single artefact. That means we can now include the build variant for simulators and the variant for devices in a single artefact.
Furthermore we are now able to distribute XCFrameworks through Swift Package Manager now. However at the time of writing the most popular iOS dependency manager is Cocoapods.
In this post I will show you how to distribute a static library as XCFramework through Cocoapods.
I assume you are already familiar with XCFrameworks, static libraries, Cocoapods and Git version control. I also assume you are familiar with the basics of Swift and iOS development.
I have used Swift 5.2.4 and Xcode 11.6 whilst writing this article.
Distributing static library as XCFramework through Cocoapods
In this section we’ll distribute the static library as an XCFramework through Cocoapods.
First we’ll download a starter pack. The starter pack contains a iOS static library project and an iOS app. We’ll build an XCFramework from the static library and distribute it through Cocoapods. In the next section we’ll consume the distributed XCFramework through Cocoapods.
The steps we’ll take:
- Download the starter pack
- Build an XCFramework
- Create git repo to store and version the XCFramework
- Install Cocoapods
- Create git repo for specification files
- Create specification file
- Publish the specification file
1. Download the starter pack
Let’s start by downloading the starter pack. Open Terminal app and execute the following commands:
cd $HOME
curl https://github.com/anuragajwani/distributing-static-lib-xcframework-cocoapods/archive/starter.zip -o static_lib.zip -L -s
unzip -q static_lib.zip
mv distributing-static-lib-xcframework-cocoapods-starter distributing-static-lib-xcframework-cocoapods
We’ll only be using the Terminal app for this section of the post. Make sure to keep it open.
2. Build an XCFramework
Next let’s build the XCFramework from the static library project. This is the artefact that we’ll distribute later. In terminal execute the following command:
cd ~/distributing-static-lib-xcframework-cocoapods/MyStaticLib
sh build_xcframework.sh
The command above will generate MyStaticLib.xcframework
inside the build
folder.
I won’t cover how to build an XCFramework from a static library in this post as that is out of the scope for this post.
3. Create git repo to store and version the XCFramework
Next let’s create a git repo where to host our XCFramework. Let’s create an empty repo. Execute the following commands:
cd ~/distributing-static-lib-xcframework-cocoapods
mkdir XCFrameworkDistribution.git
cd XCFrameworkDistribution.git
git init --bare
git clone ~/distributing-static-lib-xcframework-cocoapods/XCFrameworkDistribution.git ~/distributing-static-lib-xcframework-cocoapods/XCFrameworkDistribution
cd ~/distributing-static-lib-xcframework-cocoapods/XCFrameworkDistribution
touch README.md
git add README.md
git commit -m "Initial commit"
git push origin -u master
Next let’s copy the XCFramework into the repository and let’s save it. Execute the following commands:
cp -r ~/distributing-static-lib-xcframework-cocoapods/MyStaticLib/build/MyStaticLib.xcframework ~/distributing-static-lib-xcframework-cocoapods/XCFrameworkDistribution
git add MyStaticLib.xcframework
git commit -m "Added MyStaticLib.xcframework"
git tag -a 1.0.0 -m "Version 1.0.0"
git push origin master
git push origin --tags
Above we have tagged the commit containing MyStaticLib.xcframework
with 1.0.0
. This is how Cocoapods will find the right version when the integrator specifies that they want to install version 1.0.0
of our xcframework.
4. Install Cocoapods
We’re now ready to distribute our XCFramework through Cocoapods. Execute the following command:
gem install cocoapods -v 1.10.0.beta.1
If you already have Cocoapods installed then make sure its 1.10.0.beta.1
or newer. Cocoapods supports XCFramework from 1.9.0.
However 1.10.0.beta.1
fixes a bug which made 1.9.x
not work with static XCFrameworks.
You can find the Cocoapods version running by running the following command:
pod --version
5. Create git repo for specification files
Next we’ll create a local git repository to hold Cocoapods specification files. We’ll also tell Cocoapods to register the specification repository in its list. Execute the following commands:
cd ~/distributing-static-lib-xcframework-cocoapods/
mkdir MySpecs.git
cd MySpecs.git
git init --bare
git clone ~/distributing-static-lib-xcframework-cocoapods/MySpecs.git ~/distributing-static-lib-xcframework-cocoapods/MySpecs
cd ~/distributing-static-lib-xcframework-cocoapods/MySpecs
touch README.md
git add README.md
git commit -m "Initial commit"
git push origin -u master
pod repo add my-specs ~/distributing-static-lib-xcframework-cocoapods/MySpecs.git
A specification repository holds files on dependencies and their versions. The file contains information on how to install and configure the dependency in the project to be installed. You can find more on how Cocoapods works in a previous post of mine.
6. Create specification file
Next we’ll create a specification file for the framework. The specification must tell Cocoapods where to fetch the framework from and how to configure it. Execute the following command:
cat > ~/distributing-static-lib-xcframework-cocoapods/MyStaticLib.podspec <<-EOF
Pod::Spec.new do |s|
s.name = "MyStaticLib"
s.version = "1.0.0"
s.summary = "A brief description of MyFramework project."
s.description = <<-DESC
An extended description of MyFramework project.
DESC
s.homepage = "http://your.homepage/here"
s.license = { :type => 'Copyright', :text => <<-LICENSE
Copyright 2018
Permission is granted to...
LICENSE
}
s.author = { "$(git config user.name)" => "$(git config user.email)" }
s.source = { :git => "$HOME/distributing-static-lib-xcframework-cocoapods/XCFrameworkDistribution.git", :tag => "#{s.version}" }
s.vendored_frameworks = "MyStaticLib.xcframework"
s.platform = :ios
s.swift_version = "4.2"
s.ios.deployment_target = '12.0'
end
EOF
7. Publish the specification file
Finally we’ll publish the specification file to the specification repository we created in step 5. Execute the following command:
pod repo push my-specs ~/distributing-static-lib-xcframework-cocoapods/MyStaticLib.podspec
That’s it. The XCFramework is now ready to be consumed using Cocoapods.
How to consume XCFrameworks using Cocoapods
In this section we’ll be installing and consuming the XCFramework from the previous section using Cocoapods. We’ll be making use of an already existing app that is included in the starter project.
The steps we’ll take:
- Create Podfile
- Install dependencies
- Consume XCFramework
1. Create Podfile
Before we can install any dependencies to the app we must create a file named Podfile. A Podfile holds information on the dependencies Cocoapods must install for a target in the Xcode project.
Let’s create the Podifle. Execute the following command:
cd ~/distributing-static-lib-xcframework-cocoapods/StaticLibXCFrameworkDemo
cat > Podfile <<-EOF
target 'StaticLibXCFrameworkDemo' do
use_frameworks!
pod 'MyStaticLib', '1.0.0', :source => "$HOME/distributing-static-lib-xcframework-cocoapods/MySpecs.git"
end
EOF
Above we have declared MyStaticLib
as dependency of StaticLibXCFrameworkDemo
target. Also we specified that we’ll be using version 1.0.0
and where to find the specification file (:source
).
2. Install dependencies
Next we have to tell Cococapods to install dependencies declared in the targets specified. Execute the following command:
pod install
3. Consume XCFramework
Let’s consume the code within MyStaticLib
.
Before we can access the code, the integrator must make some changes to their build settings.
First open the project. Execute the following command:
open -a Xcode ~/distributing-static-lib-xcframework-cocoapods/StaticLibXCFrameworkDemo/StaticLibXCFrameworkDemo.xcworkspace
Next, open project configuration settings (the one that says StaticLibXCFrameworkDemo
with a blue icon to its left on the project navigator). Select StaticLibXCFrameworkDemo under targets in the editor area. Then select Build Settings tab. Search for Import Paths which lives under Swift Compiler — Search Paths.
Add the following values:
$(SRCROOT)/Pods/MyStaticLib/MyStaticLib.xcframework/ios-x86_64-simulator
$(SRCROOT)/Pods/MyStaticLib/MyStaticLib.xcframework/ios-arm64
Add the above two values to the build setting Library Search Paths too.
In this case we have added the path static library artefacts for each variant. You’ll have to do so for each build variant you support.
Next open ViewController.swift
. Before consuming the code of the static library we need to import it within the file we want to consume it. At the top let’s import the static library. Under import UIKit
add the following line of code:
import MyStaticLib
Now let’s consume the code. In the viewDidLoad
function add the following line of code:
functionA()
That’s it! Run the app in a simulator or device and watch the console to get the message from functionA()
in the static library.
Summary
In this post we learnt:
- How to distribute static library XCFrameworks through Cocoapods
- How to consume static library XCFrameworks through Cocoapods
Final Notes
In the last step when consuming the static library XCFramework we found that the integrator has to do some configuration to their build settings. Unfortunately this is currently the same with or without Cocoapods. I was quite surprised to find that these do not automatically configure. Hopefully Apple will fix this in a future release of Xcode🤞🏽.