Home iOS & Swift Tutorials

Creating a Framework for iOS

Learn how to build an iOS framework, which lets you share code between apps, modularize your code or distribute it as a third-party library.

4.8/5 9 Ratings

Version

  • Swift 5, iOS 14, Xcode 12
Update note: This tutorial was updated to iOS 14, Xcode 12 and Swift 5.3 by Emad Ghorbaninia. The original tutorial was written by Sam Davies.

Have you ever wanted to share a chunk of code between two of your apps or wanted to share a part of your program with other developers?

Maybe you wanted to modularize your code like the iOS SDK separates its API by functionality. Or perhaps you want to distribute your code the same way popular third party libraries do.

In this tutorial, you’ll extract CalendarControl, developed in Creating a Custom CalendarControl for iOS into a separate reusable framework. Along the way, you’ll:

  • Create a new framework for CalendarControl.
  • Migrate the existing code.
  • Import everything back to the app.
  • Build a binary framework, XCFramework.
  • Pack it as an uber-portable Swift Package.
  • Set up a repository for your Swift Package and publish it.

By the time you’re done, the app will behave as it did before while using the portable XCFramework you developed!

Getting Started

Download the starter and final projects by using the Download Materials button at the top or bottom of this tutorial. Find the starter project folder. Locate 1-RWCalendarPickerUI and open RWCalendarPicker.xcodeproj.

RWCalendarPicker is a Reminders-like checklist app that lets users create tasks and set their due dates.

Build and run to get an idea of how it works.

Showcase

Take a look at the files in RWCalendarPicker to familiarize yourself with the project. CalendarControl‘s code is split into several classes:

  • Day.swift: A data model which holds data of each day object.
  • MonthMetadata.swift: The data model for months.
  • CalendarDateCollectionViewCell.swift: Shows days in a month using cells in a collection view. Each cell is customized here.
  • CalendarPickerFooterView.swift: Lets the user select different months.
  • CalendarPickerHeaderView.swift: Shows the current month and year, lets the user close the picker and displays the weekday labels.
  • CalendarPickerViewController.swift: The body of the calendar where all related views are combined.

CalendarPicker is pretty handy. Wouldn’t it be nice to use it in several apps beyond this one? Frameworks to the rescue!

What is a Framework?

Frameworks are self-contained, reusable chunks of code and resources you can import into many apps. You can even share them across iOS, tvOS, watchOS and macOS apps. When combined with Swift’s access control, frameworks help define strong, testable interfaces between code modules.

Frameworks have three major purposes:

  • Encapsulate code
  • Modularize code
  • Reuse code

If you’ve programmed in other languages, you may have heard of node modules, packages, gems or jars. Frameworks are the equivalent of these in the Apple ecosystem. Some examples of common frameworks in the iOS SDK include Foundation, UIKit, SwiftUI, CloudKit and Combine.

In Swift parlance, a module is a compiled group of code distributed together. A framework is one type of module while an app is another.

Note: If you want to learn more about frameworks, read What are Frameworks?.

Creating a Framework

In Xcode 6, Apple introduced the Cocoa Touch Framework and recently changed it to Framework. Creating frameworks has never been easier. First, you’ll create the project for the framework.

In Xcode, select File ▸ New ▸ Project…. Then choose iOS ▸ Framework & Library ▸ Framework.

Framework selection dialog

Click Next. Then set the Product Name to CalendarControl. Use your own Organization Name and Organization Identifier.

Framework configuration dialog

Click Next. In the file chooser, choose to create the project at 2-Framework. Then click Create.

Now you have a project, albeit a boring one, that creates a framework!

Adding the Source Code to the Framework

Your current state is a framework without code. That’s about as appealing as straight chocolate without sugar. In this section, you’ll introduce code by adding the existing files to the framework.

From the RWCalendarPicker source directory, drag Day.swift into the CalendarControl project in Xcode. Make sure to check Copy items if needed so the files copy into the new project instead of adding a reference. Frameworks need their own code, not references, to be independent.

