8

My company's iOS framework is meant to work on a real iOS device. Said framework is currently generated as an additional target within a Xcode project which also generates an application. (This makes it relatively easy to debug the framework.)

We recently got requests to also make it work in the simulator, too. I can now make it do so, and the next step is to create a compiled version which works both on a real device and in the simulator. Sadly, I have not been able to locate any material indicating that anyone has done this using Xcode 8. There are materials explaining how do using older versions of Xcode, but what works in one version of Xcode may not work or be advisable in a later version. (We already had one method of creating a universal framework break on us.) I tried using a few pre-Xcode 8 scripts, but none of them worked.

Has anyone managed to generate a universal framework for iOS using Xcode 8? If so, how can it be done?

Thanks in advance for any help anyone can provide.

Aaron Adelman

  • Here is a complete guide I wrote on this subject: https://eladnava.com/publish-a-universal-binary-ios-framework-in-swift-using-cocoapods/ – Elad Nava Oct 26 '16 at 18:14

4 Answers4

17

It is possible since I am currently developing universal frameworks on iOS, watchOS and tvOS on Xcode 8.

The way I do it is creating an Aggregate target(cross platform) and add a run script in its build phase. The script basically compiles the iOS target for iphonesimulator and iphoneos

After this it creates a new binary merging both of them(lipo -create -output)

Would you mind posting your current build script for generating a universal framework so I can guide you with what you are doing wrong?

Take in consideration that the script could not be your issue here, your issue could be setting up your valid architectures, your architectures or even how you are signing the target. I recommend for now, to leave the Automatically manage signing option in your General settings of your target unchecked, and set your provisioning profiles and certs manually.

Run script:

    #!/bin/sh

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

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

# Step 1. Build Device and Simulator versions on iOS
xcodebuild -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${PROJECT_NAME}"  -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6' clean build
xcodebuild -workspace "${PROJECT_NAME}.xcworkspace" -scheme "${PROJECT_NAME}" -sdk iphoneos 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}/iOS"

# Step 3. Create universal binary file using lipo and place the combined executable in the copied framework directory
lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/iOS/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}"

# Step 4. Convenience step to copy the framework to the project's directory
mkdir -p "${TMPDIR}/${PROJECT_NAME}/Frameworks/iOS"

cp -R "${UNIVERSAL_OUTPUTFOLDER}/iOS/${PROJECT_NAME}.framework" "${TMPDIR}/${PROJECT_NAME}/Frameworks/iOS"


# Step 6. Create .tar.gz file for posting on the binary repository
cd "${TMPDIR}"

# We nest the framework inside a Frameworks folder so that it unarchives correctly
tar -zcf "${PROJECT_NAME}.framework.tar.gz" "${PROJECT_NAME}/Frameworks/"
mv "${PROJECT_NAME}.framework.tar.gz" "${PROJECT_DIR}/"

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

Take in consideration that I set the Build Active Architecture Only to NO in the build settings, also the valid Architectures are set as arm64, x86_64, i386, armv7, armv7s. The Architectures are ${ARCHS_STANDARD} armv7s.

I also set a user defined build setting BITCODE_GENERATION_MODE bitcode. With this build setting I make sure to generate binaries with bitcode enabled.

