Frame Up - Learn iOS 8 App Development, Second Edition (2014)

Learn iOS 8 App Development, Second Edition (2014)

Chapter 21. Frame Up

Now that you’ve got these mad Swift skills, let’s put some of them to use. This final chapter is going to wade a little into the deep end of iOS development. You’ll develop an extension—an exciting new feature of iOS 8—and to do that you’ll create a framework.

A framework is a self-contained bundle of code, resources, and interfaces. A framework can contain compiled code (classes and functions), resources (images, sounds, storyboards), and an API (the list of classes, properties, and functions you’re allowed to use). In short, it contains everything you need to use a collection of code that someone else has written. If you’ve used DLLs or static libraries on other platforms, or possibly Java .jar files, you have the general idea.

Frameworks are used extensively in iOS. In fact, iOS is (almost) nothing more than a massive collection of frameworks. A framework makes it easy to bundle together everything needed for a complete programming solution. Take the MapKit framework, for example. It contains the code for classes such as MKMapView, it contains resources like the standard push-pin map annotation image, and it contains all of the interfaces that let you use those in your app when you write import MapKit.

Starting with iOS 8, Apple now allows app developers to create frameworks and bundle them in their apps. This opens opportunities to include a framework that provides your app with a complete solution. You can now drop in whole frameworks from other developers, share solutions between your own apps, or even create and publish a framework for other developers to use.

Even more exciting, iOS 8 introduces new system-level features that you can extend by packaging your code in a special framework. In this chapter, you will do the following:

· Learn about extensions

· Create an Action extension

· Create a framework

· Share a framework

· Put some of your new closure knowledge to work

You’re going to revisit the Shorty app from Chapter 3. Wow, that seems like a long time ago. It’s great to have an app that you can browse the Web and then shorten a URL. But what if you’re surfing the Web in Safari or Chrome or the new Middle Earth browser (that translates everything into Elvish) and you suddenly need to shorten a URL? What do you do then? You read the rest of this chapter, of course.

Extensions

iOS 8 adds extensions to the app mix. An extension is a framework, bundled in your app, that provides a service to enhance the iOS experience outside your app. Currently, there are six kinds of extensions in iOS.

· Share: Provide a new way to share information.

· Action: Do something novel with the user’s information.

· Today: Add live, interactive notifications to the lock screen.

· Photo Editing: Add new photo filters and effects to other apps.

· Document Provider: Give other apps access to your app’s documents.

· Custom Keyboard: Replace the standard on-screen keyboard with your own.

You’re going to create the simplest extension possible, a non-UI Action extension. I did say we were going to be wading into the deep end, not jumping in head first. But don’t worry, there’s still plenty of work to do. Before you get started, there are a few things about extensions you should understand.

Let’s look first at how extensions work. Let’s say the user is browsing through their pictures in their Photos app when they find a particularly good picture of Gandalf. They want to share that with the Shire, so they tap the activity button in the interface. Up pops the usual suspects: Facebook, Twitter, Sina Weibo, iMessage, e-mail, and possibly others.

It’s the “others” category that’s now wide open in iOS 8. The gentlefolk of the Shire use their own social network, ShireShare. iOS 8 discovers a Share extension framework that’s been bundled inside the ShireShare app. The extension is loaded and added to the list, right alongside Twitter and the rest. If the user taps the ShireShare button, the code in the extension takes over and presents its sharing interface. This could be its own interface, or it could be built from a standardized interface provided by Cocoa Touch. The extension is then responsible for uploading the picture, along with a message or any other relevant data, to the ShireShare site. The process running the extension is then terminated, and the user resumes browsing their photos.

It’s important to note that at no time was the ShireShare app running or was it launched. This entire process occurs while the Photos app is the active app. The Sharing extension in the ShireShare app just provides a new service, one independent of its container app. Other types of extensions provide different experiences.

Creating an Extension

Extensions are packaged in a framework. You build this framework as part of your project, and the compiled framework is included in your app like any other resource. Here are the salient points of interest:

