Tải bản đầy đủ
Chapter 12. Supporting the iOS Ecosystem

Chapter 12. Supporting the iOS Ecosystem

Tải bản đầy đủ

Figure 12-1. The standard iOS share sheet
Sharing on iOS is handled by UIActivityViewController, which provides a standard
view controller offering system services, such as copy, paste, and so on, as well as
sharing to social media, email, or text messaging. Other apps can also provide share
destinations.
1. Open Main.storyboard and go to the image attachment view controller.

310

|

Chapter 12: Supporting the iOS Ecosystem

2. Add a UIToolBar from the Object library to the view and place it at the bottom of
the screen. This will also include a UIBarButtonItem, which works pretty much
exactly like our old friend UIButton, but is customized to work in toolbars.
3. Resize the toolbar to make it fit the width of the screen. Next, click on the Pin
menu, and pin the left, right, and bottom edges of the view. This will keep it at
the bottom of the screen and make it always fill the width of the screen.
4. Select the button and set its System Item property to Action, as shown in
Figure 12-2. This will change its icon to the standard iOS share icon.

Figure 12-2. Setting the button to the Action mode
5. Open ImageAttachmentViewController.swift in the Assistant editor.
6. Hold down the Control key and drag from the toolbar button you just added into
ImageAttachmentViewController. Create a new action called shareImage.
7. Add the following code to the shareImage method. Note that the type for the
sender parameter is UIBarButtonItem—you’ll need to change it when you start
writing the code:
@IBAction func shareImage(sender: UIBarButtonItem) {
// Ensure that we're actually showing an image
guard let image = self.imageView?.image else {
return
}
let activityController = UIActivityViewController(
activityItems: [image], applicationActivities: nil)
// If we are being presented in a window that's a regular width,
// show it in a popover (rather than the default modal)
if UIApplication.sharedApplication().keyWindow?.traitCollection
.horizontalSizeClass == UIUserInterfaceSizeClass.Regular {
activityController.modalPresentationStyle = .Popover
activityController.popoverPresentationController?
.barButtonItem = sender
}

Sharing with UIActivityController

|

311

self.presentViewController(activityController, animated: true,
completion: nil)
}

When the share button is tapped, we want to prepare and present a UIActivityCon
troller, which will allow the user to do something with the image. What that some‐

thing actually is depends upon the capabilities of the system and the apps that the user
has installed. To create it, you pass in an array of activityItems, which can be a wide
variety of things: URLs, images, text, chunks of data, and so on. The UIActivityCon
troller will then determine what services can accept these items, and then let the
user choose what to do.

When the app is being presented in a larger screen, such as on an iPhone 6+ or iPad,
we want to show it as a popover. To detect this, we ask the window in which the app is
running to tell us about its horizontal size class—that is, whether it is in a horizontally
“compact” view, or a horizontally larger “regular” view. If it’s in a regular-sized view,
we instruct the activity controller to use a popover, and we set the barButtonItem
property on the popoverPresentationController to the sender, which will visually
connect the popover to the share button in the toolbar.

Handoffs
Let’s imagine that your user’s on a bus, tapping out a note. She arrives at her stop, gets
off the bus, and walks into the office, still writing the note. Eventually, she reaches her
desk, and she wants to finish up the note. She could finish it up on the phone, but
she’s right in front of a dedicated workstation. Rather than deal with a tiny
touchscreen, she instead uses Handoff to move her work from her phone to the desk‐
top.
Handoff is a technology on the Mac, iOS, and on watchOS that allows the user to
start an activity on one device and seamlessly move to another device (see
Figure 12-3). The way it works is this: applications register activity types with the sys‐
tem, which are simple text strings that are the same across all of the different apps
that can receive the handoff. When the user opens a document, he marks it as the
current activity; this makes the operating system broadcast this fact to all nearby devi‐
ces. When the user decides to activate Handoff on another device, the originating
device and the receiving device quickly swap information about what he wants to do,
and the receiving device’s app delegate is then given the opportunity to continue the
activity.

312

|

Chapter 12: Supporting the iOS Ecosystem

Figure 12-3. Handoffs working with Safari on iOS and OS X
Because we’re using NSDocument and UIDocument, lots of the details
of this get taken care of for you. If you weren’t using the document
system, you’d need to manually create your own NSUserActivity
objects before calling becomeCurrent. For more information, see
the Handoff Programming Guide in the Xcode documentation.

