I spent a few hours today researching how to put together a framework bundle that can be used in multiple iOS applications with minimal hassle. As it turns out, this is not as simple as it sounds. There are several good articles on the subject out there, but the 4.0 SDK rendered pretty much all of them obsolete. So I wanted to share my current solution here.
Before going further, if you are not sure about why we need to use static libraries, what a universal library means, or how to put together your basic framework project, I would suggest working through at least a couple of these excellent articles on the subject:
The main problem you will face after using any of these methods is that the option to pick “iPhone Simulator” as a Base SDK is removed with 4.0. This means that you cannot force your Simulator target to build for the i386 architecture. There is a hidden workaround for this though: In your simulator target’s build settings, select “Other…” among the Base SDK options, then put “iphonesimulator” into the text box that pops up. This will make the target use the latest Simulator SDK.
Unfortunately, this approach doesn’t work so well in practice. When you are using an “Aggregator” target to build your static libraries, Xcode still insists on building only for the active architecture (Device or Simulator), ignoring your target’s architecture setting.
There are several alternative solutions out there, including having a Makefile or using the command line to build the project instead of Xcode. But I really wanted to be able to build with a single command, without having to leave Xcode, so I made a slightly customized Aggregator target that uses a bash script to build the two targets properly, then organizes the final build into the standard framework directory structure. Here is the script:
xcodebuild -configuration "${CONFIGURATION}" -target "${PRODUCT_NAME}Framework-Device" -sdk iphoneos
xcodebuild -configuration "${CONFIGURATION}" -target "${PRODUCT_NAME}Framework-Simulator" -sdk iphonesimulator
FRAMEWORK_NAME=${PRODUCT_NAME}
FRAMEWORK_PATH="${BUILD_DIR}/${PRODUCT_NAME}.framework"
FRAMEWORK_VERSION=A
if [ -d "$FRAMEWORK_PATH" ]
then
echo "Cleaning existing framework..."
rm -rf "$FRAMEWORK_PATH"
fi
mkdir -p "$FRAMEWORK_PATH"
mkdir -p "$FRAMEWORK_PATH/Versions"
mkdir -p "$FRAMEWORK_PATH/Versions/$FRAMEWORK_VERSION"
mkdir -p "$FRAMEWORK_PATH/Versions/$FRAMEWORK_VERSION/Resources"
mkdir -p "$FRAMEWORK_PATH/Versions/$FRAMEWORK_VERSION/Headers"
ln -s "$FRAMEWORK_PATH/Versions/$FRAMEWORK_VERSION" "$FRAMEWORK_PATH/Versions/Current"
ln -s "$FRAMEWORK_PATH/Versions/Current/Headers" "$FRAMEWORK_PATH/Headers"
ln -s "$FRAMEWORK_PATH/Versions/Current/Resources" "$FRAMEWORK_PATH/Resources"
ln -s "$FRAMEWORK_PATH/Versions/Current/${PRODUCT_NAME}" "$FRAMEWORK_PATH/${PRODUCT_NAME}"
lipo "${BUILD_DIR}/${CONFIGURATION}-iphoneos/lib${PRODUCT_NAME}Framework-Device.a" \
"${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/lib${PRODUCT_NAME}Framework-Simulator.a" \
-create -output "$FRAMEWORK_PATH/Versions/$FRAMEWORK_VERSION/${PRODUCT_NAME}"
In this customized target, I don’t actually put any other target dependencies, since the dependent builds are handled by this bash script. First two lines use the xcodebuild command line tool to force a proper build, using the correct architectures and targets. The rest of the script creates the necessary framework structure, and the final command uses the lipo tool to combine the two static libraries into a single universal one.
After the Run Script build phase, I added a Copy Files phase to copy the necessary header files into the framework directory created here.
And that’s about it. After this, you can import the generated framework bundle just like you import any other official Apple framework. It might also be possible to add this target as a dependency to your project’s target, and have it automatically rebuild itself as necessary, but I haven’t looked into it yet. Suggestions are always welcome.
© Copyright 2001-2010 Taylan Pince. All rights reserved.