· The containing app is your app, the one that contains the extension framework as a resource.

· The host app is the app that wants to use your extension.

· The code in your extension is executed in a separate process, isolated from both the containing app and the host app.

· The life cycle of an extension is independent of both the containing and host apps and is usually quite brief.

· Each extension defines one, and only one, kind of service. If you want your app to provide three different services, your app must include three separate extensions.

· Your app can use the code in a framework directly. A framework cannot use the code in your app, although it can use code from another framework.

We might as well get started. There’s no user interface design for this project because the Action extension doesn’t have a UI. Find the Shorty project from Chapter 3. From the File menu, choose the New image Target command. In the target template picker, select the iOS Application Extensions group and pick the Action extension, as shown in Figure 21-1.

image

Figure 21-1. Picking the Action extension template

Give the new extension a Product Name of ShortenAction, as shown in Figure 21-2. Make sure the language is Swift. Set Action Type to No User Interface. Xcode automatically offers to make this new target part of your existing app and to embed the framework in your Shorty application. This is exactly what you want.

image

Figure 21-2. Setting the Action extension details

When you click the Finish button, you’re likely to see another dialog that says Xcode is preparing a run scheme for your new extension. So far in this book, you’ve been using the default runs schemes to debug your app on various simulators and devices. To debug an extension, however, you can’t just run the extension. Remember that extensions run in a special process and at the behest of another (host) app. Xcode conveniently creates a run scheme that launches another app and prepares Xcode to intercept the code in your extension when that host app loads it. Setting this kind of thing up is tricky, so definitely click the Activate button when Xcode asks.

Tip The run scheme that Xcode creates for your extension will ask you what host app you want to use every time you run it. If you’re using the same host app over and over again, you can edit the scheme so it runs a particular host app instead, so you don’t have to pick one every time.

A target produces something in your project. Up until now, you’ve really been using only one target, your app target. There are other targets, like unit test targets, but this book hasn’t done anything with those. Now your project has two significant targets: an app target and an extension target.

Xcode just did quite a bit of work, so before you go too far, let’s review what just happened. When you added an extension target, Xcode did the following:

· Xcode created a new target in your project named ShortenAction. That target produces an extension framework named ShortenAction.appex.

· Xcode added the ShortenAction target as a dependent target of the Shorty target. A dependent target is a target that must be built before the parent target can be built. This ensures that when you build your Shorty app target, the ShortenAction target also gets built.

· The ShortenAction.appex bundle (the ShortenAction target’s product) was added to the list of binaries that get embedded in your Shorty app. An embedded binary is a library or framework that contains executable code that gets copied into your finished app as a resource.

· ShortenAction.appex was also added to the list of embedded application extensions. This prepares the embedded framework so it will be recognized as an extension. Without this, iOS won’t see your extension.

Connecting Your Extension

All extensions work through an extension point. An extension point is a service provided by iOS where extensions will broaden the user’s choices. A Share extension adds to the possible sharing options when the user taps the activity button. A Keyboard extension adds to the number of keyboard layouts the user can select from, and so on.

An extension connects to one extension point, specified in its Info.plist file. Select the Info.plist file of your ShortenAction target, as shown in Figure 21-3. You’ll find the extension’s Info.plist file inside the Supporting Files group of the ShortenAction group. This is the property list for your extension, and it contains important information about what kind of extension it is, determines under what circumstances it should be available, and even determines its display name. Let’s tackle the easy one first.

image

Figure 21-3. Action extension Info.plist

Edit the value of the Bundle display name and change it to Copy Short URL, as shown in Figure 21-3. This is the action’s name as it will appear to the user. The next question is “When and how will it appear?” Those answers are in the NSExtension dictionary. Expand that entry to take a look.

The first value used in NSExtension is the NSExtensionPointIdentifier. This tells iOS what kind of extension point your extension works with. The com.apple.service identifier means your extension is a UI-less Action. The rest of the properties are largely dependent on what kind of extension this is.

