Modular iOS

Distributing compiled universal iOS static libraries as XCFrameworks via Cocoapods

Learn how to distribute and consume static libraries as XCFrameworks using Cocoapods

Anurag Ajwani

--

Photo by Alfons Morales on Unsplash

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:

  1. Download the starter pack
  2. Build an XCFramework
  3. Create git repo to store and version the XCFramework
  4. Install Cocoapods
  5. Create git repo for specification files
  6. Create specification file
  7. 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:

  1. Create Podfile
  2. Install dependencies
  3. 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🤞🏽.

For more on iOS development 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.