Tamerlane
Tamerlane

Reputation: 2103

OSX and iOS shared swift module

I have created swift based Cocoa Touch Framework project named TestLib then I have added new target for Cocoa Framework named TestLibOSX. When I use the framework on iOS app it seems to be working without any issue, but when I create OSX console application it XCode is complaining that it couldn't find TestLibOSX module. Am I missing something ?

P.S This is not the same :)

EDIT: Seems this must be possible since I can see Lister is implemented that way.

Upvotes: 6

Views: 2062

Answers (2)

Tokuriku
Tokuriku

Reputation: 1352

Alright, universal frameworks are really a pain. Not only for iOS and OSX but also just within iOS since you need 2 frameworks: one for the simulator and one for the devices. The way you handle iOS is with an "Aggregate" target. I believe the same avenue could be exploited to integrate an OS X target.

When you build a framework, you get a .framework (lego block) folder. In there, there are 2 very important things to be had:

  1. The "Executable" file
  2. The "Modules" folder

In both of these places, you have to have implementation to support all the architectures you desire to support. If you where to build your frameworks separately and wanted to fuse them together, you would have to do the following:

  1. Take both executables and merge them with a "lipo" command.
  2. Make sure that all the files in both "Modules" folder are move together.

In practical terms, it's a mess so here's what I do!

  • First I build the frameworks. They can be on different targets in the same project but usually, the same name helps. For iOS only, theres only one framework so one target suffices.
  • Then I add an "Aggregate" target, it will help create multiple builds for different architectures.
  • Clicking on the project name and then the Aggregate target, I go to "Build Phases" and click on the little plus to add a "New Run Script Phase".
  • There I copy paste the code in this GIST.
  • Finally, changing your scheme to the Aggregate target and building it will do all the merging you need and you end up with a .framework that is universal.

The script is rather straightforward and with a little dabbling, I'm sure it can be tweaked to add OSX support to it. I hope this points you in the right direction :)

Contents of the script:

#!/bin/sh

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

# make sure the output directory exists
mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"

# Step 1. Build Device and Simulator versions
xcodebuild -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos  BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
xcodebuild -target "${PROJECT_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
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/"

# Step 3. Copy Swift modules (from iphonesimulator build) to the copied framework directory
cp -R "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/." "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule"

# 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}"

# Step 5. Convenience step to copy the framework to the project's directory
cp -R "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework" "${PROJECT_DIR}"

# Step 6. Convenience step to open the project's directory in Finder
open "${PROJECT_DIR}"

Upvotes: 1

ColemanCDA
ColemanCDA

Reputation: 1069

If you wish to create a single dynamic framework binary, here are the steps you can follow (as outlined in http://colemancda.github.io/programming/2015/02/11/universal-ios-osx-framework/):

1. Change the project's valid architectures and supported platforms.

This should change your framework's and test unit's valid architectures and supported platforms as well. If not, then manually change them to inherit from the project's build settings.

Step One

  • Base SDK: I recommend OS X, but it will work with iOS too. Note that with with iOS as the base SDK, "My Mac" target is separated into 3 different targets.

  • Supported Platforms: macosx iphoneos iphonesimulator

  • Valid Architectures: arm64 armv7 armv7s i386 x86_64

2. Change the search paths for the Unit Test bundle

Step Two

  • Runpath Search Paths: $(inherited) @executable_path/Frameworks @loader_path/Frameworks @executable_path/../Frameworks @loader_path/../Frameworks

  • Framework Search Paths: $(SDKROOT) $(inherited)

This will allow you to import it as import MyFramework instead of

#if os(iOS)
    import MyFramework
#else
    import MyFrameworkOSX
#endif

Upvotes: 3

Related Questions