How
to Create a Framework for iOS 8 on Xcode 6
The internet
is full with documentation about how to build an iOS framework. However, when
we began our internal development here at Insert, we still had to overcome some
non-trivial challenges before we were able to get our SDK working the way we
wanted.
Additionally, in
xcode 6, Apple dramatically changed the way developers create and build
frameworks, so a lot of the documentation you’ll find on the Internet
is not up-to-date.
In
this post we’ll show you, step-by-step, how to create and build a framework for
iOS 8.
We
will also address important challenges such as:
- How
to combine Swift and Objective-C code within the same SDK?
- How
to build the framework for the all the relevant architectures (armv7,
armv7s, arm64, i386), etc. If you just need the solution for this one
you’ll need to add a new Build Phase to your project and use the “run
script” at the bottom of this post.
In
our example we will use a Manager to enable\disable the Framework and a
CustomView Class which will contain (surprise) a custom UIView. In this example
we want to show you how how resources such as xib and png files can be
integrated within the framework.
Let’s begin with Step #1
1) Creating a project from scratch
Since xcode
6 there is a built-in option to create a dynamic framework project. This is the
option you should choose if you need to create a framework project from
scratch:
What’s the difference between a “Static Library” and a
“Framework”?
A
“Static Library” is mainly compiled code which takes the form of a (dot).a
file, for example InsertLib.a. It’s possible to export static
libraries by sharing the a file together with some public header files
that contain the public Classes and Methods which the clients of the
static lib can use.
“Cocoa
Touch Framework” is essentially a bundle which contains a “dynamic library”, H
files and resources. The idea of “dynamic library” – or in different words
“dynamic linking” – is to have one copy of the shared code in a single device
(think of CoreLocation.Framework for example) that is shared by all the apps
that link to it. This dynamic concept promises the device will improve
system performance by minimizing the framework’s memory usage.
2) Add the Manager Class files with the following code:
InsertManager.h
#import
<Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface InsertManager :
NSObject
+(instancetype) sharedManager;
-(void) startManager;
-(void) stopManager;
-(void)
showMessageInViewController:(UIViewController *)viewController;
-(BOOL) isManagerRunning;
@end
InsertManager.m
#import
"InsertManager.h"
#import "CustomView.h"
@interface InsertManager()
@property (nonatomic) BOOL
isEnabled;
@end
@implementation InsertManager
+ (instancetype) sharedManager {
static InsertManager *sharedManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[[self class] alloc]
init];
});
return sharedManager;
}
- (void) startManager {
NSLog(@"Manager is running");
_isEnabled = YES;
}
- (void) stopManager {
NSLog(@"Manager stopped..");
_isEnabled = NO;
}
-(BOOL) isManagerRunning {
return _isEnabled;
}
-(void)
showMessageInViewController:(UIViewController *)viewController {
if (_isEnabled) {
NSBundle* frameworkBundle = [NSBundle
bundleForClass:[self class]];
CustomView *csView = [[frameworkBundle
loadNibNamed:@"CustomView" owner:self options:nil] firstObject];
csView.frame = CGRectMake(0, 0,
[[UIScreen mainScreen] bounds].size.width, [[UIScreen mainScreen]
bounds].size.height);
[viewController.view addSubview:csView];
}
}
@end
3) Add the CustomView code:
CustomView.h
#import
<Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface CustomView : UIView
@property (weak, nonatomic)
IBOutlet UIImageView *imageView;
@property (weak, nonatomic)
IBOutlet UIButton *closeButton;
@end
CustomView.m
#import "CustomView.h"
@implementation CustomView
-
(IBAction)closeButtonClicked:(id)sender {
[self removeFromSuperview];
}
@end
CustomView.xib
– Download from Github to see how it is configured.
Newsroom.png
– we use this file as a background image to demonstrate how resources such as
PNG files can be used in a framework and exported to an app.
4) When
you create a new “Cocoa Touch Framework” project on xcode, a default
H file will be generated automatically with name “<Framework-name>”.h –
Make sure you add to this file all the public H files, i.e. H files that
contains public methods to be used by the framework client. In our example add
the following:
#import <UIKit/UIKit.h>
//! Project version number for
InsertSampleFramework.
FOUNDATION_EXPORT double
InsertSampleFrameworkVersionNumber;
//! Project version string for
InsertSampleFramework.
FOUNDATION_EXPORT const unsigned
char InsertSampleFrameworkVersionString[];
// In this header, you should
import all the public headers of your framework using statements like
#import
<InsertSampleFramework/InsertManager.h>
5) In
xcode click on the Target and go to the “Build Phase” section, add the public H
files to “public” in the “Headers” build phase:
6) Now
just build the Framework and you’ll have the product ready. However we’ll be
able to use the framework only in the context of an app. We will use in
this guide Apple Sample Project called “Tabster”. The complete code of
this project can be downloaded from iOS Developer Library
– go to this page and search for “Tabster” and click on it. In Tabster
page, look for the button “Download Sample Code”, download the code and
open the project in xcode.
Let’s
see how we can take our framework to work with an external app…
Integrate
the framework in external app for Development
1) Open
the Tabster project and run the app “as is”, see that it’s working as expected.
2) Copy
the root folder “InsertSampleFramework” into the Root folder of Tabster
3) Now
let’s drag our Framework project inside as a dependency (note that you’ll have
the close the xcode window of the Framework project first since this xcodeproj
can be opened only in one xcode window).
4) Add the
Framework as a dependency in the Build Phases
5) Add the
framework to “Link Binary with Libraries”. If the framework has Swift code
you’ll also need to add the framework in the “General” tab under “Embedded
Binaries”
6) Let’s
run and see how it works just as a sanity check (notice that we haven’t
integrated the framework in code yet).
7) Now
let’s use our amazing framework in the Tabster app. It’s a fairly simple
app: open the Storyboard and add a label (Insert Framework
Enable\Disable), UISegmentControl and a UIButton to ThreeViewController
8) In
Tabster go to ThreeViewController.m and add:
#import
<InsertSampleFramework/InsertManager.h>
9) Now
let’s add the following IBActions to ThreeViewController.m:
#pragma mark - IBAction
-
(IBAction)segmentValueChanged:(id)sender {
UISegmentedControl *sc = (UISegmentedControl *)sender;
NSInteger selectedSegment = sc.selectedSegmentIndex;
if (selectedSegment == 1) {
[[InsertManager sharedManager]
startManager];
}
else if (selectedSegment == 0) {
[[InsertManager sharedManager]
stopManager];
}
}
-
(IBAction)showCustomView:(id)sender {
[[InsertManager sharedManager] showMessageInViewController:self];
}
10) Run the
app, go to tab “Three” and play with the on\off segment control.
Verify that the button “Show Custom View” will work only when the manager
is running.
Integrate
our framework in external app for Distribution
Most
companies and individual developers who develop a Framework for iOS, eventually
want to their framework to be used in Distribution mode. The most important
step you’ll have to do at this stage is to build the framework for all possible
architectures (armv7, armv7s, arm64, x86, etc). We’ll achieve this by adding a
“Build Phase” to run a script that will build the framework 3 times – one for
each family of architectures (iOS Simulator, older devices (armv7, armv7s), new
devices (arm64).
Click on the Framework target and add a “New Run Script Phase”:
Here
is the line you should copy & paste to the build phase:
Some
developers prefer to write the script directly in this box, I prefer to have
the script in a separate .sh file so I can track it on Git and track changes
when they are needed in the future.
The
actual script is in ios-build-framework-script.sh :
set -e
set +u
# Avoid recursively calling this
script.
if [[ $SF_MASTER_SCRIPT_RUNNING ]]
then
exit 0
fi
set -u
export SF_MASTER_SCRIPT_RUNNING=1
# Constants
SF_TARGET_NAME=${PROJECT_NAME}
UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal
# Take build target
if [[ "$SDK_NAME" =~
([A-Za-z]+) ]]
then
SF_SDK_PLATFORM=${BASH_REMATCH[1]}
else
echo "Could not find platform
name from SDK_NAME: $SDK_NAME"
exit 1
fi
if [[ "$SF_SDK_PLATFORM"
= "iphoneos" ]]
then
echo "Please choose iPhone
simulator as the build target."
exit 1
fi
IPHONE_DEVICE_BUILD_DIR=${BUILD_DIR}/${CONFIGURATION}-iphoneos
# Build the other (non-simulator)
platform
xcodebuild -project
"${PROJECT_FILE_PATH}" -target "${TARGET_NAME}"
-configuration "${CONFIGURATION}" -sdk iphoneos BUILD_DIR="${BUILD_DIR}"
OBJROOT="${OBJROOT}" BUILD_ROOT="${BUILD_ROOT}"
CONFIGURATION_BUILD_DIR="${IPHONE_DEVICE_BUILD_DIR}/arm64"
SYMROOT="${SYMROOT}" ARCHS='arm64' VALID_ARCHS='arm64' $ACTION
xcodebuild -project
"${PROJECT_FILE_PATH}" -target "${TARGET_NAME}"
-configuration "${CONFIGURATION}" -sdk iphoneos
BUILD_DIR="${BUILD_DIR}" OBJROOT="${OBJROOT}"
BUILD_ROOT="${BUILD_ROOT}"
CONFIGURATION_BUILD_DIR="${IPHONE_DEVICE_BUILD_DIR}/armv7"
SYMROOT="${SYMROOT}" ARCHS='armv7 armv7s' VALID_ARCHS='armv7 armv7s'
$ACTION
# Copy the framework structure to
the universal folder (clean it first)
rm -rf
"${UNIVERSAL_OUTPUTFOLDER}"
mkdir -p
"${UNIVERSAL_OUTPUTFOLDER}"
cp -R
"${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework"
"${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework"
# Smash them together to combine
all architectures
lipo -create
"${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}"
"${BUILD_DIR}/${CONFIGURATION}-iphoneos/arm64/${PROJECT_NAME}.framework/${PROJECT_NAME}"
"${BUILD_DIR}/${CONFIGURATION}-iphoneos/armv7/${PROJECT_NAME}.framework/${PROJECT_NAME}"
-output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}"
1)
Make sure you build for iOS Simulator when you want to build for Distribution –
the build script detects that and automatically build for the other platforms.
2)
After running “Build” you need to choose the Framework under the
Distribution-universal directory.
3)
Integrate the Framework into the Xcode project that use it and you are set.
For more information, please visit : www.programmingyan.com
No comments:
Post a Comment