For a UI-less Action extension, iOS needs to know the class of the object that provides your service. That’s stored in the NSExtensionPrincipleClass property, and it’s set to ActionRequestHandler. iOS will create an ActionRequestHandler object and then call on it to do the work.

If this was an extension with a user interface, the process is slightly different. The NSExtensionMainStoryboard property would have the name of your extension’s main storyboard file. This, in turn, contains the scene with the UIViewController that implements your user interface.

That explains the “how,” and now you have to describe the “when.” Some extensions are context sensitive. Let’s say you’ve created a Share extension that shares a movie. It would be inappropriate to show that Share extension to the user if they were trying to share a map location or their contact information. You describe the context in which your extension should be used in the NSExtensionActivationRule property. There are two ways to do this. The first, and easiest, is to store a dictionary of predefined rules, also shown in Figure 21-3.

Each value in the dictionary defines a simple rule in which your extension should be used. For example, the NSExtensionActivationSupportsImageWithMaxCount rule is the maximum number of images your extension can work with. If it was set to 3, the user could use your extension to share up to three images at a time. In the template it’s set to 0, which means your extension doesn’t handle images. You can leave these rules set to 0 or NO in your Info.plist, or you can delete them. It doesn’t matter because it means the same thing.

The rule you want is the NSExtensionActivationSupportsWebURLWithMaxCount, and it should be set to 1. This rule activates your extension when the user wants to do something with a single URL.

Caution Don’t confuse NSExtensionActivationSupportsWebURLWithMaxCount with NSExtensionActivationSupportsWebPageWithMaxCount. The former activates your extension with a URL. The later activates your extension with the contents of a web page—which may, or may not, have a URL.

The other way to determine the “when” is to write an activation rule predicate statement and store it as a String property in NSExtensionActivationRule. If iOS see a string value, it executes the predicate statement to determine whether your extension should be activated. A predicate statement is a database-like query that can make all kinds of complex decisions. For example, it could determine that the URL the user wants to share has already been shortened. You could deactivate your extension under that circumstance since there’s no point in shortening an already shortened URL. See the Predicate Programming Guide for a thorough explanation of the predicate language.

With both the “how” and “when” out of the way, let’s move on to actually doing something.

Running your Action

Like all good templates, Xcode creates an Action extension that will run right out of the box. Select the ShortenAction scheme (the one Xcode created for you) and choose a target, as shown in Figure 21-4.

image

Figure 21-4. Selecting the extension scheme

Run your extension. The run scheme for an extension needs a host app. The host app is the app that will request your extension. Xcode prompts for a host app to launch, as shown in Figure 21-5. For this test, choose Safari.

image

Figure 21-5. Choosing the host app

Safari launches in the simulator, as shown on the left in Figure 21-6. I choose Safari because it’s an app that shares URLs, and that’s the context your extension works in. Go to any page in Safari and tap the action button. The activity picker appears, also shown in the middle of Figure 21-6. The picker shows both sharing activities and actions. Swipe the actions, and you’ll find a new Copy Short URL action, conspicuously missing an icon, as shown on the right in Figure 21-6.

image

Figure 21-6. The ShortenAction extension appears in the activity picker

Congratulations! Your extension just appeared in Safari. Now if it would only do something useful.

Lights, Code, Action

Stop the running app in Xcode and return to your project. It’s time to get familiar with the inner workings of the action.

Select the ActionRequestHandler.swift file and take a look at the code. This is where your action happens. For a UI-less Action extension, you define a class that will do the work. It must adopt the NSExtensionRequestHandling protocol and implement abeginRequestWithExtensionContext(_:) function.

Tip The class that implements your extension can be anything you want. But if you change it, don’t forget to update the NSExtensionPrincipalClass property in the Info.plist to match.

