Ever wanted to share your iOS code with others without having them copy your code and headers and hope it compiles properly? Well you can easily do that with frameworks. I had found several guides on how to do this but they were either out-of-date or incomplete. While the iOS App Store doesn't allow you include dynamic frameworks, you can create a relocatable static framework which functions very simliar with one key difference (more on this later). One more thing we can do to simplify distribution is to create a universal framework so that they same file works on the iOS device (armv7
) and in the simulator (i386
).
Let's get started by creating a new project in Xcode.
We're going to create a Mac OS X Bundle (and change it into an iOS bundle).
First we're going to replace the contents of the prefix header (Xcode puts #import <Cocoa/Cocoa.h>
in it regardless of what framework you chose to base the project on).
#import <UIKit/UIKit.h>
Now we are going to change some Build Settings
so we build an iOS Framework.
Group | Setting | Value |
---|---|---|
Architectures |
Base SDK |
Latest iOS |
Architectures |
Standard (armv7) |
|
Build Active Architecture Only |
NO |
|
Linking |
Dead Code Striping |
NO |
Link With Standard Libraries |
NO |
|
Mach-O Type |
Relocatable Object File |
|
Other Linker Flags |
-ObjC |
|
Packaging |
Wrapper Extension |
framework |
User-Defined |
GCC_GENERATE_DEBUGGING_SYMBOLS |
NO |
Now for the Build Phases
. Start with deleting the CoreFoundation.framework
entry from Link Binary with Framework
. Now click on Add Build Phase
.
Now click Add Copy Headers
and then click Add Build Phase
again and click Add Run Script
.
Now rename the run script phase to Build Universal Binary
. And this is the script we'll be using.
[[ -n "$BUILD_FOR_UNIVERSAL" ]] && exit
if [[ "$PLATFORM_NAME" == "iphoneos" ]]; then
platform="iphonesimulator"
else
platform="iphoneos"
fi
xcodebuild -configuration "$CONFIGURATION" -target "$TARGETNAME" -sdk "$platform" BUILD_FOR_UNIVERSAL="yes" OBJROOT="$OBJROOT" SYMROOT="$SYMROOT"
other_platform_build_dir="$SYMROOT/$CONFIGURATION-$platform"
universal_build_dir="$SYMROOT/$CONFIGURATION-iphoneuniversal"
mkdir -p "$universal_build_dir"
cp -rf "$TARGET_BUILD_DIR/$FULL_PRODUCT_NAME" "$universal_build_dir"
rm -f "$universal_build_dir/$EXECUTABLE_PATH"
lipo -create "$TARGET_BUILD_DIR/$EXECUTABLE_PATH" "$other_platform_build_dir/$EXECUTABLE_PATH" -output "$universal_build_dir/$EXECUTABLE_PATH"
Now before trying to build we need to add some code (otherwise the script will fail since no binary will be produced). Let's create an Objective C Class
.
And in following with Apple standards, we'll create a header that includes everything from our framework.
And we'll insert our imports into this header.
#import <Saozap/SaozapWidget.h>
Now back to the Build Phases
and under Copy Headers
move the two headers from Project
into Public
.
Now we're ready to build. Once the build has completed you should now have a framework sitting in the build products directory under Debug-iphoneuniversal
. Use this from the terminal if you want to copy the framework to your desktop (or anywhere else).
cp -r ~/Library/Developer/Xcode/DerivedData/Saozap-*/Build/Products/Debug-iphoneuniversal/*.framework ~/Desktop
In a iOS application project, add the framework and then wherever you need it import the header we created and start using our class.
#import <Saozap/Saozap.h>
SaozapWidget * w = [[SaozapWidget alloc] init];
NSLog(@"%@", w);
Now you may have noticed we removed the framework(s) from Link Binary with Frameworks
and might be wondering how we can still use NSObject
. That's because by building a relocatable object file we are no longer linking with anything and instead defer the entire process to the consumer project. This means if you use any frameworks you must get the developer consuming your framework to add the framework(s) to their Link Binary with Frameworks
step otherwise their build will fail with missing symbols.
One last piece of advice: if you are including code from other libraries in your code (like SBJson or CocoaAsyncSocket) I highly recommend prefixing all of the library's code so that anyone that uses your framework won't have conflicts if they use the same library.