To get started using Handoff, we need to describe to the system the type of “activity”
that is associated with editing this document. When we do this, the device will inform
all other devices that belong to the same person that this specific document is being
edited.
1. Select the project at the top of the Project Navigator (Figure 12-4).

Handoffs |

313

Figure 12-4. Selecting the project in the Project Navigator
2. Go to the Notes target settings (that is, the OS X app) and scroll down to the
Document Types section.
3. Add a new entry in “Additional document type properties” by expanding the
“Additional document type properties” triangle, selecting the CFBundleTypOS
Types entry, and clicking the + button that appears.
4. Call the new entry NSUbiquitousDocumentUserActivityType and set its type to
String. Set its value to au.com.secretlab.Notes.editing.
5. Now go to the same place in Notes-iOS, and add the same entry.
If you have been using a custom bundleID throughout, make
sure you use that here with .editing appended at the end. If
you don’t do this, handoffs will not work.

Once you’ve done this, the two applications will associate a Handoff-able activity
with their document types. When the document is open, the app will be able to
simply say to the system, “Begin broadcasting the fact that this document is
open.”
6. Open the AppDelegate.swift file that belongs to the Notes-iOS target (not the OS
X one!).
7. Implement the following method, which returns to the list of documents and
then signals that that view controller should resume an activity:
func application(application: UIApplication,
continueUserActivity userActivity: NSUserActivity,
restorationHandler: ([AnyObject]?) -> Void) -> Bool {
// Return to the list of documents
if let navigationController =
self.window?.rootViewController as? UINavigationController {
navigationController.popToRootViewControllerAnimated(false)

314

| Chapter 12: Supporting the iOS Ecosystem

//
//
//
if

We're now at the list of documents; tell the restoration
system that this view controller needs to be informed
that we're continuing the activity
let topViewController = navigationController.topViewController {
restorationHandler([topViewController])

}
return true
}
return false
}

The continueUserActivity method is called when the user has decided to hand
off the activity from one device to the next.The userActivity object contains the
information describing what the user wants to do, and this method is responsible
for telling the app what needs to happen to let the user pick up from where the
last device left off.
It does this through the restorationHandler closure that it receives as a parame‐
ter. This closure takes an array of objects which the app should call the restoreU
serActivityState method on; this method receives the NSUserActivity as a
parameter, which can be used to continue the state.
The reason for doing this is to move as much of the logic that drives the continu‐
ation of the activity to the view controllers, instead of making the app delegate
have to know about the details of how documents get opened.
The way that we’ll handle this in this app is to return to the DocumentListView
Controller, and then indicate that the view controller should be told about the
handoff by passing it to the restorationHandler.
8. Open DocumentListViewController.swift.
9. Add the following method to the DocumentListViewController class:
override func restoreUserActivityState(activity: NSUserActivity) {
// We're being told to open a document
if let url = activity.userInfo?[NSUserActivityDocumentURLKey]
as? NSURL {
// Open the document
self.performSegueWithIdentifier("ShowDocument", sender: url)
}
}

This method is called as a result of passing the DocumentListViewController to
the restorationHandler in continueUserActivity. Here, we extract the URL
for the document that the user wants to open by getting it from the NSUserActiv
Handoffs |

315

ity’s userInfo dictionary, and then performing the ShowDocument segue, passing
in the URL to open. This means that when the application is launched through
the Handoff system, the document list will immediately open the document that
the user wants.