If you look at the code from the template, you’ll see it’s doing quite a lot. Actually, it’s not doing much of anything at all; it’s just a demonstration of how you’d do something sophisticated. The template code shows how to run a JavaScript probe against the user’s web page. This JavaScript can extract all kinds of information from the page, which your extension could then use. Imagine, for example, an extension in a travel app; it could extract reservation information from a web page and automatically adds it to the user’s itinerary. If you need to do something like this, take a good look at the template code.

Your extension doesn’t need any of that. You need to gut the ActionRequestHandling class and replace it with the code to shorten a URL. Unfortunately, that code is currently baked into the ViewController class in your Shorty app.

Every Extension Is an Island

As I mentioned earlier, an extension is a framework. A framework provides classes, code, and resources to a consumer, typically an app. It cannot consume any classes, code, or resources from the app that contains or hosts it. A framework is an island; if it didn’t bring it, it doesn’t have it.

The next step, clearly, is to share the code that performs the URL shortening with the extension so both can use it. There are several ways to accomplish this. Here are a couple of obvious ones:

· Hack up a second version of the same code for the extension.

· Reorganize the code so the URL shortening logic is encapsulated in a portable class.

I’m not even going to talk about the first one.

Drag a new Swift file into your app project and name it Squeezer.swift. You’ll consolidate the URL shortening logic in this class. When adding the new file, make sure you’re adding the file to just the Shorty target, as shown in Figure 21-7.

image

Figure 21-7. Adding Squeezer to the app target

You want to reorganize the URL shortening logic so it can perform the same function for both your app and your extension. Your app needs the URL shortening to occur asynchronously because an app should never block its main event loop. The extension doesn’t strictly require this, but it won’t hurt. What both need is an easy way to get the shortened URL back when the conversion is done because the app and the extension will want to do different things with the result. Here, then, is the plan:

· The Squeezer class will have a shortenURL(longURL:, completion:) method.

· This function will take the long URL, build the shortening request, start the Internet transaction to convert it, and return immediately.

· When the results are obtained, it will call the completion block supplied by the caller.

· The completion block gets two parameters, the shortened URL or the error that occurred.

This mirrors how the app does it now but repackages the code so it can be easily reused in other ways. The following is the finished Squeezer.swift code:

import Foundation

let GoDaddyAccountKey = "0123456789abcdef0123456789abcdef"

public class Squeezer {
public class func shortenURL( # longURL: NSURL,image
completion: ((NSURL?, NSError?) -> Void)) {
if let absoluteURL = longURL.absoluteString {
if let encodedURL = image
absoluteURL.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding) {
let urlString = image
"http://api.x.co/Squeeze.svc/text/\(GoDaddyAccountKey)?url=\(encodedURL)"
let request = NSURLRequest(URL: NSURL(string: urlString)!)
NSURLConnection.sendAsynchronousRequest( request,
queue: NSOperationQueue.mainQueue() ) {
(_, shortURLData, error) in
if error == nil && shortURLData != nil {
var shortURL: NSURL?
if let data = shortURLData {
if let short = NSString(data: data,
encoding: NSUTF8StringEncoding ) {
shortURL = NSURL(string: short)
}
}
completion(shortURL, nil)
} else {
completion(nil, error)
}
}
}
}
}
}

Just as in Chapter 3, you’ll need to replace the GoDaddyAccountKey value with your X.co account key.

This new version does everything the ViewController class in Shorty was doing. In Shorty, you created an NSURLRequest delegate and data source, started the NSURLRequest asynchronously, and then waited for delegate calls to collect the response data and complete the transaction.

This is such a common task that iOS 8 added a new convenience method that does all of that for you in a single call: sendAsynchronousRequest(_:,queue:,completion:). Instead of defining delegate methods, you supply a closure that’s executed once the transaction is complete.

The completion block in shortenURL(longURL:,completion:) examines the results to determine whether it was successful. If it was, it decodes the data of the response and turns it into an NSURL object—just like the app used to do. It then calls the completion closure passed to your shortenURL(longURL:,completion:) function, providing it with the translated NSURL object or an error.