File into the framework

Double-check that Day.swift has Target Membership in CalendarControl to ensure it appears in the final framework. Verify this by selecting this file and ensuring CalendarControl is selected in the Target Membership area of the File inspector.

Now, add these files to your project by following the steps above:

  • MonthMetadata.swift
  • CalendarDateCollectionViewCell.swift
  • CalendarPickerFooterView.swift
  • CalendarPickerHeaderView.swift
  • CalendarPickerViewController.swift

Add all the classes

Note: Separating classes into their own folder group isn’t strictly necessary, but it’s good practice for organizing your code.

Select your project in the Project navigator and choose CalendarControl in Targets. Open the Build Settings. Then set Build Libraries for Distribution to yes. This produces a module interface file which shows your public API when someone jumps to definition of your module in Xcode.

Build Libraries for Distribution

Build the framework project. Make sure you get Build Succeeded with no build warnings or errors.

Adding Framework to the Project

Close CalendarControl. Go back to RWCalendarPicker. Now that you’ve assembled your calendar framework into the framework, you no longer need the migrated files in the main project. Delete the following files. Select Move to Trash in the confirmation dialog.

  • Day.swift
  • MonthMetadata.swift
  • CalendarDateCollectionViewCell.swift
  • CalendarPickerFooterView.swift
  • CalendarPickerHeaderView.swift
  • CalendarPickerViewController.swift

Remove classes

Build the project. You’ll see several predictable errors where Xcode complains about not knowing what the heck a CalendarPickerViewController is. Specifically, you’ll see a Cannot find ‘CalendarPickerViewController’ in scope error message.

You’ll solve these problems by adding the CalendarControl framework project.

Embedding Your Binary

Now, right-click the root RWCalendarPicker node in the Project navigator. Click Add Files to “RWCalendarPicker”.

In the file chooser, navigate to 2-Framework/CalendarControl and select CalendarControl.xcodeproj. Then click Add to add it as a sub-project.

Embedding

Note: It isn’t strictly necessary to add the framework project to the app project. You could add the CalendarControl.framework output.

However, combining the projects makes it easier to develop the framework and app simultaneously. Any changes you make to the framework project automatically propagate up to the app. It also makes it easier for Xcode to resolve the paths and know when to rebuild the project.

Build and run. You’ll see the same compile error!

Error

Even though the two projects are now together, RWCalendarPicker still doesn’t get CalendarControl. It’s like they’re sitting in the same room, but RWCalendarPicker can’t see the new framework.

Follow these steps to fix the problem:

  1. Change the scheme to CalendarControl and build it.
  2. Then, expand the CalendarControl project to see the Products folder.
  3. Look for CalendarControl.framework beneath it. This file is the output of the framework project that packages the binary code, headers, resources and metadata.
  4. Then select the top level RWCalendarPicker node to open the project editor.
  5. Click the RWCalendarPicker target. Then go to the General tab.
  6. Scroll down to the Frameworks, Libraries and Embedded Content section.
  7. Drag CalendarControl.framework from the Products folder of CalendarControl.xcodeproj onto this section.

Add the Framework

You added an entry for the framework in Frameworks, Libraries and Embedded Content.

Now the app knows about the framework and where to find it. That should be enough, right?

Switch to RWCalendarPicker scheme and build the project. More of the same errors.

Still not working

You missed an important part of Framework development: Access Control.

Access Control

Although the framework is part of the project, the project’s code doesn’t know about it. Out of sight, out of mind.

Go to ItemDetailViewController.swift, and add the following line to the list of imports at the top of the file:

import CalendarControl

While it’s critical, this inclusion still won’t fix the build errors because Swift uses access control to let you determine whether constructs are visible to other files or modules.

By default, Swift makes everything internal — visible only within its own module.

To restore functionality to the app, you have to update the access control on the CalendarControl classes.

