[iOS] Building a Framework using Cocoapods

Introduction

When faced with the task of creating a framework, the first thing I did, as expected, was a fair amount of googling. However, in all the tutorials that I met, they created a framework without pods, and of course it was not clear how to work with cocoapods in this case. How to transfer pods, whether it is necessary to transfer them at all, and how, in general, to correctly build the framework, and then implement it in another project.

Tested on xcode 12.4, swift 5

How to create static library Static Library with Cocoapods I wrote here

1. Build a framework

Well, let’s get down to business, open xcode, create a new project, select Framework, click next, name the project FrameworkExample, enable / disable tests – at your discretion.

I turned on unit tests and ended up with an empty project framework:

Now we can create .swift files and add them to our still empty framework.

Don’t forget to specify access modifiers public and open for property and function classes!

Fonts, Localizable.strings localization, Assets images are not added to the library, we will add them to the client project separately!

If we have files (for example, we want to transfer from another project to the framework) that need to be included in the framework, then we simply transfer (drag and drop) them in FrameworkExample:

By clicking on target FrameworkExamples -> General, don’t forget to configure which version the framework is available from and for which devices:

After that, we build our framework. Ctrl + B (twice – for the device and the simulator), and if you don’t have cocoapods, then everything should be successful Build Succeededgo to the end of point 1

If there are subs, then we get an error that they were not found:

So, we create Podfile in the project folder, insert the necessary pods, do not forget to specify use_frameworks! :

platform :ios, '13.0'

target 'FrameworkExample' do  
	use_frameworks!
	pod 'Moya' 
	pod 'Alamofire'  
	pod 'Kingfisher'  
	pod 'EasyPeasy'  
	pod 'KeychainAccess' 
	pod 'SwiftPhoneNumberFormatter'
end

Install pods, open workspace, build Ctrl + B – for simulator and device – should get Build Succeeded:

Find the folder Products, right click on the framework file, then Show In Finder:

We get either into the folder Debug-iphoneos, or Debug-iphonesimulator depending on which product was clicked, we see the folder of our framework FrameworkExample.framework, as well as loaded pods.

If we go one level higher to the Products folder, we will see the same folder for the device:

Thus, at this stage we have built a framework, which will contain all the files placed in the project, the framework itself consists of the following files:

2. Compile the Universal Framework

Universal Framework means a framework that fits both the device and the simulator. That is, we will merge frameworks from folders Debug-iphoneos and Debug-iphonesimulator.

First, let’s add a new target -> Aggregator:

Let’s call it UniversalFramework, it should look like this:

Next, let’s add the Run Script Phase – the code that will be executed when the UniversalFramework is built and create a combined framework for us:

Insert the following script:

echo "project"
# 1) Открываем "${PROJECT_DIR}"
if [ "true" == ${ALREADYINVOKED:-false} ]
then
echo "RECURSION: Detected, stopping"
else
export ALREADYINVOKED="true"

UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-iosuniversal

# 2) Переходим (либо создаем) папку с выходными файлами
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"

# 3) Билдим для устройства и симулятора
echo "iphone"
xcodebuild -target "${TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos  BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" build

echo "iphonesim"
xcodebuild -target "${TARGET_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" build

# 4) Копируем из папки Debug-iphoneos в папку universal
echo "universal"
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/"

# 5) Копируем swift modules
echo "iphone simulator path"
SIMULATOR_SWIFT_MODULES_DIR="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/."
if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]; then
cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule"
fi

# 6) С помощью lipo создаем universal framework
echo "lipo create"
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}"

echo "end"
fi

Build UniversalFramework Ctrl + B,

Now right clicking FrameworkExample.framework and clicking Show In Finder, go to the folder with the framework, and, going up one level to the folder Products, we see the created folder with our universal framework:

This folder contains all the files of our UniversalFramework. Let’s move on to the next step!

3. We implement the framework in ClientApp

“ClientApp” means any project

Create a new project xcode -> new -> file -> project -> App

Let’s call it ClientAppWithFramework:

Move the folder Debug-iosuniversal, those. our universal framework into the folder with our project, then connect it to Build Phases -> Link Binary With Libraries:

Next, download pods to our client project – Podfile can be taken from FrameworkExample:

platform :ios, '13.0'

target 'ClientAppWithFramework' do  
	use_frameworks!
	pod 'Moya' 
	pod 'Alamofire'  
	pod 'Kingfisher'  
	pod 'EasyPeasy'  
	pod 'KeychainAccess' 
	pod 'SwiftPhoneNumberFormatter'
end

After installing pods, open workspace… Import FrameworkExample into ViewController.swift:

Ok, let’s move on to testing and possible errors!

4. Testing and correcting errors

Among my framework files was a test file that contains a public function that prints to us The Framework works!:

public class TestViewController: UIViewController {

    public override func viewDidLoad() {
        super.viewDidLoad()

    }
    
    public func printLog() {
        print("The Framework works!")
    }

}

Let’s try to call this function from ClientApp, add two lines:

import FrameworkExample

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let vc = TestViewController()
        vc.printLog()
        
    }  
}

Above I pisal, that we will upload fonts and pictures separately, so if you have fonts, pictures, localization, then it’s time to transfer them to ClientApp

We launch the application if it crashes with an error:

Then you need to change on the General tab Do not embed on Embed & Sign:

We try to start again, and again an error is possible:

Here is the solution on the Build Phases -> Validate Workspace tab, change from No to Yes, build the project, the error should become a Warning (Magic!), then change Validate Workspace back to No – the error and the vorning should disappear:

We launch the application, make sure that everything works correctly:

Great, the framework is embedded in another project and you can use it!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *