Creating iOS Framework with Xcode4

I have been searching for a way to create a static library for a while. I figured out how to do so but then had the issue of creating a static library that was only one file and worked with both iOS devices (arm6 and arm7 architecutres) and the iOS Simulator (i386 architecture). I finally got that to work but then ran into another problem. I was using the MapKit library which is unavailable in the i386 version. I could not figure out a way to make all of these work together. Finally, I figured out how to create a framework instead of a library. There are rumors that Apple does not approve apps with custom frameworks but that is not true. I have deployed an app to the app store, that was approved, with a custom framework. With that being said, let's get into how to build our own custom framework that will be re-usable, can use any other framework, and will work with both iOS devices and the iOS Simulator.


Step 1: Create a new iOS Application
From here we will be able to write a test application in addition to writing the framework. I am naming my framework project "example".
* If you are building against iOS 5.0 but want to deploy to an earlier version of iOS you will want to open the project's "Build Settings" and set Objective-C Automatic Reference Counting = No

Step 2: Create a New Bundle Target
A bundle is used here because it will allow us to store xib files, as well as source code and any other resources we need. My new bundle target will be name "exampleBundle".

Step 3: Configure Bundle Target
Here is a list of the changes that need to be made:
  • Base SDK = Latest iOS
  • Architectures = Standard
  • Build Active Architecture = No
  • Valid Architectures = $(ARCHS_STANDARD_32_BIT)
    • It is important to copy this string exactly as is
  • Dead Code Stripping = No
    • This is because Apple does not want you to have a dynamic library. This enables that "static" part of the framework
  • Link With Standard Libraries = No
    • Any libraries required by your framework will have to be imported into the project that uses your framework
    • ie- If your framework requires MapKit (like mine did) then the app that uses your framework will need to add MapKit to the imported frameworks
  • Match-O Type = Relocatable Object File
  • Wrapper Extension = framework
Step 4: Create Aggregate Target
I have named my aggregate target "exampleAggregate". The aggregrate is the final target and when it is built it will turn the code into a framework.

Step 5: Configure Aggregate Target
  • Go to the "Build Phases" tab of the newly created aggregate target.
  • Add a new "Run Script" as a build phase.
  • Set the script to be the following where FMK_NAME is the name of your new framework:
# Sets the target folders and the final framework product.
FMK_NAME=Example
FMK_VERSION=A

# Install dir will be the final output to the framework.
# The following line create it in the root folder of the current project.
INSTALL_DIR=${SRCROOT}/Products/${FMK_NAME}.framework

# Working dir will be deleted after the framework creation.
WRK_DIR=build
DEVICE_DIR=${WRK_DIR}/Release-iphoneos/${FMK_NAME}.framework
SIMULATOR_DIR=${WRK_DIR}/Release-iphonesimulator/${FMK_NAME}.framework

# Building both architectures.
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphoneos
xcodebuild -configuration "Release" -target "${FMK_NAME}" -sdk iphonesimulator

# Cleaning the oldest.
if [ -d "${INSTALL_DIR}" ]
then
rm -rf "${INSTALL_DIR}"
fi

# Creates and renews the final product folder.
mkdir -p "${INSTALL_DIR}"
mkdir -p "${INSTALL_DIR}/Versions"
mkdir -p "${INSTALL_DIR}/Versions/${FMK_VERSION}"
mkdir -p "${INSTALL_DIR}/Versions/${FMK_VERSION}/Resources"
mkdir -p "${INSTALL_DIR}/Versions/${FMK_VERSION}/Headers"

# Creates the internal links.
# It MUST uses relative path, otherwise will not work when the folder is copied/moved.
ln -s "${FMK_VERSION}" "${INSTALL_DIR}/Versions/Current"
ln -s "Versions/Current/Headers" "${INSTALL_DIR}/Headers"
ln -s "Versions/Current/Resources" "${INSTALL_DIR}/Resources"
ln -s "Versions/Current/${FMK_NAME}" "${INSTALL_DIR}/${FMK_NAME}"

# Copies the headers and resources files to the final product folder.
cp -R "${DEVICE_DIR}/Headers/" "${INSTALL_DIR}/Versions/${FMK_VERSION}/Headers/"
cp -R "${DEVICE_DIR}/" "${INSTALL_DIR}/Versions/${FMK_VERSION}/Resources/"

# Removes the binary and header from the resources folder.
rm -r "${INSTALL_DIR}/Versions/${FMK_VERSION}/Resources/Headers" "${INSTALL_DIR}/Versions/${FMK_VERSION}/Resources/${FMK_NAME}"

# Uses the Lipo Tool to merge both binary files (i386 + armv6/armv7) into one Universal final product.
lipo -create "${DEVICE_DIR}/${FMK_NAME}" "${SIMULATOR_DIR}/${FMK_NAME}" -output "${INSTALL_DIR}/Versions/${FMK_VERSION}/${FMK_NAME}"

rm -r "${WRK_DIR}"
 Step 6: Create source code to be used in the framework.
The source code should be sure to hide anything you don't want another developer to see. Remember, the developer has to have the header files in order to use your framework so don't put anything unnecessary in the header files. I am going to create two classes. One will be named "publicClass" and will useable by other developers that use this framework. The second will be named "privateClass" and will not be useable or visible to other developers. The second class will only be used internal to this framework.

Also, when you created the Bundle target a new group was created in the Project Navigator panel with the same name as the bundle (in my case "exampleBundle"). All of your framework code should be stored under this group. This is not necessary but will be beneficial as you try to keep everything organized and will help you be sure not to miss any source code or header files when you are dealing with them in the next step.


