How to create a Framework for iOS (三)使用 Framework 与 Bundles

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 the RWUIControls.framework that you’ve just built.

Start by creating a new project in Xcode. Choose File/New/Project and select iOS/Application/Single View Application. Call your new app ImageViewer; set it for iPhone only 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 a RWKnobControl.

Look in the ImageViewer directory of the zip file you downloaded earlier for a sample image. Drag sampleImage.jpg from the finder into the ImageViewer group in Xcode.

ios_framework_drag_sample_image_into_xcode

Check the Copy items into destination group’s folder box, and click Finish to complete the import.
Importing a framework follows a nearly identical process. Drag RWUIControls.framework from the desktop into the Frameworks group in Xcode. Again, ensure that you’ve checked the box before Copy items into destination group’s folder.

ios_framework_import_framework

Open up RWViewController.m and replace the code with the following:

#import "RWViewController.h"
#import <RWUIControls/RWUIControls.h>

@interface RWViewController ()
@property (nonatomic, strong) UIImageView *imageView;
@property (nonatomic, strong) RWKnobControl *rotationKnob;
@end

@implementation RWViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Create UIImageView
    CGRect frame = self.view.bounds;
    frame.size.height *= 2/3.0;
    self.imageView = [[UIImageView alloc] initWithFrame:CGRectInset(frame, 0, 20)];
    self.imageView.image = [UIImage imageNamed:@"sampleImage.jpg"];
    self.imageView.contentMode = UIViewContentModeScaleAspectFit;
    [self.view addSubview:self.imageView];

    // Create RWKnobControl
    frame.origin.y += frame.size.height;
    frame.size.height /= 2;
    frame.size.width  = frame.size.height;
    self.rotationKnob = [[RWKnobControl alloc] initWithFrame:CGRectInset(frame, 10, 10)];
    CGPoint center = self.rotationKnob.center;
    center.x = CGRectGetMidX(self.view.bounds);
    self.rotationKnob.center = center;
    [self.view addSubview:self.rotationKnob];

    // Set up config on RWKnobControl
    self.rotationKnob.minimumValue = -M_PI_4;
    self.rotationKnob.maximumValue = M_PI_4;
    [self.rotationKnob addTarget:self
                          action:@selector(rotationAngleChanged:)
                forControlEvents:UIControlEventValueChanged];
}

- (void)rotationAngleChanged:(id)sender
{
    self.imageView.transform = CGAffineTransformMakeRotation(self.rotationKnob.value);
}

- (NSUInteger)supportedInterfaceOrientations
{
    return UIInterfaceOrientationMaskPortrait;
}

@end

This is a simple view controller that does the following:

  • Import the framework’s header with #import <RWUIControls/RWUIControls.h>.
  • Set up a couple of private properties to hold the UIImageView and the RWKnobControl.
  • Create a UIImageView, and use the sample image that you added to the project a few steps back.
  • Create a RWKnobControl and position it appropriately.
  • Set some properties on the knob control, including setting the change event handler to be the rotationAngleChanged: method.
  • The rotationAngleChanged: method simply updates the transform property of the UIImageView so the image rotates as the knob control is moves.

For further details on how to use the RWKnobControl check out the previous 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.

ios_framework_image_viewer_rotating

Using a Bundle for Resources

Did you notice that the RWUIControls framework 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 the RWUIControls library; a ribbon control. This will place an image of a ribbon on the top right hand corner of a UIView.

Creating a bundle

The resources will be added to a bundle, which takes the form of an additional target on the RWUIControls project.

Open the UIControlDevApp project, and select the RWUIControls sub-project. Click the Add Target button, then navigate to OS X/Framework and Library/Bundle. Call the bundle RWUIControlsResources and select Core Foundation from the framework selection box.

ios_framework_add_bundle_target

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 the RWUIControlsResources target and then the Build Settings tab. Search for base sdk, select the Base SDK line and press delete. This will switch from OSX to iOS.

ios_framework_bundle_set_base_sdk

You also need to change the product name to RWUIControls. Search for product name and double-click to edit. Replace ${TARGET_NAME} with RWUIControls.