You can now greatly simplify the code in ViewController. Select the ViewController.swift file and make the following changes:

1. Remove the NSURLConnectionDelegate and NSURLConnectionDataDelegate protocols from the class declaration. You don’t need these anymore; Squeezer handles all of the communications now.

2. Delete the GoDaddyAccountKey, shortenURLConnection, and shortURLData properties.

3. Delete the connection(_:,didFailWithError:), connection(_:,didReceiveData:), and connectionDidFinishLoading(_:) functions.

4. Rewrite the shortenURL(_:) action method as follows:

@IBAction func shortenURL( AnyObject ) {
if let toShorten = webView.request?.URL {
Squeezer.shortenURL(longURL: toShorten) { (shortURL, _) in
if let urlString = shortURL?.absoluteString {
self.shortLabel.title = urlString
self.clipboardButton.enabled = true
} else {
self.shortLabel.title = "failed"
self.clipboardButton.enabled = false
self.shortenButton.enabled = true
}
}
shortenButton.enabled = false
}
}

Squeezer is now doing all of the communications and URL shortening work. Both the success and failure cases are being handled by the closure.

Take a moment to run your Shorty app—by changing the target scheme back to Shorty—and double-check that it still works. It’s always a good idea to retest your app after reorganizing your code.

Obtaining the User’s Data

With the shortening logic encapsulated in Squeezer, you can now reuse it to implement your extension. Select the ActionRequestHandler.swift file. Keep the extensionContext property and replace the beginRequestWithExtensionContext(_:) function with the following:

funcbeginRequestWithExtensionContext(context: NSExtensionContext) {
self.extensionContext = context
for item in context.inputItems as [NSExtensionItem] {
if let attachments = item.attachments as? [NSItemProvider] {
for itemProvider in attachments {
let urlType = String(kUTTypeURL)
if itemProvider.hasItemConformingToTypeIdentifier(urlType) {
itemProvider.loadItemForTypeIdentifier(urlType, options: nil) {
(item,error) in
dispatch_async(dispatch_get_main_queue()) {
if let url = item as? NSURL {
self.actionWithURL(url)
}
}
}
}
}
}
}
}

When the user taps your extension, this function gets called. The first thing it does is save the NSExtensionContext in a property. Your code will need it again later.

The NSExtensionContext object contains all of information your extension needs to work with the user’s selection. First, it examines the inputItems, an array of NSExtensionItem objects. Each item is a type of data, each of which contains one or more attachments. You need to examine the items and then extract the attachments that your extension wants to work with.

NSEXTENSIONCONTEXT WITH A UI

For non-UI extensions that need an NSExtensionContext, like this one, the extension context is passed as a parameter to beginRequestWithExtensionContext(_:). If your extension has a UI, however, you get the extension context through a different route.

Your interface will be in a storyboard. The extension point loads your storyboard. In the process, the UIViewController that implements your interface is created. Before your view controller is presented, iOS 8 sets its extensionContext property with the active extension context. (This is a new property, added in iOS 8).

When your view controller code begins execution, you get the information about the items the user wants to work with from your view controller’s extensionContext property. Everything after that is the same.

Let’s say the user selected two images and a movie to share. The context will contain two items: one for the images and a second for the movie. The image item will contain two attachments, and the movie item will contain one attachment.

Back in your code, the two loops rattle through all of the attachments looking for any that are URLs. It does this using the hasItemConformingToTypeIdentifier(_:) function. You pass this function a universal type identifier (UTI) for a URL—or whatever type you’re interested in—and it returns true if the attachment is, or can be converted to, that type.

Once you’ve found an attachment you want to work with, the next step is to extract it. Attachments can represent large amounts of data—an entire movie, for example. The attachment content is obtained asynchronously using theloadItemForTypeIdentifier(_:,options:,completionHandler:) function. This function procures the contents of the attachment, possibly converting it to the desired type, and then executes a closure when it’s ready. Your closure calls the actionWithURL(_:)function, which you haven’t written yet.

