It’s coming to the end of a project and the final deliverable will be a library for iOS. I have seen a lot of people distribute these frameworks as a .zip with the prebuilt .a, separate .h files and resources, but there is a cleaner, friendlier way, that is to package everything into a .framework & .bundle then distribute these as a .dmg.

Nicely packaged framework

We then move from giving customers a .zip with lots of different components that they have to import into their Xcode project, to a single .framework and .bundle that they can just drop right in and use, this also makes it easier for customers in-future should they want to move to an updated version of the library.

Creating a .bundle

A .bundle file is a standard folder with the extension ‘.bundle’ make sure all your resources (images, sounds, etc) are inside one of these. When copying a .bundle into an Xcode iOS app project it is automatically added to the bundle resources, so your libraries’ users won’t have to do any extra work. (You need to make sure that your libraries logic accesses your resources from inside the bundle).

Creating a .framework

Apple don’t seem to have any specific documentation for creating iOS dot frameworks but if you take a look at their OSX documentation you will see that .frameworks are in-fact just folder structures consisting primarily of symbolic links, it is important that all the symbolic links within the framework be relative paths, else when you move or distribute the framework all the paths will be wrong.

This is the minimum structure for a .framework

MyFramework.framework/
    MyFramework  -> Versions/Current/MyFramework
    Resources    -> Versions/Current/Resources
    Versions/
        A/
            MyFramework
            Resources/
                English.lproj/
                    InfoPlist.strings
                Info.plist
        Current  -> A

Here is where I have found iOS to deviate slightly from OSX, as you may notice that it is possible to store our resources directly inside the framework (so why are we creating a separate bundle?), well… in OSX frameworks this is fine but if you try to do this in an iOS framework you will find that any resource inside a framework is not automatically copied into the application bundle resources, this causes issues when our framework requires our resources and we don’t want users of our framework to have to perform additional integration (we just want them to drop the framework into their project and be on their way), this is why we distribute our resources in a separate .bundle

Importing the library

Once your library is stored as a .framework importing it changes slightly, where before you would have included your library like this.

#import "myClass.h"

You now import it like this instead

#import <lukesFramework/myClass.h>

Doing all this inside an Xcode project

So, with all this in mind, I have put together the following script that performs everything mentioned, outputting our library as a .dmg with .framework & .bundle inside, you just need to set your own project’s variables between lines 9 and 31. This script then goes in your Xcode Project’s Targets ‘Build phases’ place it inside a ‘Run Script’.

####
#   Output library project as a .dmg containing .framework and .bundle
####
 
# Sets framework version
FRAMEWORK_VERSION=A
 
###
# Your vars START (input and output directories)
###
 
#Replace with your framework's name
FRAMEWORK_NAME=LukesFramework
 
#Replace with where you would like your .framework temporarily stored before its copied into the .dmg
DOT_FRAMEWORK_OUTPUT_DIR=${SRCROOT}/bin/tmp/output/${FRAMEWORK_NAME}.framework
 
#Replace with where you would like your final .dmg to be output
DMG_OUTPUT_DIR=${SRCROOT}/bin/release/${FRAMEWORK_NAME}.dmg
 
#Replace with your .bundle's location (inside your output derived data)
DOT_BUNDLE_LOCATION=${TARGET_BUILD_DIR}/../LukesFramework/LukesFramework.bundle
 
#Replace with your .a's location (inside your output derived data)
DOT_A_LOCATION=${TARGET_BUILD_DIR}/../LukesFramework/LukesFramework-${CURRENT_PROJECT_VERSION}.a
 
#Replace with the folder location containing your header files (inside your output derived data)
DOT_H_LOCATION=${TARGET_BUILD_DIR}/../LukesFramework
 
###
# your vars END
###
 
# Remove any old versions
if [ -d "${DOT_FRAMEWORK_OUTPUT_DIR}" ]
then
rm -rf "${DOT_FRAMEWORK_OUTPUT_DIR}"
fi
 
# Create dot framework file structure (an empty .framework)
mkdir -p "${DOT_FRAMEWORK_OUTPUT_DIR}"
mkdir -p "${DOT_FRAMEWORK_OUTPUT_DIR}/Versions"
mkdir -p "${DOT_FRAMEWORK_OUTPUT_DIR}/Versions/${FRAMEWORK_VERSION}"
mkdir -p "${DOT_FRAMEWORK_OUTPUT_DIR}/Versions/${FRAMEWORK_VERSION}/Resources"
mkdir -p "${DOT_FRAMEWORK_OUTPUT_DIR}/Versions/${FRAMEWORK_VERSION}/Headers"
 
# Create .framework sym links from relative paths
ln -s "${FRAMEWORK_VERSION}" "${DOT_FRAMEWORK_OUTPUT_DIR}/Versions/Current"
ln -s "Versions/Current/Headers" "${DOT_FRAMEWORK_OUTPUT_DIR}/Headers"
ln -s "Versions/Current/Resources" "${DOT_FRAMEWORK_OUTPUT_DIR}/Resources"
ln -s "Versions/Current/${FRAMEWORK_NAME}" "${DOT_FRAMEWORK_OUTPUT_DIR}/${FRAMEWORK_NAME}"
 
# Copy headers and binary to their .framework locations
cp -R ${DOT_A_LOCATION} "${DOT_FRAMEWORK_OUTPUT_DIR}/Versions/${FRAMEWORK_VERSION}/${FRAMEWORK_NAME}"
find ${DOT_H_LOCATION} -name "*.h" -exec cp {} ${DOT_FRAMEWORK_OUTPUT_DIR}/Versions/${FRAMEWORK_VERSION}/Headers/ \;
 
# Copy bundle to .framework
cp -R ${DOT_BUNDLE_LOCATION} "${DOT_FRAMEWORK_OUTPUT_DIR}/../"
 
# Wrap up into a DMG
hdiutil create -volname ${FRAMEWORK_NAME} -srcfolder "${DOT_FRAMEWORK_OUTPUT_DIR}/../" -ov -format UDZO ${DMG_OUTPUT_DIR}
 
# Clean framework output (we only need the DMG)
#rm -rf "${DOT_FRAMEWORK_OUTPUT_DIR}/../../"
open ${DMG_OUTPUT_DIR}