Although it’s a bit tedious, the process of updating access control improves modularity by hiding code not meant to appear outside the framework. You do this by leaving certain functions with no access modifier or explicitly declaring them internal.

Swift has five levels of access control. Use the following rules of thumb when creating your own frameworks:

  • Open and public: For code called by the app or other frameworks, such as a custom view.
  • Internal: For code used between functions and classes within the framework, such as custom layers in that view.
  • Fileprivate: For code used within a single file, such as a helper function that computes layout heights.
  • Private: For code used within an enclosing declaration, such as a single class block and extensions of that declaration in the same file.

When CalendarControl was part of the RWCalendarPicker app, internal access wasn’t a problem. Now that it’s in a separate module, you must make it public for the app to use it. You’ll do that in the next section.

Note: If you want to learn more about access control and understand the difference between open and public, take a look at the Access Control Documentation.

Updating the Framework Access Level

Open CalendarPickerViewController.swift. Make the class public by adding the public keyword to the class definition, like so:

public class CalendarPickerViewController: UIViewController {

Now CalendarPickerViewController is visible to any app file that imports the CalendarControl framework.

Next, add the public keyword to:

  • CalendarPickerViewController.init(baseDate:selectedDateChanged:)
  • CalendarPickerViewController.init(coder:)
  • CalendarPickerViewController.viewDidLoad()
  • CalendarPickerViewController.viewWillTransition(to:with:)
  • CalendarPickerViewController.collectionView(_:numberOfItemsInSection:)
  • CalendarPickerViewController.collectionView(_:cellForItemAt:)
  • CalendarPickerViewController.collectionView(_:didSelectItemAt:)
  • CalendarPickerViewController.collectionView(_:layout:sizeForItemAt:)
Note: You might wonder why you have to declare init as public. Apple explains this and other finer points of access control in their Access Control Documentation.

Build and run. Now you get your CalendarControl.

Congratulations! You now have a working stand-alone framework and an app that uses it!

Baby framework

Publishing the XCFramework

You might have heard about XCFramework during WWDC 2019. Yes, you’re right: This is the name of the binary framework you can generate with Xcode.

Before 2019, you only had one opportunity to make your own binary framework: Universal Static Library, also known as Fat Framework.

To support multiple architectures, like a simulator and devices, you had to combine them under one library in the fat framework. However, after this article, your frameworks don’t have to be fat anymore.

Archiving Your Framework

For this section, you’ll work with your old friend, Terminal. Woohoo!

Open your terminal and navigate to the framework folder with the following command. Alternatively, you could drag your project folder to your terminal after the cd command:

cd "CreateFrameworkForiOS/starter/2-Framework"

Next, start archiving your framework for the following targets:

  • iOS
  • Simulator
  • macOS

Start with iOS. Enter the following command into the terminal:

xcodebuild archive \
-scheme CalendarControl \
-configuration Release \
-destination 'generic/platform=iOS' \
-archivePath './build/CalendarControl.framework-iphoneos.xcarchive' \
SKIP_INSTALL=NO \
BUILD_LIBRARIES_FOR_DISTRIBUTION=YES

This command will generate an archive of your framework by using the following list as inputs :

  1. -scheme CalendarControl: It’ll use this scheme for archiving.
  2. -configuration Release: It’ll use the release configuration for building.
  3. -destination ‘generic/platform=iOS’: This is the architecture type.
  4. -archivePath: It saves archives into this folder path with the given name.
  5. SKIP_INSTALL: Set NO to install the framework to the archive.
  6. BUILD_LIBRARIES_FOR_DISTRIBUTION: Ensures your libraries are built for distribution and creates the interface file.

Next, target Simulator. Make an archive by adding this command to your terminal:

xcodebuild archive \
-scheme CalendarControl \
-configuration Release \
-destination 'generic/platform=iOS Simulator' \
-archivePath './build/CalendarControl.framework-iphonesimulator.xcarchive' \
SKIP_INSTALL=NO \
BUILD_LIBRARIES_FOR_DISTRIBUTION=YES

These command options are the same as those for iOS except for the following differences:

