Supplemental Instructions for Building a Universal Framework for iOS

framework_ios

If you’re an iOS developer and have ever found yourself needing to deploy a project as a framework, you know that there’s presently not a great (or easy) way to do so out-of-the-box. You can create a static library, fat static library, etc. But there’s no out-of-the-box way to turn a project into a framework. Well, I found and followed the instructions laid out in an article entitled “Building a Universal Framework for iOS” (by Justin DeWind of Atomic Object), and sure enough, it worked like a charm. Everything got packaged up into a nice little universal framework that can be used in any environment (simulator or device). Well, at least it worked part of the time.

The only down side to the method outlined in the Atomic Object article is that (as is stated at the end of the article) you need to “Ensure that the header files are copied into place (XCode is known to mess this up on occasion).” Sure enough, I found that the headers were only copied in every other time I built my project. I’m not sure how or why this happened, but I spent some time investigating and noodling around with my project and found a more bulletproof way to ensure that all of the headers are included every time.

I’m not going to reinvent the wheel by explaining in-depth the entire process to create a universal framework (you can read the article yourself to get up to speed). Rather, I’ll provide a broad overview of the steps the article set forth to create the universal framework, as well as propose an alternative (supplemental) way to package the header files into the final framework.

Overview of Steps

  1. Create a new Cocoa Touch Static Library project
  2. Configure architectures in the project’s build settings
  3. Create aggregate target
  4. Build static libraries
  5. Build universal binary (see below for supplemented code)
  6. Copy header files into place (see below for further explanation)
  7. Configure aggregate target build against the ‘Release’ configuration
  8. Build and verify framework

Don’t Add Copy Files Phase

You no doubt noticed that I crossed out the “copy header files” step in the outline above. I found that letting Xcode include the headers for you doesn’t always work (hence why the author of the original page called out that Xcode is known to mess it up on occasion). Instead, I found it better to copy the header files in manually by updating the run script.

Update the Run Script

To get around the lack of copy headers, we will update the run script to remove any existing universal directories (including the “Versions/A/Headers”), as well as to copy in the necessary headers. This ensures that the header directories are expunged first, so the directories are made new each build, and that you are in charge of copying the header files rather than letting the default Xcode copy files directive do so.

Most of the final run script is from the original article, with the exception of the added “rm” command to remove the Headers directory, and the addition of the “cp” commands at the end of the script, which manually copy the headers into the Headers directory.

Here’s what the full run script looks like:

SIMULATOR_LIBRARY_PATH="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/lib${PROJECT_NAME}.a" &&
DEVICE_LIBRARY_PATH="${BUILD_DIR}/${CONFIGURATION}-iphoneo/lib${PROJECT_NAME}.a" &&
UNIVERSAL_LIBRARY_DIR="${BUILD_DIR}/${CONFIGURATION}-iphoneuniversal" &&
UNIVERSAL_LIBRARY_PATH="${UNIVERSAL_LIBRARY_DIR}/${PRODUCT_NAME}" &&
FRAMEWORK="${UNIVERSAL_LIBRARY_DIR}/${PRODUCT_NAME}.framework" &&
#
# Remove output directories (to avoid errors in overwriting)
#
rm -rf "${BUILD_DIR}/${CONFIGURATION}/${PRODUCT_NAME}.framework/Versions/A/Headers" &&
rm -rf "${BUILD_DIR}/${CONFIGURATION}" &&
#
# Create framework directory structure.
#
mkdir -p "${UNIVERSAL_LIBRARY_DIR}" &&
mkdir -p "${FRAMEWORK}/Versions/A/Headers" &&
mkdir -p "${FRAMEWORK}/Versions/A/Resources" &&
#
# Generate universal binary for the device and simulator.
#
lipo "${SIMULATOR_LIBRARY_PATH}" "${DEVICE_LIBRARY_PATH}" -create -output "${UNIVERSAL_LIBRARY_PATH}" &&
#
# Move files to appropriate locations in framework paths.
#
cp "${UNIVERSAL_LIBRARY_PATH}" "${FRAMEWORK}/Versions/A" &&
ln -s "A" "${FRAMEWORK}/Versions/Current" &&
ln -s "Versions/Current/Headers" "${FRAMEWORK}/Headers" &&
ln -s "Versions/Current/Resources" "${FRAMEWORK}/Resources" &&
ln -s "Versions/Current/${PRODUCT_NAME}" "${FRAMEWORK}/${PRODUCT_NAME}" &&
#
# Copy header files (need to do in run script to avoid copy errors from default Copy Files build phase)
#
cp "${SRCROOT}/${PROJECT_NAME}/MyLibrary.h" "${FRAMEWORK}/Versions/A/Headers" &&
cp "${SRCROOT}/${PROJECT_NAME}/MyFirstHeaderFile.h" "${FRAMEWORK}/Versions/A/Headers" &&
cp "${SRCROOT}/${PROJECT_NAME}/MySecondHeaderFile.h" "${FRAMEWORK}/Versions/A/Headers"

 

Obviously, you’ll need to manually enter the names of your header files in the last few lines of this script. This may be a pain if you have a lot of header files, but in my experience with this fix, it worked every time to successfully remove the existing directories and then copy in all of the necessary header files.

Changing the Output Location for Your Framework

If you want to take things a step further, like I did, you can noodle around with outputting the framework to a better location on your hard drive – say a location relative to your source code – rather than in the default “Derived Data” folder (which is hidden and takes a bit of know-how to get to). I found that you can do this fairly easily by changing the value of UNIVERSAL_LIBRARY_DIR to use ${PROJECT_DIR} instead of ${BUILD_DIR}.

You’ll need to toy around with pathing in this instance, backing out of the project directory. In my case, I wanted to put the framework in a directory named “Release” that was 2 directory levels up from my source code. When all was said and done, my value for UNIVERSAL_LIBRARY_DIR ended up looking something like this:

UNIVERSAL_LIBRARY_DIR="${PROJECT_DIR}/../../Release/${CONFIGURATION}-iphoneuniversal"

 

With this logic in place, my framework now gets placed into my “Release” directory, which is much easier to find and access than the default location specified – not to mention easier to source control along with my source code.

Until next time, happy coding!

There are no comments yet, add one below.

Leave a Comment