Own Dynamic Framework for iOS, macOS, watchOS and tvOS

Last Updated on

You can find a lot of tutorials on the Internet of shared code between platforms through Dynamic Frameworks, but some tutorials do not work anymore or they are unnecessarily complicated. So I decided to do tutorial with video.

As I’ve said, when we share code between iOS, tvOS, watchOS or macOS, so the simplest way is use Dynamic Framework, which is available on iOS from iOS 8.
We can divide tutorial to three parts:

  1. Creating framework, which contains our shared code
  2. Framework configuration for devices and simulators
  3. Using framework in iOS and macOS applications

If you are lazy to read, so here is the full video tutorial:

Creating a Dynamic Framework

Open Xcode and create new project. File -> New -> Project. Choose iOS -> Framework & Library -> Cocoa Touch Framework and name it MyKit


When you are done with project let’s go rename target inside project from MyKit to iOS MyKit and also rename automatically created scheme for this target. Schemes -> Manage Schemes

We add new target for macOS platform a name it macOS MyKit

We delete folder called macOS MyKit which was automatically created with new target for macOS

After all steps the project will be look something like this:


For each target we open Build Settings and search for Product Name and then we change value to MyKit. This step is important for keeping the same name of framework for each platform.


Again for each target in Build Settings search for Info.plist File and copy value from iOS MyKit target and then paste it to others targets. This step is important for keeping the only one Info.plist file of framework for each platform.


We create our first source file. File -> New -> File and choose iOS -> Source -> Swift File and name it Model. Do not forget to check all targets!

Content of Model.swift:

import Foundation

public class Model {
    public let devices: [String]
    public init() {
        self.devices = ["iPhone", "iPad", "iPod"]

We create another file and this time it will be shell script. File -> New -> File and choose iOS -> Other -> Shell Script. Name it UniversalFramework and check only OS MyKit target.


Content of UniversalFramework.sh:

if [ "true" == ${ALREADYINVOKED:-false} ]
echo "RECURSION: Detected, stopping"
export ALREADYINVOKED="true"


# make sure the output directory exists

# Step 1. Build Device and Simulator versions
xcodebuild -target "${TARGET_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos  BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
xcodebuild -target "${TARGET_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build

# Step 2. Copy the framework structure (from iphoneos build) to the universal folder

# Step 3. Copy Swift modules from iphonesimulator build (if it exists) to the copied framework directory
if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]; then

# Step 4. Create universal binary file using lipo and place the combined executable in the copied framework directory
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}"


I modified the original script. Source HERE.

The new shell file is need to set as executable. Open terminal Aplications -> Utitlities -> Terminal. Then open UniversalFramework.sh file in finder. Write this command to terminal:

$ chmod u+x "path_to_file"


Switch back to the Xcode and select Project MyKit and target iOS MyKit then go to Build Phases.
Click on + button and choose New Run Script Phase and name it Universal Framework.
Insert this shell command:



Select iOS MyKit scheme and any simulator device.

Then we can build selected scheme. Product -> Build.

Change the scheme to macOS MyKit and run build again.

Inside left column select Products folder and right click on any MyKit.framework and choose Show in Finder.

We can see Debug-XYZ folders. Important folder for us is called Debug, which contains MyKit framework for macOS and folder Debug-iosuniversal, which contains MyKit framework for iOS and Simulator.

We are done with framework and it is ready for use 🙂

Let’s look at how to use our framework in iOS and macOS applications. In iOS application we have to remove x86/64 architecture from framework when we build application for iTunes (App Store).

How to use framework

Create new Workspace. File -> New -> Workspace and name it MyApp. I recommended create folder with same name and save workspace file into it.

In the left corner click on the + button and choose New Project.

We select iOS -> Application -> Single View Application and name it MyApp. I recommended create folder with iOS name and save new project into it.




We create new project, but this time macOS application. OS X -> Application -> Cocoa Application and name it MyApp. Again, I recommended create folder with macOS name and save the project into it.


Inside workspace we select iOS application and click on General tab. From Debug-iosuniversal folder, which we opened at the end of first section, we move MyKit.framework to Embedded Binaries. Be sure, that check box Copy Items If needed is unchecked.

The same action we do it for macOS application, but this time we move MyKit.framework from Debug folder.


For iOS and also for macOS application we selecct Build Settings tab and search for Framework Search Paths then we add path to framework. For iOS it will be path to Debug-iosuniversal and for macOS Debug folder.

Now it is time for using our framework inside applications. Select ViewController.swift file in iOS application and replace content with this code:

import UIKit
import MyKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        let model = Model()
        // Do any additional setup after loading the view, typically from a nib.

    override func didReceiveMemoryWarning() {
        // Dispose of any resources that can be recreated.


Same action we do it for macOS application. So select ViewController.swift file in macOS application and replace content with this code:

import Cocoa
import MyKit

class ViewController: NSViewController {

    override func viewDidLoad() {
        let model = Model()

        // Do any additional setup after loading the view.

    override var representedObject: AnyObject? {
        didSet {
        // Update the view, if already loaded.


Inside iOS application we create new shell script. File -> New -> File then OS -> Other -> Shell Script and name it Trim.sh

Content of Trim.sh:

echo "Trimming $FRAMEWORK..."



for ARCH in $ARCHS
echo "Extracting $ARCH..."

echo "Merging binaries..."
lipo -o "$FRAMEWORK_EXECUTABLE_PATH-merged" -create "${EXTRACTED_ARCHS[@]}"


echo "Done."

Source HERE.

Again, in Terminal we set file as executable.

$ chmod u+x "path_to_file"

Select Build Phases tab and click on + button and choose New Run Script Phase and name it Trim Framework.

Insert this shell command:

${SRCROOT}/Trim.sh MyKit


We select scheme for iOS application and build it. Product -> Build.

We repeat previous step for macOS application.

And we are finally done!

Libor Polehňa


Author picture

Thanks very much Libor. Really helpful article and worked well.

Author picture

That’s an astute answer to a tricky qusteion

Load more
Leave a comment

Instead of great success we have experienced great entrepreneurship lessons (for now). It also transformed me, a person who has never worked in IT before, into the app project/product manager. I once took part in a workshop, which encouraged people to invest in their idea and start ...

Read post

  Project Čestina 2.0 covering a variety of the modern Czech language with its slangs and new words has joined forces with vocabulary Flashcard app Vocabulary Miner. They are bringing new words not only to Czechs but ...

Read post

  The most popular higher-order functions are map, filter, and reduce. We all use them since we think that syntax is much better and it is even faster to write them than the old way for-in loop. But is it really true ...

Read post

In order to allow our CI server to deploy applications on Google Play automatically, we have to generate the JSON key. This whole process has three parts: Create a Service account Generate the key Grant access to our key Thanks to this key, we can quickly deploy applications ...

Read post