Before you do, delete any other methods that were included by the template. You can also delete the Action.js file from the project; you won’t be using it.

Reusing Squeezer

Add the code for the actionWithURL(_:) function to the ActionRequestHandler class, as follows. This is the function that implements your action.

func actionWithURL(url: NSURL) {
Squeezer.shortenURL(longURL: url) { (shortURL, error) in
if error == nil {
if let url = shortURL {
UIPasteboard.generalPasteboard().URL = url
}
self.extensionContext?.completeRequestReturningItems( nil,
completionHandler: nil)
} else {
self.extensionContext?.cancelRequestWithError(error!)
}
}
}

The code reused Squeezer to shorten the URL in the background. When it’s done, it executes the closure. The closure checks to see whether the conversion was successful. If it was, it puts the shortened URL onto the clipboard.

Now here’s the important part. Once your extension has done whatever it’s going to do, you must call either the completeRequestReturningItems(_:,completionHandler:) or cancelRequestWithError(_:) function of the NSExtensionContext object you received in the beginning. These calls tell the extension point that your action is complete or that the user (or some unseen force) canceled it.

Timing here is really important. Extensions have to get in, do their thing, and get out. If your extension does something direct and quick, it should do that and then call one of those functions. This Action extension falls into that category. Shortening a URL shouldn’t take more than a second or two, at most. Accordingly, your code waits until the URL is converted and then signals that it’s done.

On the other hand, if your extension needs to do something time-consuming, such as upload an entire movie to a server, you should start a background upload task and then immediately signal that your extension has completed. The upload will continue in the background while your user gets on with her life. The App Extension Programming Guide has a section entitled “Performing Uploads and Downloads” that explains how to set that up.

That’s all well and good, but that’s not your biggest problem. Xcode it telling you that there’s no class named Squeezer, and it’s right; there’s no class named Squeezer in your extension (framework). Remember, a framework can’t access the code or resources in its container app.

You return, once again, to the problem of how to share the URL shortening code with both your app and your extension. One of the simplest is not to share but to duplicate. Select the Squeezer.swift file. Use the file inspector and change the target membership of the file so it is in both the Shorty and ShortenAction targets, as shown in Figure 21-8.

image

Figure 21-8. Compiling Squeezer twice

By making the Squeezer.swift file a member of both targets, the file gets compiled twice: once when the ShortenAction framework is built and again when the Shorty app is built. The net effect is that both your app and your extension can use the same class.

Let’s take it out for a drive. Select the ShortenAction scheme again (see Figure 21-4) and test it in Safari. Browse to a page, tap the action button, and then tap your Copy Short URL action. If everything is working properly, the URL of the page will be shortened and copied to the clipboard. To verify this, set a breakpoint in the actionWithURL(_:) function, as shown in Figure 21-9.

image

Figure 21-9. Examining actionWithURL(_:) in action

When the action executes, it calls actionWithURL(_:) to convert the URL. Stepping through the closure code, as shown in Figure 21-9, you can see the successfully shortened URL in the variable pane and step through the code that puts it on the clipboard.

This is pretty amazing. With very little work, you’ve created a new action that users can use in any of the many thousands of apps that will share an URL. Use the same basic technique to create new sharing services, photo filters, and interactive notifications.

But you’re not done just yet. There are cosmetic problems and more serious engineering issues to deal with. So, before you retire this book to your bookshelf, let’s take care of those.

Action Icons

Action extensions have their own icon. This is unusual; most extensions appear using their container app’s icon. The reason is that action icons aren’t icons; they’re stencils. A stencil is an icon created from the alpha channel (the layer of an image that determines the transparency of each pixel) of an image. The pixel colors are ignored, and the alpha channel is used to create a monochromatic button.

The first step is to add the necessary image files to your project, specifically to the extension’s resources. Begin by setting up your extension so it has an icon resource. Select the Shorty project in the project navigator, select the ShortenAction target, and then select the General tab. Locate the App Icons and Launch Images section and click the Use Asset Catalog button next to the App Icons Source, as shown in Figure 21-10.