  1. -destination ‘generic/platform=iOS Simulator’: This is where you set the architecture type.
  2. -archivePath: This generates the archive into the folder path with the given name.

Finally, generate a new archive for macOS. Add the following command in the terminal:

xcodebuild archive \
-scheme CalendarControl \
-configuration Release \
-destination 'platform=macOS,arch=x86_64,variant=Mac Catalyst' \
-archivePath './build/CalendarControl.framework-catalyst.xcarchive' \
SKIP_INSTALL=NO \
BUILD_LIBRARIES_FOR_DISTRIBUTION=YES

This command is the same as the others except for the following differences:

  1. -destination ‘platform=macOS,arch=x86_64,variant=Mac Catalyst’: This is where you indicate the architecture type.
  2. -archivePath: This generates the archive into the folder path with the given name.

As you can see in your finder and the following screenshot, you generate three different archives files from your framework.

Archive list

Generating the XCFramework

Now, make the binary framework, XCFramework. Add the following command to the terminal:

xcodebuild -create-xcframework \
-framework './build/CalendarControl.framework-iphonesimulator.xcarchive/Products/Library/Frameworks/CalendarControl.framework' \
-framework './build/CalendarControl.framework-iphoneos.xcarchive/Products/Library/Frameworks/CalendarControl.framework' \
-framework './build/CalendarControl.framework-catalyst.xcarchive/Products/Library/Frameworks/CalendarControl.framework' \
-output './build/CalendarControl.xcframework'

This command adds your XCFramework to the build folder using the previous archives you generated.

Check the build folder to see what the XCFramework includes.

XCFramework in Finder

Boooooom! You’ve now made your first XCFramework.

XCFramework

Integrating XCFramework Into Your Project

It’s time to remove the CalendarControl reference from the RWCalendarPicker project, like this:

Removing

You do this because you don’t need to have an access to your framework’s source code anymore. Drag XCFramework to the Frameworks, Libraries and Embedded Content section of your project target:

Adding XCFramework

Build and run. You’ll have access to the same classes as before but this time your framework is only binary.

Distributing XCFramework as a Swift Package

At WWDC 2020, Apple announced that you can easily distribute your XCFramework within Swift Packages. Isn’t that awesome?

Note: If you’re not familiar with Swift Packages or Swift Package Manager you can find out more by reading Swift Package Manager for iOS.

You should have a Swift Package for distributing your XCFramework. You’ll create one in the next section. Then you can share your fancy framework by publishing it on GitHub.

Preparing the Swift Package

In the starter project files you already have a simple Swift Package. Navigate to /starter/3-SwiftPackage/CalendarControl and open Package.swift

Swift Package manifest

This class is the manifest for your Swift Package. You need to modify it to make CalendarControl a Swift Package.

Follow these steps and fill manifest with the right value:

  1. Platforms:
  2. platforms: [
      .macOS(.v10_15), .iOS(.v14), .tvOS(.v14)
    ],
    

    This code indicates which platforms it can run on.

  3. Products:
  4. products: [
      .library(
        name: "CalendarControl",
        targets: ["CalendarControl"]),
    ],
    

    These are products the package provides. These can be either a library — code you can import into other Swift projects — or an executable — code you can run by the operating system. A product is a target you can export for other packages to use.

  5. Targets:
  6. targets: [
      .binaryTarget(
        name: "CalendarControl",
        path: "./Sources/CalendarControl.xcframework")
    ]
    

