Search code examples
swiftxcodeswift-package-manager

Swift package manager: create custom framework


I want to create a custom framework in Swift, so that I can use it in multiple projects. Therefore I started with

mkdir TestFramework && cd TestFramework
swift package init --type library

and

swift package generate-xcodeproj

Then I started to create in there some classes and I built the code. The result is a .framework file. I moved it to my other project and linked it there (therefore I just copy the .framework-folder to the Xcode-project and add it to the target). import TestFramework works, but I have no access to any classes, I created in the Framework: Use of unresolved identifier 'TestClass'

The strange thing is: TestFramework.framework/Versions/A/TestFramework seems to be an executable. This is the Package.swift:

let package = Package(
    name: "TestFramework",
    products: [
        // Products define the executables and libraries produced by a package, and make them visible to other packages.
        .library(
            name: "TestFramework",
            targets: ["TestFramework"]),
    ],

Solution

  • The best way to approach this is using an Xcode toplevel workspace (File -> New -> Workspace). So create a workspace named Example.

    As the second step create a directory named lib and place it inside the Example directory. Placing it inside the directory is not strictly required but I do this to keep the files in a single project directory. Next run inside the lib directory:

    swift package init --type library
    

    and add some code to create your framework.

    Next create a new project using Xcode. So File -> New -> Project and store that project also inside the Example directory (Again not strictly required to put it inside Example). For now let's assume you create a Cocoa App named TestUI.

    Now open your example workspace and add the two projects to the workspace using File -> Add Files to "Example". You should add the generated lib.xcodeproj for the lib directory and the TestUI.xcodeprojfor TestUI.

    From this moment on Xcode will automatically take care of things. It will store all files from both projects in a single build directory and you can create/run/build/debug your application as you would expect.

    The structure should look like this:

    Example
    |-- Example.xcworkspace
    |-- TestUI
    |   |-- TestUI
    |   |   |-- AppDelegate.swift
    |   |   |-- Assets.xcassets
    |   |   |   |-- AppIcon.appiconset
    |   |   |   |   `-- Contents.json
    |   |   |   `-- Contents.json
    |   |   |-- Base.lproj
    |   |   |   `-- Main.storyboard
    |   |   |-- Info.plist
    |   |   |-- MailboxViewController.swift
    |   |   |-- TestUI.entitlements
    |   |   `-- ViewController.swift
    |   `-- TestUI.xcodeproj
    `-- lib
        |-- lib.xcodeproj
        |-- Package.resolved
        |-- Package.swift
        |-- README.md
        |-- Sources
        |   |-- ExampleLib
        |   |   |-- Account.swift
        |   |   |-- Credentials.swift
    

    There is one major limitation to this approach. If your library has non Swift components, e.g. a wrapper around a C-library it will not work out of the box. You will get messages like "Cannot find required Framework xxx" if you try to use it. This would also happen if you add some required packages to your library that are 'C' based.

    I have found the following work around. First go to the lib directory and run the following command:

    find lib.xcodeproj -name module.modulemap
    

    In the case that you have used/created the xxx library you get the following output:

    lib.xcodeproj/GeneratedModuleMap/xxx/module.modulemap
    

    Now go to the Example workspace and select TestUI -> Build Settings -> All and enter in the Search box "Other Swift Flags".

    Open that and add the following two fields:

    -Xcc
    -fmodule-map-file=$(SRCROOT)/../lib/lib.xcodeproj/GeneratedModuleMap/xxx/module.modulemap
    

    And that's it. It should all work now. For compilation and running it is not even necessary to embed binaries in the TestUI application although I assume it will be necessary for distribution.