image

Figure 21-10. Configuring an icon source for the extension

In the dialog sheet, change the migration option to New Asset Catalog and make sure the “Also migrate launch images” option is not checked. (Extensions don’t have launch images, and you don’t want to supply one).

Click the Migrate button, and Xcode will create a new asset catalog as a resource for your extension. I suggest changing the name of the catalog so you don’t confuse it with the asset catalog of your container app, as shown in Figure 21-11.

image

Figure 21-11. Renaming the extension’s asset catalog

If the new asset catalog has an AppIcon group, select it and delete it. With the ActionImages.xcassets catalog selected, add a new App Icon, as shown in Figure 21-12.

image

Figure 21-12. Adding a new App Icon set

Locate the Learn iOS Development Projects image Ch 21 image ShortenAction (Resources) folder. Drag the four image files into the AppIcon set of the new catalog, as shown in Figure 21-13. These are stencil images whose alpha layer contains the actual design. The color pixels of the image are superfluous but are set to gray just so they don’t appear blank while you’re working with them.

image

Figure 21-13. Adding the Action icon stencils

Test your extension in Safari again, as shown in Figure 21-14. This time, your action will have a stencil-generated icon like the rest of the actions, as shown on the right of Figure 21-14. If you don’t see it, try using the iOS Simulator’s Reset Content and Settings command and running your test again. This command is equivalent to restoring the iOS device, complete with new car smell. It erases all apps, documents, and any preferences saved on the device. iOS tends to cache information about extensions and might not notice subtle changes made behind the scene. Resetting the simulator forces it to start over.

image

Figure 21-14. Your extension’s icon

Your action extension is complete and ready to ship. You could just stop here, or you could take a step into the big league.

Reusing Squeezer, for Real

Compiling the Squeezer class twice is probably fine for a small project like this. But projects don’t tend to stay small. As you add more extensions and the capabilities of those extensions grow, what you’ll need to share with your app is also going to grow, sometimes substantially. Obviously, you’ll need to share all of your data model classes. If you have custom document classes, your extension might want to use those too. And then there’s the down-sampling code so your uploaded images aren’t too big, the encryption routines so Mordor can’t intercept the messages, the code that fetches the current weather forecast for the Shire, and on and on.

What you need is some way to package up compiled code and resources so that both your app and your extension can use them. That, my friend, is the definition of a framework. In this section you’ll create a second framework, one just for sharing code, and use that framework in both your app and your extension. Let’s get started.

Creating a Framework

Start by adding a new framework target to your project. Select Shorty in the project navigator. If the targets are collapsed, expand them. Click the + button at the bottom of the targets. In the target picker that appears, select the iOS Framework & Library group and then choose to add a Cocoa Touch Framework, as shown in Figure 21-15.

image

Figure 21-15. Choosing a framework template

In the next dialog, name the product SqueezeKit, as shown in Figure 21-16. Make sure both the Project and Embed in Application settings are set to Shorty. This will make the new framework a part of this project and then automatically embed the framework in the Shorty app. It will then be a resource of the Shorty app, just as ShortenAction is now.

image

Figure 21-16. Creating the framework

Adding Code to a Framework

The next step is to put something in the framework. Select the Squeezer.swift file, as shown in Figure 21-17, and use the file inspector to change its target membership. You want the Squeezer class to be compiled in the SqueezeKit target but not in the app or the extension.

image

Figure 21-17. Making Squeezer a member of SqueezeKit

To make this new relationship clearer, drag the Squeezer.swift file from its current location in the project navigator and drop it into the SqueezeKit group.

MOVING SQUEEZER.SWIFT, FOR REAL

You are free to drag around items in your project navigator and organize them any way you like. Your organization in the navigator, however, is independent of the how the files are organized in your filesystem. When you move Squeezer.swift from the top-level project group and drop it into the SqueezeKitgroup, the source file doesn’t move.