Step 7: Complete Bundle Target Setup
  • Now that we have the source code complete, go back to the target bundle we created and navigate to the "Build Phases" tab.
  • Expand the "Compile Sources" section. Here we will add all *.m files that need to be compiled. Basically, any of the code that was written in the previous step (not including header files) should be added here. In my case, publicClass.m and privateClass.m are being added.
  • If your framework is using any other frameworks (such as MapKit) add it to "Link Binary with Libraries"
  • If you have any additional resources, add them to the "Copy Bundle Resources" section.
  • Add "Copy Headers" as a new Build Phase
  • Add header files to the "Copy Headers" section
    • Any header files that should be public should be added and placed under "Public"
    • Any header files that should be private should be added and placed under "Private"
*Note: I also created a header file named "exampleHeader.h". This header file simply imports all of the public header files in my framework. Now, when another developer uses this framework he/she can just use
#import <exampleBundle/exampleHeader.h>
instead of importing every header file. This may be overkill for this example framework but it will be much appreciated by the developer if you build a complex framework that has several header files.
    Step 8: Fix <bundle name>-Prefix.pch
     This file should have a line "#import <Cocoa/Cocoa.h>" and that line must be removed

    Step 9: Build the Aggregate Target
    We have completed all necessary steps and your framework is ready to build. If you build the aggregate target you will find a new folder under <project root directory>\Products and it will be given the name <bundle name>.framework. This is your new framework! It contains the header files that developers will need and the compiled source code.

    Your next step will be to create a project that uses this framework. When you do so you will want to import the framework. Do this by navigating to the "Build Settings" tab under your app target then click the "+" under "Link Binaries with Libraries" just as you would with any standard framework. The only difference is that your framework will not be in the list. You will have to click "Add Other..." then navigate to the <bundle name>.framework folder. Select that folder and click "Open". 

    Your new framework has been added and you or the other developer can begin developing!  



    If you follow these steps and have issues please let me know so that I can update this and have completely accurate instructions for creating a framework. My source code for this project is hosted on git hub. Find it here.

    16 comments:

    1. Are Resources working for you with this method? I'm not seeing the Resources section under the included Framework in XCode when including the project, either in my own tests or using your sample. The Resources symlink is clearly in the .framework bundle, but the directory is not showing up in the XCode framework browse/open area, nor are included resources available to bundle into the final target. Did I miss some step?

      ReplyDelete
    2. I have not created frameworks that include resources yet. I just did a quick test and if I add the resource to "Copy Bundle Resources" it will copy itself into the framework that is created. However, I am not able to use that resource in another project. I will look into this some more and hopefully get back to you with a solution.

      ReplyDelete
    3. @natbro, sorry for the delay. I had some issues getting it working but I finally figured it out. This will work with any resource but it is a bit of a pain. Here is a quick example using myImage.png.

      1) Open a terminal and "cd" to the directory that holds myImage.png
      2) Run the command "xxd -i myImage.png myImage.png.h" -- This will turn the image into a .h header file
      3) Include the new myImage.png.h file in the framework before building and make it a public header
      4) After adding the framework to the project using it, import myImage.png.h
      5) Load the data into a NSData object with [NSData dataWithBytes:myImage_png length:myImage_png_len];

      This will give you an NSData object that can be used to create a UIImage (or whatever you need if you aren't working with images). I tried this with .xib files as well.

      Let me know if this works for you.

      ReplyDelete
    4. I tried the example code out in Xcode 4.2 but won't work

      Framework gets built correctly but when its included in the project for use i get this error

      Command /Developer/Platforms/iPhoneSimulator.platform/Developer/usr/bin/clang failed with exit code 1

      ReplyDelete
    5. Hi men, great tutorial the only mistake I found is, where it says:

      "Do this by navigating to the 'Build Settings' tab under your app target then click"

      I rather found that in my xcode version (4.2) under 'Build Phases' so it should say:
      "Do this by navigating to the 'Build Phases' tab under your app target then click"

      Excellent tutorial! You saved my day :)

      ReplyDelete
    6. Thank you very much for this code. I had to build my bundle target (not my aggregate). So that might be something people might try if they can't get it working.

      Again, invaluable tutorial.

      ReplyDelete
    7. Does this work as of late Xcode versions?

      ReplyDelete
      Replies
      1. It works although I just tried the simplest example above. Of course the comment of Nacho is important. And I had a mistake - make sure FMK_NAME is the exact same as the bundle name, in the example above it should be "exampleBundle".

        Delete
    8. This comment has been removed by the author.

      ReplyDelete
    9. This comment has been removed by the author.

      ReplyDelete
    10. This works perfectly for me and I am able to organize lot of my common codes into frameworks. Saves so much work for the projects that use it.

      However, building Aggregate takes a hell lot of time compared to building just the bundle.
      Do you have any tips for speeding this up?

      ReplyDelete
      Replies
      1. I have the same problem, is there any resolution for this?

        Thank you

        Delete
    11. Using XCode 4.6 I get these errors:
      cp: build/Release-iphoneos/SesameSDKFramework.framework/Headers/: No such file or directory
      rm: /Users/admin/Work/SesameSDK/SesameSDK/Products/SesameSDKFramework.framework/Versions/A/Resources/Headers: No such file or directory
      rm: /Users/admin/Work/SesameSDK/SesameSDK/Products/SesameSDKFramework.framework/Versions/A/Resources/SesameSDKFramework: No such file or directory

      Am I doing something wrong?
      10x for a great tutorial!

      ReplyDelete
    12. Hi,
      Good tutorial...But I tried to access .xib files and it is not working for me..Can u help me ?

      ReplyDelete
    13. how to add a library file inside the framework. i want to integrate restKit to the created framework.

      ReplyDelete