Friday, April 8, 2011

Creating a Static Framework in Xcode 4

For iOS there is no template in Xcode to build a framework for iOS.  In order to get around this, you can create a Cocoa Static Library project and modify it so that you can develop and compile a static iOS Framework.  Using the post at "http://www.cocoanetics.com/2010/05/making-your-own-iphone-frameworks-in-xcode/" as a basis for writing this tutorial I was able to get it working and used it in a another project by following the steps outlined in this post.  This is the first library I've ever tried to create outside of Java so please feel free to chime in with any comments or corrections.

Create the Xcode project for the framework(skip if you already have an existing project):
- Open Xcode and create a new project (File->New->Project)
- Under the "Framework & Library" section under iOS select "Cocoa Touch Static Library".
- Under "Product Name" enter the name you want to call your framework.
- Click "Next" and "Create".

Add a Bundle target:
- Click the "Add Target" button at the bottom of the Xcode window.
- Select the "Bundle" template under Mac OS X.
- Give it the name of your framework.
- Click "Finish".

Set the Build Settings:
- Change the following properties to the values indicated below...
- Base SDK (replace the value with the appropriate iOS SDK version for your framework).
- Build Active Architecture Only = No
- Architecture and Valid Architectures = $(ARCHS_STANDARD_32_BIT)
- Mac OS X Deployment Target = Compiler Default
- Targeted Device Family (choose the appropriate iOS device i.e. iPhone/iPad)
- Dead Code Stripping = No
- Link With Standard Libraries = No
- Mach-O Type = Relocatable Object File
- Wrapper Extension = framework
- Generate Debug Symbols = No

Modify Info.plist file:
- Under your frameworks "Supporting Files" folder should be a file named <framework>-Info.plist.  Modify the "Bundle OS Type code to "FMWK"

Modify the Prefix.pch file:
- The generated Prefix.pch file under "Supporting Files" contains a reference to the Cocoa framework header file.  Remove that reference from the file.

Remove unnecessary frameworks from your project:
- Under the "Frameworks" folder delete frameworks such as Cocoa.framework and AppKit.framework that your project does not rely on.

Add frameworks required by your framework:
- If the project was created from a Cocoa template, the Cocoa framework is setup by default and not UIKIt which is required for iOS projects.
- Drag and drop any required frameworks such as UIKit under the "Frameworks" folder in your project pane.  In the dialog box that comes up uncheck "Copy items into destination group's folder (if needed)".
- UIKit should be in a folder similar to the following - /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/System/Library/Frameworks/UIKit.framework

Add and develop your framework code files.

Add a new build phase for "Copy Headers":
- Select your framework target.
- Click the "Build Phases" tab.
- Click the "Add Build Phase" button and select "Add Copy Headers" from the pull-down menu.
- Under "Compile Sources" your source files should already be there, if not add them.
- Under "Link Binary With Libraries" remove any frameworks that might be present.
- Under "Copy Headers" add your .h  files to the section with the appropriate scope.  They can be "Public", "Private", or "Project".  You can drag them from the "Project" section to the "Public" or "Private" sections or from the project file pane.

Create Target for Universal Framework
-------------------------------------------------
- Create an "Aggregate" target named "<Framework Name> Universal Framework".
- Update the "Build Settings" by changing "SDKROOT" to "iphoneos".
- Click "Add Build Phase" and then "Add Run Script".  Copy the script below into the script area in "Run Script".  Be sure to update the "FMK_NAME" property to the name of your framework.

# Sets the target folders and the final framework product.
FMK_NAME=MyFramework
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}"

Run the "MyApp Universal Framework" target to build the framework.  The framework bundle should now be under the "Products" folder of your project.  To use it in another project simply drag and drop it from your framework project to a folder in your project.

Hope this was helpful!

20 comments:

  1. thx man, this helped me really out ...

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

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

    ReplyDelete
  4. I have errors related to Appkit(175).... when I tried to Build.....

    Add a new build phase for "Copy Headers":
    - Select your framework target.
    - Click the "Build Phases" tab.***** @ this point

    ReplyDelete
  5. This guide is great. Thank you very much !

    Additional note for whom it may concern:
    In case any of you want to compile with an SDK other than the default (e.g. in case you have a beta SDK as the default) or for any other reason don't need both targets to be built every time (mockup for simulator always stays the same and the iphoneos is the one that needs to be actually updated) and etc - you can build each Framework individually (as explained in the "Static Framework" section above), comment out both xcodebuild's and rm -r "${WRK_DIR}", and just create a "build" folder and copy the content of "Products" from your DerivedData folder (or wherever your individual frameworks landed) into it.

    ReplyDelete
  6. Kevin - did you ever have this error when running this framework in the simulator:
    ld: warning: ignoring file /Users/westonmcbride/Library/Developer/Xcode/DerivedData/LCFrameworkTest4-etivnhrcrfbtxrgwxjcsmsepljur/Build/Products/Debug-iphoneos/LCBDSDK.framework/LCBDSDK, file was built for armv7 which is not the architecture being linked (i386)
    Undefined symbols for architecture i386:

    It seems that the framework is lacking support for i386 (the simulator), as it runs fine on the device. Have you encountered this problem?

    ReplyDelete
    Replies
    1. Did you perform the steps under "
      Create Target for Universal Framework". That builds the framework for both the arm and i386 architectures and allows the framework to run on both.

      Delete
    2. Yes, I did. Turns out, I had a file name misspelled in the script, and thus the framework was only being compiled for armv7. That was my problem!
      Thanks! Great tutorial. It was very helpful.

      Delete
  7. i follow all step but i am not getting sucess my .framework file is showing red and i am ot able to open finder

    ReplyDelete
  8. Last step not clear "Create Target for Universal Framework"

    ReplyDelete
  9. Thank you for your guide to with upgrade information

    iOS app development course

    ReplyDelete
  10. The content is well-researched and well-written, making it easy to follow and understand. Great job, and keep up the fantastic work in providing such informative and helpful content to your readers! Overall, I was impressed by the quality of this blog post and highly recommend it to anyone interested in working with a web development company in India.

    ReplyDelete