ios_framework_bundle_set_product_name

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 for hidpi and change the COMBINE_HIDPI_IMAGES setting to NO.

ios_framework_bundle_hidpi_images

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 the Framework target, and then the Build Phases tab. Expand the Target Dependencies panel, click the +, and then select the RWUIControlsResources target to add it as a dependency.

ios_framework_add_bundle_as_framework_dependency

Now, within the Framework target’s Build Phases, open the MultiPlatform Build panel, and add the following to the end of the script:

# Copy the resources bundle to the user's desktop
ditto "${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.

ios_framework_bundle_on_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 the UIControlDevApp project, then click on the UIControlDevApp target. Expand the Products group of the RWUIControls project and drag RWUIControls.bundle to the Copy Bundle Resources panel inside the Build Phases tab.

In the Target Dependencies panel, click the + to add a new dependency, and then select RWUIControlsResources.

ios_framework_add_bundle_for_dev_project

Building a Ribbon View

That’s all the setup required. Drag the RWRibbon directory from inside the zip file you downloaded earlier into the RWUIControls group within the RWUIControls project.

ios_framework_drag_rwribbon

Choose Copy the items into the destination group’s folder, making sure they are part of the RWUIControls static lib target by ticking the appropriate box.

ios_framework_rwribbon_membership

An important part of the source code is how you reference images. If you take a look at addRibbonView inside the RWRibbonView.m file you’ll see the relevant line:

UIImage *image = [UIImage imageNamed:@"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 theRWUIControlsResources target.

ios_framework_rwribbon_image_membership

Remember the discussion about making sure the framework is accessible to the public? Well, now you need to export the RWRibbon.h header file, select the file, then choose Public from the drop down menu in the Target Membership panel.

ios_framework_rwribbon_header_membership

Finally, you need to add the header to the framework’s header file. Open RWUIControls.h and add the following lines:

// RWRibbon
#import <RWUIControls/RWRibbonView.h>

Add the Ribbon to the Example App

Open RWViewController.m in the UIControlDevApp project, and add the following instance variable between the curly braces in the @interface section:

RWRibbonView  *_ribbonView;  

To create a ribbon view, add the following at the end of viewDidLoad:

/ 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 = [[UIView alloc] initWithFrame:_ribbonView.bounds];  
sampleView.backgroundColor = [UIColor lightGrayColor];  
[_ribbonView addSubview:sampleView];

Build and run the UIControlDevApp scheme and you’ll see the new ribbon control at the bottom of the app:

ios_framework_dev_app_with_ribbon

Using the Bundle in ImageViewer

The last thing to share with you is how to use this new bundle inside another app, the ImageViewer app you created earlier.

To start, make sure your framework and bundle are up to date. Select the Framework scheme and then press cmd+B to build it.

Open up the ImageViewer project, find the RWUIControls.framework item inside the Frameworks group and delete it, choosing Move to Trash if you’re prompted. Then drag the RWUIControls.framework from your desktop to the Frameworks group. This is necessary because the framework is much different than it was when you first imported it.

ios_framework_delete_framework

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 the ImageViewer directory in Finder and retry.

To import the bundle, simply drag it from the desktop to the ImageViewer group. Choose to Copy items into destination group’s folder and ensure that it’s added to the ImageViewer target by ticking the necessary box.

ios_framework_import_bundle

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 the imageView property from UIImageView to RWRibbonView:

@property (nonatomic, strong) RWRibbonView *imageView;

Replace the first part of the viewDidLoad method which was responsible for creating and configuring the UIImageView, with the following:

[super viewDidLoad];
// Create UIImageView
CGRect frame = self.view.bounds;  
frame.size.height *= 2/3.0;  
self.imageView = [[RWRibbonView alloc] initWithFrame:CGRectInset(frame, 0, 20)];  
UIImageView *iv = [[UIImageView alloc] initWithFrame:self.imageView.bounds];  
iv.image = [UIImage imageNamed:@"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 the RWKnobControl and the RWRibbonView from theRWUIControls framework.

ios_framework_image_viewer_with_ribbon