If you’re using a source control system or are just a neat freak like me, and you want your project’s folder structure to mirror its organization, you’ll want to move the source file into the framework’s subfolder too. Here’s the easiest way to do that:

1. Select the source file.

2. Right-click or Control+click the file and choose the Show in Finder command. Now you know where the real file is.

3. Back in Xcode, delete the file from your project and choose Remove Reference; do not move it to the trash.

4. Back in the Finder, move the file to its new location. In this example, that would be inside the SqueezeKit folder.

5. Drag the relocated file from the Finder back into your project (inside the SqueezeKit group). When Xcode asks, add the “new” file to the just the SqueezeKit target.

While there are ways of relocating files without removing and re-adding them to your project, this technique avoids a lot of the quirks involved in doing that.

Your Squeezer class is now compiled in a framework, and that framework is embedded and linked to your app. You should be able to use it everywhere, right? Not just yet.

Try to build your app and see what happens. In the ViewController.swift file, the compiler is now complaining that there is no Squeezer class. That’s because Squeezer is now in a framework. To use the code in a framework, you must import it. At the top of theViewController.swift file, add an import statement for the SqueezeKit framework, like this (new code in bold):

import UIKit
import SqueezeKit

Build your app again, and again (!) the compiler is saying that there’s no such class. Remember back in the beginning of Chapter 20, where I mentioned access control directives and said that until you start building frameworks you can generally ignore them? You can’t ignore them any longer.

The Squeezer class and all of its properties and methods were assigned the default access of internal, which means the class and its members are visible only in the app or framework where it is compiled. When you were compiling it in your app, it didn’t matter. Now it does.

Select the Squeezer.swift file and add a public access keyword to both the class and its method, as follows (new code in bold):

public class Squeezer {
public class func shortenURL(# longURL: NSURL, ...

Compile your app again, and the compiler errors go away! Congratulations, you’ve created a framework, packed it with useful code, decided what parts of that code are public, and used it in your app.

For Shorty, you’re not quite done. Now you’re seeing the same errors in the actionWithURL(_:) function of the ActionRequestHandler class. This is the same problem as before. Add a new import statement to beginning of ActionRequestHandler.swift, just as you did for ViewController.swift.

import UIKit
import MobileCoreServices
import SqueezeKit

Sharing a Framework with an Extension

There’s one tiny little detail you should attend to before calling it quits. Extensions run in a controlled environment. To protect that environment, iOS prohibits extensions from using certain Cocoa Touch classes and methods. For example, you can’t get the UIApplication object of the process that’s running your extension.

Xcode helps you keep from trying to do something you can’t with a special compiler flag that will warn you if you try to use something that’s out of bounds. The code in your extension is compiled with that flag set. The code in your framework is not. If you’re using a framework in your extension, you should set this flag in the framework too. That way, the framework won’t let you compile code you can’t use from the extension.

Select the Shorty project in the navigator, select the SqueezeKit target, and switch to the Build Settings tab. Make sure you’re looking at All settings. Enter the term safe into the search field and locate the Require Only App-Extension-Safe API setting, as shown in Figure 21-18. Click the setting value for the SqueezeKit target and change it to YES, also shown in Figure 21-18.

That’s it. You’re done!

image

Figure 21-18. Making SqueezeKit extension-safe

Summary

Extensions allow your code to enhance into the iOS experience well beyond the bounds of your app. You have the basics, but there’s so much more to explore. Give the App Extension Programming Guide a good read, and you’ll see other ways you can extend your apps.

You’ve learned a tremendous amount about iOS app development since Chapter 1. It’s been an exciting journey and one that’s only just begun. With the foundation you have now, you can explore many of the technologies I didn’t cover in this book and go deeper into the ones I did.

I hope you’ve enjoyed reading this book as much as I enjoyed writing it. Use your imagination, apply what you’ve learned, and promise to write (james@learniosappdev.com) or tweet (@LearniOSAppDev) me when you’ve written something great. Good luck!