10. Finally, add the following code to the viewWillAppear method of DocumentView
Controller, to make the activity current:
// If this document is not already open, open it
if document.documentState.contains(UIDocumentState.Closed) {
document.openWithCompletionHandler { (success) -> Void in
if success == true {
self.textView?.attributedText = document.text
self.attachmentsCollectionView?.reloadData()
>
>

// We are now engaged in this activity
document.userActivity?.becomeCurrent()
// Register for state change notifications
self.stateChangedObserver = NSNotificationCenter
.defaultCenter().addObserverForName(
UIDocumentStateChangedNotification,
object: document,
queue: nil,
usingBlock: { (notification) -> Void in
self.documentStateChanged()
})
self.documentStateChanged()
}

Every UIDocument has an NSUserActivity. To indicate to the system, and to
every other device that the user owns, that the user’s current task is editing this
document, we call becomeCurrent on the document’s userActivity. This causes
the current device to broadcast to all other devices in range, letting them know
that we’re offering to hand off this activity.
You can now test handoffs. Launch the iOS app on your phone, and then launch
the OS X app. Open a document on your phone, and a Handoff icon will appear
at the left of the dock on your Mac, as shown in Figure 12-5.

316

|

Chapter 12: Supporting the iOS Ecosystem

Figure 12-5. Handoff on OS X
The reverse will also work on iOS: open a document on your Mac, and the iOS
app’s icon will appear on the lock screen (Figure 12-6).

Figure 12-6. Handoff on iOS—the handoff icon is shown in the bottom-left corner

Searchability
The next feature we’ll add is the ability for users to search the phone to find docu‐
ments that they’ve written. There are three different searching technologies that we
can use to support this: using NSUserActivity objects, Core Spotlight, and web
indexing.
• NSUserActivity allows you to index parts of your app—for example, if you have
an app that downloads and shows recipes, every time the user views a recipe, you
record that as an activity and describe how to get back to this screen; Spotlight
indexes this activity and displays it if the user searches for things that match the
activity’s description.
• Core Spotlight gives you control over the search index: you manually submit
metadata items into the index. We’ll be covering using Core Spotlight in “Search‐
ing with a Spotlight Indexing Extension” on page 322.
• Web indexing allows you to mark up websites for Apple’s search crawler to view.

Searchability

|

317

Because we’re not building web apps in this book, we won’t be cov‐
ering web archiving. If you’re interested in it, you can read more
about it in the App Search Programming Guide, in the Xcode doc‐
umentation.

We’ll be covering marking NSUserActivity objects as searchable in this chapter. In
the next chapter, which covers creating extensions, we’ll also talk about creating a
Spotlight indexing extension, which provides additional search functionality by regis‐
tering the contents of all documents in the app with Core Spotlight.
We’ll start by adding support for indexing the app through NSUserActivity.
1. Open DocumentViewController.swift.
2. Import the Core Spotlight framework at the top of the file:
import CoreSpotlight

3. Update the viewWillAppear method to add searchable metadata to the docu‐
ment’s user activity when the document is opened:
// If this document is not already open, open it
if document.documentState.contains(UIDocumentState.Closed) {
document.openWithCompletionHandler { (success) -> Void in
if success == true {
self.textView?.attributedText = document.text
self.attachmentsCollectionView?.reloadData()
>
>
>
>
>
>
>
>
>
>
>
>
>
>

// Add support for searching for this document
document.userActivity?.title = document.localizedName
let contentAttributeSet
= CSSearchableItemAttributeSet(
itemContentType: document.fileType!)
contentAttributeSet.title = document.localizedName
contentAttributeSet.contentDescription = document.text.string
document.userActivity?.contentAttributeSet
= contentAttributeSet
document.userActivity?.eligibleForSearch = true
// We are now engaged in this activity
document.userActivity?.becomeCurrent()
// Register for state change notifications
self.stateChangedObserver = NSNotificationCenter
.defaultCenter().addObserverForName(

318

|

Chapter 12: Supporting the iOS Ecosystem

UIDocumentStateChangedNotification,
object: document,
queue: nil,
usingBlock: { (notification) -> Void in
self.documentStateChanged()
})
self.documentStateChanged()
}

This code adds further metadata to the document’s userActivity. First, it pro‐
vides a name for the document, which will appear in the Spotlight search results.
In addition, we create a CSSearchableItemAttributeSet, which is the (overcom‐
plicated) term for “stuff the search system uses to decide if it’s what the user’s
looking for.” In this case, we provide two pieces of information: the name again,
and the text of the document.
We then provide this to the userActivity and mark it as available for searching.
You can now test searching. Run the app and open a document. Type some words
into the document, close the app, and go to the Search field (swipe down while on the
home screen). Type in some of the words that you added to the document, and your
document will appear! When you tap on the search result, the app will launch, and
you’ll be taken to the document.

Conclusion
When an application participates in the wider iOS ecosystem, it feels like it “belongs”
on the user’s device. When you take advantage of as many system features as possible,
rather than reinventing new systems from whole cloth, it’s more likely that users will
consider your apps an indispensable part of their device use.

Conclusion

|

319