iOSAddicted
  • 389
  • 1
  • 17
  • I don't have a current build script, as none of those I tried worked in Xcode 8. – אהרן אדלמן Oct 06 '16 at 12:36
  • Build Failed due line 23: syntax error near unexpected token `fi' line 23: `fi' Command /bin/sh failed with exit code 2 Step 5 is missing and what is the purpose of "iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 6' clean build" in the Step1 of script. – niczm25 Feb 16 '17 at 08:33
  • @niczm25 I just edited. Regarding the destination it is just making sure you compile for iOS using an iPhone 6. The destination used to be necessary on xcode7 due to a bug. I am not sure that it is still necessary. – iOSAddicted Feb 16 '17 at 11:06
  • Have you ever got the universal framework to work (I mean universal as in tvOS, watchOS, and iOS) I get errors saying the same architecture can't be built with lipo – Knight0fDragon Feb 16 '17 at 16:55
  • @Knight0fDragon I wouldn't go that route. You should generate 3 binaries, the iOS one, tvOS and watchOS and each one of those can include simulator architectures(thats why we refer them as universal). Trying to generate a binary that includes armv7k(watch architecture)for an iOS app it is only going to give you headaches. – iOSAddicted Feb 16 '17 at 17:00
  • This then would require having to import multiple frameworks in the code, and will become a real pain when dealing with the visual designers. I am trying to set it up where they all share the same module name – Knight0fDragon Feb 16 '17 at 17:04
  • all of your binaries can have the same module name. Then u only need to use @import yourframeworkname and if you are compiling your application for tv, then it will only execute the tv binary. No need for change of imports – iOSAddicted Feb 16 '17 at 17:15
  • @iOSAddicted thanks but i am wondering if exporting the fat binary would be recommendable, because i have read some article that you can't use universal fat binaries with application uploaded to AppStore and yet others have successfully uploaded there app without error? am i missing something or the above script and your instruction is enough to distribute universal framework. – niczm25 Feb 17 '17 at 03:23
  • @niczm25 if you use cocoapods and integrate your fat binaries through them, the simulator architectures will be stripped. If you don't use cocoapods, you will have to strip the simulator architectures when archiving for release. You can build your own strip architectures script or do some googling. – iOSAddicted Feb 17 '17 at 10:45
  • @iOSAddicted one more thing regarding exporting universal framework, i have a question that i'm 50/50 if i would ask or hoping to find a similar question. It is necessary to code sign your framework before distributing it? – niczm25 Feb 20 '17 at 03:34
  • 1
    @niczm25 I am not sure is necessary, but I always code sign my frameworks – iOSAddicted Feb 20 '17 at 16:41
  • I got it working for my project, cocoaspods does not allow the same binary name in it's package, turns out there is a separate section they put in to handle tvos and iwatch – Knight0fDragon Feb 21 '17 at 16:06
  • The bundle identifier != product name. My tvOS and watchOS frameworks are named myFrameworktvOS and myFrameworkWatchOS. But the product name for both of them is myFramework. When I import them I import them as @import myFramework – iOSAddicted Feb 21 '17 at 16:08
  • I do not use separate bundle identifiers, I have it set up working with the same identifier that imports using the same product name – Knight0fDragon Feb 21 '17 at 16:19
  • @iOSAddicted when you distribute your cocoa touch framework would you take debug build or release build? and how would do it, i can't seem to find other answer than this [link][http://stackoverflow.com/questions/34643800/how-to-build-a-release-version-of-an-ios-framework-in-xcode#] but as the comments say on the answer, it seem to be like a poor way to build a release version of a library. – niczm25 Feb 23 '17 at 08:45
  • @iOSAddicted For me this works fine on actual devices but will not compile on a simulator when trying to consume framework classes in a consuming sample project. My framework is written in Swift 3 as well as my consuming client application. Everything works fine on a real iOS device so i'm not sure if it could be an Architectures build setting. I followed the steps in this post exactly. On a simulator, the import of my framework will succeed but using anything in the framework can't be found. Not sure what I missed. – zic10 May 20 '17 at 15:31
  • 1
    This is a great and elegant build script, way better than the ones I've seen before. Just a couple of minor suggestions. This assumes that the workspace is named the same as the project; you could make this explicit, and overridable ( by custom build var) with WORKSPACE_NAME := "${PROJECT_NAME}.xcworkspace. Similarly with the library name:- LIBRARY_NAME := ${PROJECT_NAME} – Gordon Dove Aug 15 '17 at 08:49
  • what do you do with BITCODE_GENERATION_MODE? I am currently trying to create iphonesimulator binary without bitcode enabled and iphoneos binary with bitcode enabled – hariszaman Oct 05 '17 at 09:23
  • @hariszaman you don't need to enable it. I do because I provide frameworks for watchOS and tvOS – iOSAddicted Nov 15 '17 at 14:51
  • @niczm25 sorry for the late reply, you have to pass the -configuration "Release" or "Debug" depending on what you are looking for. I would advise passing "Release" – iOSAddicted Jan 27 '18 at 02:05
  • never had a chance to reply @Knight0fDragon about your FAT Universal Framework that includes iOS, watchOS and tvOS. Unfortunately the lipo command will always fail when trying to merge different platforms together. You cannot have a framework that runs on tvOS watchOS and iOS. You will need 3 separate frameworks. – iOSAddicted Jan 27 '18 at 02:08
  • @iOSAddicted Do we need to create universal framework in case of macOS ? I am creating a macOS framework. – subin272 Jun 02 '20 at 11:05
3

build iOS/tvOS universal/fat framework

https://github.com/unchartedworks/universalbuild

Usage:

universalbuild (-p|--project name.xcodeproj) (-s|--scheme schemename) (-c|--configuration configurationname)

Example:

git clone https://github.com/cruisediary/Pastel.git
cd Pastel
universalbuild -p ./Pastel.xcodeproj -s Pastel -c Debug
webcpu
  • 3,174
  • 2
  • 24
  • 17
  • Looks interesting, but I can't give you a +1 unless it works with workspaces :( – Gordon Dove Aug 15 '17 at 08:00
  • Unfortunately, it doesn't work like that. I did some research before I implemented it, many framework projects use project but not workspace. That's why it only supports Xcode project. – webcpu Aug 15 '17 at 09:08
1

Run script to create universal framework (no recursion issue)

#!/bin/sh

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

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

# Next, work out if we're in SIM or DEVICE
if [ "false" == ${ALREADYINVOKED:-false} ]
then

export ALREADYINVOKED="true"

if [ ${PLATFORM_NAME} = "iphonesimulator" ]
then
xcodebuild -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos  BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
else
xcodebuild -target "${PROJECT_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build
fi

# 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 (if it exists) to the copied framework directory
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

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

fi
Himanshu Mahajan
  • 4,779
  • 2
  • 36
  • 29
0

I too struggled with the same issue. I had a series of cocoapods that were used as source pods, but needed to be converted into binary pods. Between lots of googling, trial and error and hacking, I came up with a script that I have had great success with. It is based on the scripts you see here, but refactored into functions and some debugging output (that goes into the logs in /tmp) when something is missing.

That script can be found as a github gist here: https://gist.github.com/intere/bc380fa45ccf23976d3fc297522d29a8

FWIW, I've been using Xcode 9 (9.2, then 9.3, and now 9.4).