    Targets are modules of code that are built independently. Here you add a path for your XCFramework.

Note: The first line of manifest must contain a formatted comment which tells Swift Package Manager the minimum version of the Swift compiler requires to build the package.

Finally, add your XCFramework to the project and put it under Sources:

Adding XCFramework

Congratulations! Your Swift Package is ready for distribution.

Framework hero

Large Binary and Compute Check-Sum

Although this isn’t the case for CalendarControl, a particularly massive framework will need extra care. Open up the spoiler below if you’d like to learn more about handling larger binaries.

[spoiler title=”Large Binary and Compute Check-Sum”]

If you have a large binary framework, as Apple suggested, you shouldn’t add it to your repository. Instead, you can upload it to your host and add a URL in your manifest target like this:

targets: [
  .binaryTarget(
    name: "CalendarControl",
    url: "https://example.com/CalendarControl.xcframework.zip",
    checksum: "4d4053074fd8b92f9f2f339c48980b99")
]

Here you have three inputs:

  1. Name: This is your package name.
  2. URL: Your XCFramework zip file URL.
  3. Checksum: Your Swift Package will ensure the framework zip file is the same as you made by checking this checksum.

Follow these steps to calculate the checksum:

Checksum

  1. Compress your framework and copy the zip file into Swift Package folder.
  2. In the terminal, navigate to Swift Package folder.
  3. Run the following command and generate a checksum.
swift package compute-checksum CalendarControl.xcframework.zip

Your XCFramework checksum is here. You can copy this to your manifest.

[/spoiler]

Publishing Your Swift Package

Xcode makes publishing your packages easy. Next, you’ll publish the CalendarControl Swift Package you created.

Using Xcode, open the Swift Package project. Then select Source Control ▸ New Git Repositories… from the menu bar.

Git repo

This creates a Git repository on your computer and an initial commit of your code.

Before you can publish your package for others to use, it must be available publicly. The easiest way to do this is to publish to GitHub.

If you don’t have a GitHub account you can create one for free at github.com. Then, if you haven’t done so already, add your GitHub account to Xcode by selecting Xcode ▸ Preferences in the Xcode menu. Select Accounts.

Now, click + to add a new account. Select GitHub and fill in your credentials as requested.

Add github

Open the Source Control navigator and select the CalendarControl package. Then open the context menu by either Right-clicking or Control-click.

Next, select New “CalendarControl” Remote…. You can change the visibility to Private or accept the default settings. Then click Create.

Create Remote

This creates a new repository on GitHub and automatically pushes the code there for you.

Next, set your framework’s version by creating a tag for your package. In the context menu, select Tag main…. Tag it as version 1.0.0 and click Create.

Tagging

Finally, select Source Control ▸ Push… from the Xcode menu bar. Make sure Include tags is selected. Then click Push.

This pushes the tag to GitHub where the SwiftPM can read it. Version 1.0.0 of your package is now live. :]

Pushing

Using Swift Package

Now it’s the time to use your Swift Package in the RWCalendarPicker project.

Open RWCalendarPicker again. Remove the imported CalendarControl framework from your project target:

Removing

Next, you’ll learn how to add a Swift Package to a project using Swift Package Manager.

Adding Package Dependency Using SPM

Select File ▸ Swift Packages ▸ Add Package Dependency…. Paste the Git Repository URL. The click Next.

Depending on your GitHub settings, you may need to authenticate your SSH key here. Then, under Rules, make sure Up to Next Major is selected for the version 1.0.0. Click Next.

If you want to learn more about major and minor versioning check out semver.org. After Xcode fetches the package, ensure the CalendarControl product is selected and added to the RWCalendarPicker target. Then select Finish.

Adding Swift Package

Build and run. Make sure everything runs as before.

Building

Fantastic! Now you have a remote Swift Package that you used inside a project.

Gold Medal

Where to Go From Here?

You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.

In this tutorial, you learned how to:

  1. Extract CalendarControl from RWCalendarPicker and make a new framework.
  2. Use the CalendarControl Framework in RWCalendarPicker.
  3. Build a binary framework and use that XCFramework in RWCalendarPicker.
  4. Publish a Swift Package and use it in RWCalendarPicker.

Nice work!

From here, take a look at WWDC 2019 and 2020 videos regarding this topic:

If you have any comments or questions, please join the discussion below.

Average Rating

4.8/5

Add a rating for this content

9 ratings

More like this

Contributors

Comments