Tutorials
LOGIN / SIGN UPBECOME A VIDEOSUBSCRIBER
How to Create a Framework for iOS
Sam Davieson April 28, 2014
In theprevious tutorial, you learned how to create a reusable knob control. However, it might not be obvious how to make it easy for other developers to reuse it.
One way to share it would be to provide the source code files directly. However, this isn’t particularly elegant. It exposes implementation details you might not want to share. Additionally, developers might not want to see everything, because they may just want to integrate a portion of your brilliant code into their own codebase.
Another approach is to compile your code into a static library for developers to add to their projects. However, this requires you to distribute public header files in tandem, which is awkward at best.
You need a simpler way to compile your code, and it needs to be easy to share and reuse across multiple projects. What you need is a way to package a static library and its headers in a single component, which you can add to a project and use immediately.
Good thing that’s the focus of this tutorial. What you’ll learn will help you solve this exact problem, by making use of frameworks. OS X has the best support for them because Xcode offers a project template which includes a default build target and can accommodate resource files such as images, sounds and fonts. You can create a framework for iOS, but it’s a touch trickier; if you follow along you’ll learn how to work around the many roadblocks.
As you work through this tutorial, you’ll:
Build a basic static library project in Xcode
Build an app with a dependency on this library project
Discover how to convert the static library project into a fully-fledged framework
Finally, at the end, you’ll see how to package an image file to go along with your framework in a resource bundle
Getting Started
The main purpose of this tutorial is to explain how to create a framework for use in your iOS projects, so unlike other tutorials on the site, there will only be a small amount of Objective-C code used throughout the tutorial, and this is mainly just to demonstrate the concepts we’ll cover.
Start by downloading the source files for theRWKnobControlavailablehere. As you go through the process of creating the first project in the sectionCreating a Static Library Project, you’ll see how to use them.
All of the code, and project files you’ll create whilst making this project are available onGithub, with a separate commit for each build-stage of the tutorial.
What is a Framework?
A framework is a collection of resources; it collects a static library and its header files into a single structure that Xcode can easily incorporate into your projects.
On OS X, it’s possible to create aDynamically Linkedframework. Through dynamic linking, frameworks can be updated transparently without requiring applications to relink to them. At runtime, a single copy of the library’s code is shared among all the processes using it, thus reducing memory usage and improving system performance. As you see, it’s powerful stuff.
On iOS, you cannot add custom frameworks to the system in this manner, so the only dynamically linked frameworks are those that Apple provides.
However, this doesn’t mean frameworks are irrelevant to iOS.Statically Linkedframeworks are still a convenient way to package up a code-base for re-use in different apps.
Since a framework is essentially a one-stop shop for a static library, the first thing you’ll do in this tutorial is learn how to create and use a static library. When the tutorial moves on to building a framework, you’ll know what’s going on, and it won’t seem like smoke and mirrors.
Creating a Static Library Project
Open Xcode and create a new static library project by clickingFile\New\Projectand selectingiOS\Framework and Library\Cocoa Touch Static Library.
Name the productRWUIControlsand save the project to an empty directory.
A static library project is made up of header files and implementation files, which are compiled to make the library itself.
To make life easier for developers that use your library and framework, you’re going to make it so they only need to import a single header file to access all the classes you wish to make public.
When creating the static library project, Xcode addedRWUIControls.handRWUIControls.m. You don’t need the implementation file, so right click onRWUIControls.mand selectdelete; move it to the trash if prompted.
Open upRWUIControls.hand replace the contents with the following:
#import
This imports the umbrella header ofUIKit, which the library itself needs. As you create the different component classes, you’ll add them to this file, which ensures they’ll be accessible to the libraries users.
The project you’re building relies onUIKit, but Xcode’s static library project doesn’t link against it by default. To fix this, addUIKitas a dependency. Select the project in the navigator, and in the central pane, choose theRWUIControlstarget.
Click onBuild Phasesand then expand theLink Binary with Librariessection. Click the+to add a new framework and navigate to findUIKit.framework, before clickingadd.
A static library is of no use unless it’s combined with header files. These compile a manifest of what classes and methods exist within the binary. Some of the classes you create in your library will be publicly accessible, and some will be for internal use only.
Next, you need to add a new phase in the build, which will collect the public header files and put them somewhere accessible to the compiler. Later, you’ll copy these into the framework.
While still looking at theBuild Phasesscreen in Xcode, chooseEditor\Add Build Phase\Add Copy Headers Build Phase.
Note:If you find the option is grayed out, try clicking in the white area directly below the existing build phases to shift the focus, and then try again.
DragRWUIControls.hfrom the navigator to thePublicpart of the panel to place it in the public section underCopy Headers. This ensures the header file is available to anybody who uses your library.
Note:It might seem obvious, but it’s important to note that all header files included in any of your public headers must also be made public. Otherwise, developers will get compiler errors while attempting to use the library. It’s no fun for anybody when Xcode reads the public headers and then cannot read the headers you forgot to make public.
Creating a UI Control
Now that you’ve set up your project, it’s time to add some functionality to the library. Since the point of this tutorial is to describe how to build a framework, not how to build a UI control, you’ll borrow the code from the last tutorial. In the zip file you downloaded earlier you’ll find the directoryRWKnobControl. Drag it from the finder into theRWUIControlsgroup in Xcode.
Choose toCopy items into destination group’s folderand ensure the new files go to theRWUIControlsstatic library target by ticking the appropriate box.
This will add the implementation files to the compilation list and, by default, the header files to theProjectgroup. This means that they will be private.
Note:The three section names can be somewhat confusing until you break them down.Publicis just as you’d expect.Privateheaders are still exposed, which is a little misleading. AndProjectheaders are those specific to your project which are, somewhat ironically, private. Therefore, you’ll find more often than not that you’ll want your headers in either thePublicorProjectgroups.
Now you need to share the main control header,RWKnobControl.h, and there are several ways you can do this. The first is to drag the file from theProjectgroup to thePublicgroup in theCopy Headerspanel.
Alternatively, you might find it easier to change the membership in theTarget Membershippanel when editing the file. This option is a bit more convenient as you can continue to add and develop the library.
Note:As you continue to add new classes to your library, remember to keep their membership up-to-date. Make as few headers public as possible, and ensure the remainder are in theProjectgroup.
The other thing to do with your control’s header file is add it to the library’s main header file,RWUIControls.h. With such a main header file, a developer using your library only needs to include one file like you see below, instead of having to sort out which pieces they need:
#import
Therefore, add the following toRWUIControls.h:
// Knob Control#import
Configuring Build Settings
You are now very close to building this project and creating a static library. However, there are a few settings to configure to make the library as user-friendly as possible.
First, you need to provide a directory name for where you’ll copy public headers. This ensures you can locate the relevant headers when you use the static library.
Click on the project in the Project Navigator, and then select theRWUIControlsstatic library target. Select theBuild Settingstab, then search forpublic header. Double click on thePublic Headers Folder Pathsetting and enter the following in the popup:
include/$(PROJECT_NAME)
You’ll see this directory later.
Now you need to change some other settings, specifically those that remain in the binary library. The compiler gives you the option of removing dead code; code which is never accessed. And you can also remove debug symbols i.e. function names and other debugging related details.
Since you’re creating a framework for others to use, it’s best to disable both and let the user choose what’s best for their project. To do this, using the same search field as before, update the following settings:
Dead Code Stripping– Set this toNO
Strip Debug Symbols During Copy– Set this toNOfor all configurations
Strip Style– Set this toNon-Global Symbols
Build and run. There’s not a lot to see yet, but it’s still good to confirm the project builds successfully and without warnings or errors.
To build, select the target asiOS Deviceand presscmd+Bto perform the build. Once completed, thelibRWUIControls.aproduct in theProductsgroup of the Project Navigator will turn from red to black, signaling that it now exists. Right click onlibRWUIControls.aand selectShow in Finder.
In this directory you’ll see the static library itself,libRWUIControls.a, and the directory you specified for the public headers,include/RWUIControls. Notice the headers you made public can be found in this folder, just as you might expect.
Creating a Dependent Development Project
Developing a UI controls library for iOS is extremely difficult when you can’t actually see what you’re doing, and that seems to be the case now.
Nobody wants you to work blindly, so in this section you’re going to create a new Xcode project that will have a dependency on the library you just created. This will allow you to develop the framework using an example app. Naturally, the code for this app will be kept completely separate from the library itself, as this makes for a much cleaner structure.
Close the static library project by choosingFile/Close Project. Then create a new project usingFile/New/Project. SelectiOS/Application/Single View Application, and call the new projectUIControlDevApp. Set the class prefixes toRWand specify that it should beiPhoneonly. Finally save the project in the same directory you used forRWUIControls.
To add theRWUIControlslibrary as a dependency, dragRWUIControls.xcodeprojfrom the finder into theUIControlDevAppgroup in Xcode.
You can now navigate around the library project, from inside the app’s project. This is perfect because it means that you can edit code inside the library and run the example app to test the changes.
Note:You can’t have the same project open in two different Xcode windows. If you find that you’re unable to navigate around the library project, check that you don’t have it open in another Xcode window.
Rather than recreate the app from the last tutorial, you can simply copy the code. First, selectMain.storyboard,RWViewController.handRWViewController.mand delete them by right clicking and selectingDelete, choosing to move them to the trash. Then copy theDevAppfolder from the zip file you downloaded earlier right into theUIControlDevAppgroup in Xcode.
Now you’re going to add the static library as a build dependency of the example app:
Select theUIControlDevAppproject in the Project Navigator.
Navigate to theBuild Phasestab of theUIControlDevApptarget.
Open theTarget Dependenciespanel and click the+to show the picker.
Find theRWUIControlsstatic library, select and clickAdd. This action means that when building the dev app, Xcode will check to see whether the static library needs rebuilding or not.
In order to link against the static library itself, expand theLink Binary With Librariespanel and again click the+. SelectlibRWUIControls.afrom theWorkspacegroup and clickAdd.
This action makes it so that Xcode will link it against the static library, just as it links against system frameworks likeUIKit.
Build and run to see it in action. If you followed the previous tutorial on building a knob control, you’ll recognize the simple app before your eyes.
The beauty of using nested projects like this is that you can continue to work on the library itself, without ever leaving the example app project, even as you maintain the code in different places. Each time you build the project, you’re also checking that you have the public/project header membership set correctly. The example app won’t build if it’s missing any required headers.
Building a Framework
By now, you’re probably impatiently tapping your toes and wondering where the framework comes into play. Understandable, because so far you’ve done a lot of work and yet there is no framework in sight.
Well, things are about to change, and quickly too. The reason that you’ve not created a framework is because it’s pretty much a static library and a collection of headers – exactly what you’ve built so far.
There are a couple of things that make a framework distinct:
The directory structure. Frameworks have a special directory structure that is recognized by Xcode. You’ll create a build task, which will create this structure for you.
TheSlices. Currently, when you build the library, it’s only for the currently required architecture, i.e. i386, arm7, etc. In order for a framework to be useful, it needs to include builds for all the architectures on which it needs to run. You’ll create a new product which will build the required architectures and place them in the framework.
There is a quite a lot of scripting magic in this section, but we’ll work through it slowly; it’s not nearly as complicated as it appears.
Framework Structure
As mentioned previously, a framework has a special directory structure which looks like this:
Now you’ll add a script to create this during the static library build process. Select theRWUIControlsproject in the Project Navigator, and select theRWUIControlsstatic library target. Choose theBuild Phasestab and add a new script by selectingEditor/Add Build Phase/Add Run Script Build Phase.
This creates a new panel in the build phases section, which allows you to run an arbitraryBashscript at some point during the build. Drag the panel around in the list if you want to change the point at which the script runs in the build process. For the framework project, run the script last, so you can leave it where it’s placed by default.
Rename the script by double clicking on the panel titleRun Scriptand replace it withBuild Framework.
Paste the following Bash script into the script field:
set-eexportFRAMEWORK_LOCN="${BUILT_PRODUCTS_DIR}/${PRODUCT_NAME}.framework"# Create the path to the real Headers diemkdir -p"${FRAMEWORK_LOCN}/Versions/A/Headers"# Create the required symlinks/bin/ln -sfh A"${FRAMEWORK_LOCN}/Versions/Current"/bin/ln -sfh Versions/Current/Headers"${FRAMEWORK_LOCN}/Headers"/bin/ln -sfh"Versions/Current/${PRODUCT_NAME}"\"${FRAMEWORK_LOCN}/${PRODUCT_NAME}"# Copy the public headers into the framework/bin/cp -a"${TARGET_BUILD_DIR}/${PUBLIC_HEADERS_FOLDER_PATH}/"\"${FRAMEWORK_LOCN}/Versions/A/Headers"
This script first creates theRWUIControls.framework/Versions/A/Headersdirectory before then creating the threesymbolic linksrequired for a framework:
Versions/Current=>A
Headers=>Versions/Current/Headers
RWUIControls=>Versions/Current/RWUIControls
Finally, the public header files copy into theVersions/A/Headersdirectory from the public headers path you specified before. The-aargument ensures the modified times don’t change as part of the copy, thereby preventing unnecessary rebuilds.
Now, select theRWUIControlsstatic library scheme and theiOS Devicebuild target, then build usingcmd+B.
Right click on thelibRWUIControls.astatic library in theProductsgroup of theRWUIControlsproject, and once again selectShow In Finder.
Within this build directory you can access theRWUIControls.framework, and confirm the correct directory structure is present and populated:
This is a leap forward on the path of completing your framework, but you’ll notice that there isn’t a static lib in there yet. That’s what you’re going to sort out next.
Multi-Architecture Build
iOS apps need to run on many different architectures:
arm7: Used in the oldest iOS 7-supporting devices
arm7s: As used in iPhone 5 and 5C
arm64: For the 64-bit ARM processor in iPhone 5S
i386: For the 32-bit simulator
x86_64: Used in 64-bit simulator
Every architecture requires a different binary, and when you build an app Xcode will build the correct architecture for whatever you’re currently working with. For instance, if you’ve asked it to run in the simulator, then it’ll only build the i386 version (or x86_64 for 64-bit).
This means that builds are as fast as they can be. When you archive an app or build in release mode, then Xcode will build for all three ARM architectures, thus allowing the app to run on most devices. What about the other builds though?
Naturally, when you build your framework, you’ll want developers to be able to use it for all possible architectures, right? Of course you do since that’ll mean you can earn the respect and admiration of your peers.
Therefore you need to make Xcode build for allfivearchitectures. This process creates a so-calledfatbinary, which contains a slice for each of the architectures. Ah-ha!
Note:This actually highlights another reason to create an example app which has a dependency on the static library: the library only builds for the architecture required by the example app, and will only rebuild if something changes. Why should this excite you? It means the development cycle is as quick as possible.
The framework will be created using a new target in theRWUIControlsproject. To create it, select theRWUIControlsproject in the Project Navigator and then click theAdd Targetbutton shown below the existing targets.
Navigate toiOS/Other/Aggregate, clickNextand name the targetFramework.
Note:Why use anAggregatetarget to build a Framework? Why so indirect? Because Frameworks enjoy better support on OS X, as reflected by the fact that Xcode offers a straightforwardCocoa Frameworkbuild target for OS X apps. To work around this, you’ll use the aggregate build target as a hook for bash scripts that build the magic framework directory structure. Are you starting to see the method to the madness here?
To ensure the static library builds whenever this new framework target is created, you need to add a dependency on the static library target. Select theFrameworktarget in the library project and add a dependency in theBuild Phasestab. Expand theTarget Dependenciespanel, click the+and choose theRWUIControlsstatic library.
The main build part of this target is the multi-platform building, which you’ll perform using a script. As you did before, create a newRun Scriptbuild phase by selecting theBuild Phasestab of theFrameworktarget, and clickingEditor/Add Build Phase/Add Run Script Build Phase.
Change the name of the script by double clicking onRun Script. This time name itMultiPlatform Build.
Paste the following Bash script into the script text box:
set-e# If we're already inside this script then dieif[ -n"$RW_MULTIPLATFORM_BUILD_IN_PROGRESS"];thenexit0fiexportRW_MULTIPLATFORM_BUILD_IN_PROGRESS=1RW_FRAMEWORK_NAME=${PROJECT_NAME}RW_INPUT_STATIC_LIB="lib${PROJECT_NAME}.a"RW_FRAMEWORK_LOCATION="${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework"
set -eensures that if any part of the script should fail then the entire script will fail. This helps you avoid a partially-built framework.
Next, theRW_MULTIPLATFORM_BUILD_IN_PROGRESSvariable determines whether the script was called recursively. If it has, then quit.
Then set up some variables. The framework name will be the same as the project i.e.RWUIControls, and the static lib islibRWUIControls.a.
The next part of the script sets up some functions that the project will use later on. Add the following to the very bottom of the script:
functionbuild_static_library {# Will rebuild the static library as specified# build_static_library sdkxcrun xcodebuild -project"${PROJECT_FILE_PATH}"\ -target"${TARGET_NAME}"\ -configuration"${CONFIGURATION}"\ -sdk"${1}"\ ONLY_ACTIVE_ARCH=NO \ BUILD_DIR="${BUILD_DIR}"\ OBJROOT="${OBJROOT}"\ BUILD_ROOT="${BUILD_ROOT}"\ SYMROOT="${SYMROOT}"$ACTION}functionmake_fat_library {# Will smash 2 static libs together# make_fat_library in1 in2 outxcrun lipo -create"${1}""${2}"-output"${3}"}
build_static_librarytakes anSDKas an argument, for exampleiphoneos7.0, and will build the static lib. Most of the arguments pass directly from the current build job, the difference being thatONLY_ACTIVE_ARCHis set to ensure all architectures build for the current SDK.
make_fat_libraryuseslipoto join two static libraries into one. Its arguments are two input libraries followed by the desired output location. Read more aboutlipohere
The next section of the script determines some more variables which you’ll need in order to use the two methods. You need to know what the other SDK is, for example iphoneos7.0 should go to iphonesimulator7.0 and vice versa, and to locate the build directory for that SDK.
Add the following to the very end of the script:
# 1 - Extract the platform (iphoneos/iphonesimulator) from the SDK nameif[["$SDK_NAME"=~ ([A-Za-z]+) ]];thenRW_SDK_PLATFORM=${BASH_REMATCH[1]}elseecho"Could not find platform name from SDK_NAME:$SDK_NAME"exit1fi# 2 - Extract the version from the SDKif[["$SDK_NAME"=~ ([0-9]+.*$) ]];thenRW_SDK_VERSION=${BASH_REMATCH[1]}elseecho"Could not find sdk version from SDK_NAME:$SDK_NAME"exit1fi# 3 - Determine the other platformif["$RW_SDK_PLATFORM"=="iphoneos"];thenRW_OTHER_PLATFORM=iphonesimulatorelseRW_OTHER_PLATFORM=iphoneosfi# 4 - Find the build directoryif[["$BUILT_PRODUCTS_DIR"=~ (.*)$RW_SDK_PLATFORM$ ]];thenRW_OTHER_BUILT_PRODUCTS_DIR="${BASH_REMATCH[1]}${RW_OTHER_PLATFORM}"elseecho"Could not find other platform build directory."exit1fi
All four of these statements are very similar, they use string comparison and regular expressions to determineRW_OTHER_PLATFORMandRW_OTHER_BUILT_PRODUCTS_DIR.
The fourifstatements in more detail:
SDK_NAMEwill be of the formiphoneos7.0oriphonesimulator6.1. This regex extracts the non-numeric characters at the beginning of this string. Hence, it results iniphoneosoriphonesimulator.
This regex pulls the numeric version number from the end of theSDK_NAMEvariable,7.0or6.1etc.
Here a simple string comparison switchesiphonesimulatorforiphoneosand vice versa.
Take the platform name from the end of the build products directory path and replace it with the other platform. This ensures the build directory for the other platform can be found. This will be critical when joining the two static libraries.
Now you can trigger the build for the other platform, and then join the resulting static libraries.
Add the following to the end of the script:
# Build the other platform.build_static_library"${RW_OTHER_PLATFORM}${RW_SDK_VERSION}"# If we're currently building for iphonesimulator, then need to rebuild# to ensure that we get both i386 and x86_64if["$RW_SDK_PLATFORM"=="iphonesimulator"];thenbuild_static_library"${SDK_NAME}"fi# Join the 2 static libs into 1 and push into the .frameworkmake_fat_library"${BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}"\"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_INPUT_STATIC_LIB}"\"${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}"
First there’s a call to build the other platform using the function you defined beforehand
If you’re currently building for the simulator, then by default Xcode will only build the architecture for that system, e.g.i386orx86_64. In order to build both architectures, this second call tobuild_static_libraryrebuilds with theiphonesimulatorSDK, and ensures that both architectures build.
Finally a call tomake_fat_libraryjoins the static lib in the current build directory with that in the other build directory to make the multi-architecture fat static library. This is placed inside the framework.
The final commands of the script are simple copy commands. Add the following to the end of the script:
# Ensure that the framework is present in both platform's build directoriescp -a"${RW_FRAMEWORK_LOCATION}/Versions/A/${RW_FRAMEWORK_NAME}"\"${RW_OTHER_BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.framework/Versions/A/${RW_FRAMEWORK_NAME}"# Copy the framework to the user's desktopditto"${RW_FRAMEWORK_LOCATION}""${HOME}/Desktop/${RW_FRAMEWORK_NAME}.framework"
The first command ensures that the framework is present in both platform’s build directories.
The second copies the completed framework to the user’s desktop. This is an optional step, but I find that it’s a lot easier to place the framework somewhere that is easily accessible.
Select theFrameworkaggregate scheme, and presscmd+Bto build the framework.
This will build and place aRWUIControls.frameworkon your desktop.
In order to check that the multi-platform build worked, fire up a terminal and navigate to the framework on the desktop, as follows:
$cd~/Desktop/RWUIControls.framework$ RWUIControls.framework xcrun lipo -info RWUIControls
The first command navigates into the framework itself, and the second line uses thelipocommand to get the required information on theRWUIControlsstatic library. This will list the slices that are present in the library.
You can see here that there are five slices:i386,x86_64,arm7,arm7sandarm64, which is exactly what you set out to build. Had you run thelipo -infocommand beforehand, you would have seen a subset of these slices.
How to Use a Framework
Okay, you have a framework, you have libraries and they’re elegant solutions for problems you’ve not yet encountered. But what’s the point of all this?
One of the primary advantages in using a framework is its simplicity in use. Now you’re going to create a simple iOS app that uses theRWUIControls.frameworkthat you’ve just built.
Start by creating a new project in Xcode. ChooseFile/New/Projectand selectiOS/Application/Single View Application. Call your new appImageViewer; set it foriPhoneonly and save it in the same directory you’ve used for the previous two projects. This app will display an image and allow the user to change its rotation using aRWKnobControl.
Look in theImageViewerdirectory of the zip file you downloaded earlier for a sample image. DragsampleImage.jpgfrom the finder into theImageViewergroup in Xcode.
Check theCopy items into destination group’s folderbox, and clickFinishto complete the import.
Importing a framework follows a nearly identical process. DragRWUIControls.frameworkfrom the desktop into theFrameworksgroup in Xcode. Again, ensure that you’ve checked the box beforeCopy items into destination group’s folder.
Open upRWViewController.mand replace the code with the following:
#import"RWViewController.h"#import@interfaceRWViewController()@property(nonatomic,strong)UIImageView*imageView;@property(nonatomic,strong) RWKnobControl *rotationKnob;@end@implementationRWViewController- (void)viewDidLoad{ [superviewDidLoad];// Create UIImageViewCGRectframe =self.view.bounds; frame.size.height *=2/3.0;self.imageView = [[UIImageViewalloc] initWithFrame:CGRectInset(frame,0,20)];self.imageView.image = [UIImageimageNamed:@"sampleImage.jpg"];self.imageView.contentMode =UIViewContentModeScaleAspectFit; [self.view addSubview:self.imageView];// Create RWKnobControlframe.origin.y += frame.size.height; frame.size.height /=2; frame.size.width = frame.size.height;self.rotationKnob = [[RWKnobControl alloc] initWithFrame:CGRectInset(frame,10,10)];CGPointcenter =self.rotationKnob.center; center.x =CGRectGetMidX(self.view.bounds);self.rotationKnob.center = center; [self.view addSubview:self.rotationKnob];// Set up config on RWKnobControlself.rotationKnob.minimumValue = -M_PI_4;self.rotationKnob.maximumValue = M_PI_4; [self.rotationKnob addTarget:selfaction:@selector(rotationAngleChanged:) forControlEvents:UIControlEventValueChanged];}- (void)rotationAngleChanged:(id)sender{self.imageView.transform =CGAffineTransformMakeRotation(self.rotationKnob.value);}- (NSUInteger)supportedInterfaceOrientations{returnUIInterfaceOrientationMaskPortrait;}@end
This is a simple view controller that does the following:
Import the framework’s header with#import .
Set up a couple of private properties to hold theUIImageViewand theRWKnobControl.
Create aUIImageView, and use the sample image that you added to the project a few steps back.
Create aRWKnobControland position it appropriately.
Set some properties on the knob control, including setting the change event handler to be therotationAngleChanged:method.
TherotationAngleChanged:method simply updates thetransformproperty of theUIImageViewso the image rotates as the knob control is moves.
For further details on how to use theRWKnobControlcheck out theprevious tutorial, which explains how to create it.
Build and run. You’ll see a simple app, which as you change the value of the knob control the image rotates.
Using a Bundle for Resources
Did you notice that theRWUIControlsframework only consists of code and headers? For example, you haven’t used any other assets, such as images. This is a basic limitation on iOS, where a framework can only contain header files and a static library.
Now buckle up, this tutorial is about to take off. In this section you’ll learn how to work around this limitation by using a bundle to collect assets, which can then be distributed alongside the framework itself.
You’re going to create a new UI control to be part of theRWUIControlslibrary; a ribbon control. This will place an image of a ribbon on the top right hand corner of aUIView.
Creating a bundle
The resources will be added to a bundle, which takes the form of an additional target on theRWUIControlsproject.
Open theUIControlDevAppproject, and select theRWUIControlssub-project. Click theAdd Targetbutton, then navigate toOS X/Framework and Library/Bundle. Call the bundleRWUIControlsResourcesand selectCore Foundationfrom the framework selection box.
There are a couple of build settings to configure since you’re building a bundle for use in iOS as opposed to the default of OSX. Select theRWUIControlsResourcestarget and then theBuild Settingstab. Search forbase sdk, select theBase SDKline and pressdelete. This will switch from OSX to iOS.
You also need to change the product name toRWUIControls. Search forproduct nameand double-click to edit. Replace${TARGET_NAME}withRWUIControls.
By default, images which have two resolutions can produce some interesting results; for instance when you include a retina @2x version. They’ll combine into a multi-resolution TIFF, and that’s not a good thing. Search forhidpiand change theCOMBINE_HIDPI_IMAGESsetting toNO.
Now you’ll make sure that when you build the framework, the bundle will also build and add the framework as a dependency to the aggregate target. Select theFrameworktarget, and then theBuild Phasestab. Expand theTarget Dependenciespanel, click the+, and then select theRWUIControlsResourcestarget to add it as a dependency.
Now, within theFrameworktarget’sBuild Phases, open theMultiPlatform Buildpanel, and add the following to the end of the script:
# Copy the resources bundle to the user's desktopditto"${BUILT_PRODUCTS_DIR}/${RW_FRAMEWORK_NAME}.bundle"\"${HOME}/Desktop/${RW_FRAMEWORK_NAME}.bundle"
This command will copy the built bundle to the user’s desktop. Build the framework scheme now so you can see the bundle appear on the desktop.
Importing the Bundle
In order to develop against this new bundle, you’ll need to be able to use it in the example app. This means you must add it as both a dependency, and an object to copy across to the app.
In the Project Navigator, select theUIControlDevAppproject, then click on theUIControlDevApptarget. Expand theProductsgroup of theRWUIControlsproject and dragRWUIControls.bundleto theCopy Bundle Resourcespanel inside theBuild Phasestab.
In theTarget Dependenciespanel, click the+to add a new dependency, and then selectRWUIControlsResources.
Building a Ribbon View
That’s all the setup required. Drag theRWRibbondirectory from inside the zip file you downloaded earlier into theRWUIControlsgroup within theRWUIControlsproject.
ChooseCopy the items into the destination group’s folder, making sure they are part of theRWUIControlsstatic lib target by ticking the appropriate box.
An important part of the source code is how you reference images. If you take a look ataddRibbonViewinside theRWRibbonView.mfile you’ll see the relevant line:
UIImage*image = [UIImageimageNamed:@"RWUIControls.bundle/RWRibbon"];
The bundle behaves just like a directory, so it’s really simple to reference an image inside a bundle.
To add the images to bundle, choose them in turn, and then, in the right hand panel, select that they should belong to theRWUIControlsResourcestarget.
Remember the discussion about making sure the framework is accessible to the public? Well, now you need to export theRWRibbon.hheader file, select the file, then choosePublicfrom the drop down menu in theTarget Membershippanel.
Finally, you need to add the header to the framework’s header file. OpenRWUIControls.hand add the following lines:
// RWRibbon#import
Add the Ribbon to the Example App
OpenRWViewController.min theUIControlDevAppproject, and add the following instance variable between the curly braces in the@interfacesection:
RWRibbonView *_ribbonView;
To create a ribbon view, add the following at the end ofviewDidLoad:
// Creates a sample ribbon view_ribbonView = [[RWRibbonView alloc] initWithFrame:self.ribbonViewContainer.bounds];[self.ribbonViewContainer addSubview:_ribbonView];// Need to check that it actually works :)UIView*sampleView = [[UIViewalloc] initWithFrame:_ribbonView.bounds];sampleView.backgroundColor = [UIColorlightGrayColor];[_ribbonView addSubview:sampleView];
Build and run theUIControlDevAppscheme and you’ll see the new ribbon control at the bottom of the app:
Using the Bundle in ImageViewer
The last thing to share with you is how to use this new bundle inside another app, theImageViewerapp you created earlier.
To start, make sure your framework and bundle are up to date. Select theFrameworkscheme and then presscmd+Bto build it.
Open up theImageViewerproject, find theRWUIControls.frameworkitem inside theFrameworksgroup and delete it, choosingMove to Trashif you’re prompted. Then drag theRWUIControls.frameworkfrom your desktop to theFrameworksgroup. This is necessary because the framework is much different than it was when you first imported it.
Note:If Xcode refuses to let you add the framework, then it might not have properly moved it to the trash. If this is the case then delete the framework from theImageViewerdirectory in Finder and retry.
To import the bundle, simply drag it from the desktop to theImageViewergroup. Choose toCopy items into destination group’s folderand ensure that it’s added to theImageViewertarget by ticking the necessary box.
You’re going to add the ribbon to the image, which rotates, so there are a few simple changes to make to the code inRWViewController.m.
Open it up and change the type of theimageViewproperty fromUIImageViewtoRWRibbonView:
@property(nonatomic,strong) RWRibbonView *imageView;
Replace the first part of theviewDidLoadmethod which was responsible for creating and configuring theUIImageView, with the following:
[superviewDidLoad];// Create UIImageViewCGRectframe =self.view.bounds;frame.size.height *=2/3.0;self.imageView = [[RWRibbonView alloc] initWithFrame:CGRectInset(frame,0,20)];UIImageView*iv = [[UIImageViewalloc] initWithFrame:self.imageView.bounds];iv.image = [UIImageimageNamed:@"sampleImage.jpg"];iv.contentMode =UIViewContentModeScaleAspectFit;[self.imageView addSubview:iv];[self.view addSubview:self.imageView];
Build and run the app. You’ll see you’re now using both theRWKnobControland theRWRibbonViewfrom theRWUIControlsframework.
Where To Go From Here?
Want to learn even faster? Save time with ourvideo courses
In this tutorial, you’ve learned everything you need to know about building a framework for use in your iOS projects, including the best way to develop frameworks and how to use bundles to share assets.
Do you have a favored functionality that you use in lots of different apps? The concepts you’ve learned can make your life easier by creating a library you can access over and over again. A framework offers an elegant way to procure a library of code, and gives you the flexibility to access whatever you need for your next series of awesome apps.
The source code for the completed project is available onGithub, with a commit for each build step, or as a downloadablezip file.
Sam is a strange mashup of developer, writer and trainer. By day you'll find him recording videos for Razeware, writing tutorials, attending conferences and generally being a good guy. By night he's likely to be out entertaining people, armed with his trombone and killer dance moves.
He'd like it very much if you were to follow him on twitter at@iwantmyrealname, seek him out assammydon GitHub, or check his personal siteiwantmyreal.name.
Save time.
Learn more with our video courses.
GET STARTED!
raywenderlich.com Weekly