Sharing Is Caring - Learn iOS 8 App Development, Second Edition (2014)

Learn iOS 8 App Development, Second Edition (2014)

Chapter 13. Sharing Is Caring

Social networking has exploded in recent years, and mobile apps have played a huge part in that revolution. Not too long ago, it was quite difficult to add social networking features to your app. Today, recent additions to iOS have made it so easy that—given what you know about view controllers—the process can be accurately described as “trivial.” In this, rather short, chapter, you will learn how to do the following:

· Share content via Facebook, Twitter, Sina Weibo, Tencent Weivo, Flickr, Vimeo, e-mail, SMS, and more

· Customize content for different services

Choosing which app to modify will probably be the most difficult decision in this chapter. Would people you know want to learn interesting facts about the Surrealists in Chapter 2? Of course you’d want to share a shortened URL from Chapter 3. Do your friends want to know what your Magic Eight Ball prediction was in Chapter 4? You took pictures of your cool stuff in Chapter 7; what if someone wants to see them? It would be easy to add sharing features to all of these apps. In the end, I chose to expand on the ColorModel app from Chapter 8. You’ve spent a lot of time and effort picking just the right color, and I’m sure your friends will appreciate you sharing it with them.

Color My (Social) World

Start with the final ColorModel app from Chapter 8. You’ll find that project in the Learn iOS Development Projects image Ch 6 image ColorModel-6 image ColorModel folder. You will add a button that shares the chosen color with the world. iOS includes a standard “activity” button item, just for this purpose, so use that. In the Main.storyboard interface file, add a toolbar to the bottom of the view controller’s interface, as shown in Figure 13-1.

image

Figure 13-1. Adding a toolbar and toolbar button item

Select the new toolbar and click the pin constraints control, as shown in Figure 13-2. Add left, right, and bottom constraints, accepting the default values. This will keep the toolbar positioned at the bottom of the layout. Toolbars have an intrinsic height that never changes, so you don’t need to pin its height.

image

Figure 13-2. Adding the toolbar constraints

The toolbar comes with one bar button item pre-installed. Select that bar button item and change its identifier property to Action. It will now look like this:

image

Switch to the assistant editor view (View image Assistant Editor image Show Assistant Editor). Make sure the ViewController.swift file appears in the right pane. Add the following action stub:

@IBAction func share(sender: AnyObject!) {
}

Connect the action function to the activity button by dragging the action’s connection to the button. I won’t include a figure for that because if you don’t know what that looks like by now, you’ve clearly skipped most of the earlier chapters.

Having Something to Share

Start by sharing the red-green-blue code for the color. Currently, the HTML value for the color is generated by the observeValueForKeyPath(_:,ofObject:,change:,context:) function (ViewController.swift). You now have a second method (share(_:)) that needs that conversion; consider reorganizing the code so this conversion is more readily accessible. Translating the current color into its equivalent HTML value feels as if it lies in the domain of the data model, so add this computed property to Color.swift:

var rgbCode: String {
var red: CGFloat = 0, green: CGFloat = 0, blue: CGFloat = 0, alpha: CGFloat = 0
color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)
return NSString(format: "%02X%02X%02X",CInt(red*255),CInt(green*255),CInt(blue*255))
}

Now that your data model object will return the color’s HTML code, replace the code in ViewController.swift to use the new property. Edit the end of observeValueForKeyPath(_:,ofObject:,change:,context:) so it looks like the following (replacement code in bold):

case "color":
colorView.setNeedsDisplay()
webLabel.text = "#\(colorModel.rgbCode)"

Note I’ve mentioned it once already, but it bears repeating. If you’re repeating yourself (writing the same code in multiple places), stop and think about how you could consolidate that logic.

Presenting the Activity View Controller

While still in the ViewController.swift file, finish writing the new action method.

@IBAction func share(sender: AnyObject!) {
let shareMessage = "I wrote an iOS app to share a color! RGB=#\(colorModel.rgbCode)"
let itemsToShare = [shareMessage]
let activityViewController = UIActivityViewController(activityItems: itemsToShare,
applicationActivities: nil)
if let popover = activityViewController.popoverPresentationController {
popover.barButtonItem = sender as UIBarButtonItem
}
presentViewController(activityViewController, animated: true, completion: nil)
}

The method starts by collecting the items to share. Items can be a message (string), an image, a video, a document, a URL, and so on. Basically, you can include any message, link, media object, or attachment that would make sense to share. You then collect them in an array.

The next section is just as straightforward. You create a UIActivityViewController, initializing it with the items you want to share. You then modally present the view controller. (The code in between handles the popover case for iPads and the like; if you’ve read Chapter 12, you know all about that).

That’s all there is to it! Run the project and tap the share button, as shown in Figure 13-3.

image

Figure 13-3. Sharing a String message

Tapping the share button presents a picker that lets the user decide how they want to share this message. While your goal is to add sharing to your app, the motivation behind the UIActivityViewController is to allow the user to do any number of things with the data items you passed in, all external to your application. This includes actions such as copying the message to the clipboard, which is why it’s named UIActivityViewController and not UIPokeMyFriendsViewController.

Tapping Mail composes a new mail message containing your text. Each activity has its own interface and options. Some, such as the copy to clipboard action, have no user interface at all; they just do their thing and dismiss the controller.

Note This is one of those rare cases where the modal controller dismisses itself. UIActivityViewController does not use a delegate to report what it did, and you are not responsible for dismissing it when it’s done. In fact, the only way to find out whether it performed an activity is to assign a code block to its completionWithItemsHandler property before presenting it. The code block receives four parameters: an activityType string describing which activity was chosen (such as UIActivityTypePostToFacebook), a Booleancompleted parameter that is true if it was successful, an array of the data items that were ultimately shared, and an NSError object describing any failure.

The activity choices will be the intersection of what activities are available, what services you have authorized or configured, and the types of items you’re sharing. In Figure 13-3, the choices are pretty paltry. That’s because this app was run on the simulator, which does not have any services like Twitter or Facebook configured, and the only thing you’re sharing is a simple string. I think you can do better.

The More You Share, the More You Get

I admit, sharing a hexadecimal color code probably isn’t going to get you a lot of “likes” on Facebook. In fact, it’s pretty boring. When you share a color, what you want to share is a color. You can improve the user experience by preparing as much content as you can for the activity view controller. Include not just plain-text messages, but images, video, URLs, and so on. The more, the merrier.

It’s a shame you can’t attach the image displayed by ColorView. You went to a lot of work to create a beautiful hue/saturation chart, with the selected color spotlighted. But all that drawRect(_:) code draws into a Core Graphics context; it’s not a UIImage object you can pass toUIActivityViewController.

Or could you?

If you remember the section “Drawing Images” in Chapter 11, it’s possible to create a UIImage object by first creating an off-screen graphics context, drawing into it, and then saving the results as an image object. That would let you turn your drawing into a sharable UIImage object! So, what are you waiting for?

Refactoring drawRect(_:)

Turn your attention to the ColorView.swift file. What you need now is a method that does the work of drawing the hue/saturation image in a graphics context, separate from your drawRect(_:) function. Let’s call it drawColorChoice(bounds:). This new function will perform the same drawing work that’s currently in drawRect(_:). Your drawRect(_:) function now does little beyond calling drawColorChoice(bounds:) to draw itself and now looks like the following:

override func drawRect(rect: CGRect) {
drawColorChoice(bounds: bounds)
}

The bulk of the code that was in drawRect(_:), with few changes, has been relocated to the drawColorChoice(bounds:) function. You can copy this code from the Learn iOS Development Projects image Ch 13 image ColorModel-2 image ColorModel folder, along with its companion function, hsImage(size:,brightness:).

What you just did might look a little silly, even a waste of time. After all, you just replaced a function with another function that does the same thing and then called it to do exactly what you were doing before. But there’s one critical, and strategic, difference between the two.drawRect(_:) draws itself at its current bounds. drawColorChoice(bounds:) draw the hue/saturation field at the rectangle specified. That makes it possible to draw the image with different sizes into different contexts.

Tip This kind of software change is called refactoring. Code refactoring is the art of restructuring your code without changing what it does (see http://refactoring.com/). You refactor to better organize your classes, simplify their interfaces, reduce complexity, or—as in this example—consolidate and reuse code. The key point is that the drawRect(_:) function still behaves the same as it did before the change. But your code is now organized so it’s more flexible and reusable.

You did all of this so you can add a new function that returns the hue/saturation graphic as a UIImage object. Add that function now.

func colorChoiceImage(# size: CGSize) -> UIImage {
UIGraphicsBeginImageContext(size)
let context = UIGraphicsGetCurrentContext()
var bounds = CGRect(origin: CGPointZero, size: size)

UIColor.clearColor().set()
CGContextFillRect(context, bounds)

bounds.inset(dx: radius, dy: radius)
drawColorChoice(bounds: bounds)

let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}

If you made it through Chapter 11, you shouldn’t have any problems understanding this code. It begins by creating an off-screen graphics context, fills it with transparent pixels, calls drawColorChoice(bounds:) to draw the hue/saturation chart and color choice (inset a little so the “loupe” doesn’t get clipped), and then turns the finished drawing into a UIImage object. Easy peasy.

Providing More Items to Share

Now if you want to get what the ColorView object draws on the screen as an image object, you simply call its colorChoiceImage(size:) function. Use this in the share(_:) function. Select the ViewController.swift file and replace the first two statements with the following code (changes in bold):

@IBAction func share(sender: AnyObject!) {
let shareMessage = "I wrote an iOS app to share a color! image
RGB=#\(colorModel.rgbCode) @LearniOSAppDev"
let shareImage = colorView.colorChoiceImage(size: CGSize(width: 380, height: 160))
let shareURL = NSURL(string: "http://www.learniosappdev.com/")!
let itemsToShare = [shareMessage,shareImage,shareURL]
let activityViewController = UIActivityViewController(activityItems: itemsToShare,
applicationActivities: nil)
...

Run the app again. This time, you’re passing three items (a string, an image, and a URL) to the UIActivityViewController. Notice how this changes the interface, as shown in Figure 13-4.

image

Figure 13-4. Activities with more sharable items

Each activity responds to different kinds of data. Now that you include an image object, activities like Save Image and Assign to Contact appear. Each activity is also free to do what it thinks makes sense for the types of data you provide. The Mail activity will attach images and documents to a message, Facebook will upload images to the user’s photo album, while Twitter may upload the picture to a photo-sharing service and then include the link to that image in the tweet. It’s completely automatic.

Tip If you’re curious about what activities work with what kinds of data, refer to the UIActivity class documentation. Its “Constants” section lists all of the built-in activities and the classes of objects each responds to.

Excluding Activities

iOS’s built-in activities are smart, but they aren’t prescient; they don’t know what the intent of your data is. Activities know when they can do something with a particular type of data but not if they should. If there are activities that you, as a developer, don’t think are appropriate for your particular blend of data, you can explicitly exclude them.

You’ve decided that printing a color example or assigning it to a contact don’t make any sense. (You assume the user has no contacts for Little Red Riding Hood, the Scarlet Pimpernel, the Green Hornet, or other colorful characters.) Return to the share(_:) function inViewController.swift. Immediately after the statement that creates the activityViewController, add the following statement. You’ll find this finished project in the Learn iOS Development Projects image Ch 13 image ColorModel-3 image ColorModel folder.

activityViewController.excludedActivityTypes = image
[UIActivityTypeAssignToContact,UIActivityTypePrint]

Setting this property excludes the listed built-in activities from the choices. Run the app again. This time, the excluded activities are, well, excluded (see Figure 13-5). Compare this to Figure 13-4.

image

Figure 13-5. Activities with some excluded

The Curse of the Lowest Common Denominator

The activity view controller is a fantastic iOS feature, and it’s only gotten better with time. About the only negative thing you can say about it is that it’s too easy to use. Its biggest problem is that there’s no obvious way of customizing that data items based on what the user wants to do with it.

Case in point: when I was developing the app for the chapter, I initially added a simple rgbCode property to the Color class that returned the HTML code for the color (#f16c14). The problem with this is Twitter. On Twitter, so-called hash tags start with a pound/hash sign and are used to identify keywords in tweets. My color (#f16c14) would be interpreted as an f16c14 tag, which won’t be trending any time soon. To avoid this, I rewrote the property so it returns just the hexadecimal portion of the RGB value and purposely left out the # in the message passed toUIActivityViewController. That way, if the user decided to share with Twitter, it wouldn’t tweet a confusing message.

Note Sina Weibo also uses hash tags, but the pound/hash signs bracket the tag (#Tag#). Thus, #f16c14 would not be a hash tag on Weibo.

But that’s just the tip of the iceberg. Message length for mail and Facebook can be considerably longer than those on Twitter. Why should your text message or Facebook post be limited to 140 characters?

Providing Activity-Specific Data

The iOS engineers did not ignore this problem. There are several ways of customizing your content based on the type of activity the user chooses. The two avenues iOS provides are as follows:

· UIActivityItemSource

· UIActivityItemProvider

The first is a protocol, which your class adopts. Any object that conforms to the UIActivityItemSource protocol can be passed in the array of data items to share. The UIActivityViewController will then call these two (required) functions:

activityViewController(_:,itemForActivityType:) -> AnyObject?
activityViewControllerPlaceholderItem(_:) -> AnyObject

The first method is responsible for converting the content of your object into the actual data you want to share or act upon. What’s significant about this message is that it includes the activity the user chose in the itemForActivityType parameter. Use this parameter to alter your content based on what the user is doing with it.

For ColorModel, you’re going to turn your ViewController object into a sharing message proxy object. Select your ViewController.swift file. Adopt the UIActivityItemSource protocol in your ViewController class (changes in bold).

class ViewController: UIViewController, UIActivityItemSource {

Tip If you had a more complex conversion, or multiple conversions, I recommend creating new classes (possibly subclasses of UIActivityItemProvider) that did nothing but perform the transformation. This would make it easy to develop as many different kinds of conversions as you needed.

Now add the first of UIActivityItemSource’s two required functions.

func activityViewController(activityViewController: UIActivityViewController, image
itemForActivityType activityType: String) -> AnyObject? {
var message: String?
switch activityType {
case UIActivityTypePostToTwitter, UIActivityTypePostToWeibo:
message = "Today's color is RGB=\(colorModel.rgbCode). image
I wrote an iOS app to do this! @LearniOSAppDev"
case UIActivityTypeMail:
message = "Hello,\n\nI wrote an awesome iOS app that lets me share a color image
with my friends.\n\nHere's my color (see attachment): hue=\(colorModel.hue)°, image
saturation=\(colorModel.saturation)%, brightness=\(colorModel.brightness)%.\n\nimage
If you like it, use the HTML code #\(colorModel.rgbCode) in your design.\n\nEnjoy,\n\n"
default:
message = "I wrote a great iOS app to share this color: #\(colorModel.rgbCode)"
}
return message
}

This function performs the conversion from your object to the actual data object that the activity view controller is going to share or use. For this app, your controller will provide the message (String object) to post.

Your method examines the activityType parameter and compares it against one of the known activities. (If you provided your own custom activity, the value would be the name you gave your activity.) For Twitter and Weibo, it prepares a short announcement, avoiding inadvertently creating any hash tags and including a Twitter-style mention. If the user chooses to send an e-mail, you prepare a rather lengthy message, without a mention. For Facebook, SMS, and any other activity, you create a medium-length message.

Find the share(_:) function and change the beginning of it so it looks like this (removing shareMessage and changing the code in bold):

@IBAction func share(sender: AnyObject!) {
let shareImage = colorView.colorChoiceImage(size: CGSize(width: 380, height: 160))
let shareURL = NSURL(string: "http://www.learniosappdev.com/")!
let itemsToShare = [self,shareImage,shareURL]

Instead of preparing a message, you now pass your ViewController object with a promise to provide the message. Once the user has decided what they want to do (mail, tweet, message, and so on), your view controller will receive aactivityViewController(_:,itemForActivityType:) call and produce the data.

Promises, Promises

You may have noticed the chicken-and-egg problem here. What activities are available is determined by the kinds of data you pass to the activity view controller. But with UIActivityItemSource, the data isn’t produced until the user chooses an activity. So, how does the activity view controller know what kind of activities to offer if it doesn’t yet know what kind of data your method plans to produce?

The answer is the second required UIActivityItemSource function, and you need to add that now.

func activityViewControllerPlaceholderItem(activityViewController: image
UIActivityViewController) -> AnyObject {
return "My color message goes here."
}

This method returns a placeholder object. While it could be the actual data you plan to share, it doesn’t have to be. Its only requirement is that it be the same class as the object that activityViewController(_:,itemForActivityType:) will return in the future. Since youractivityViewController(_:,itemForActivityType:) returns a string, all this function has to do is return any string object.

Caution The object that activityViewController(_:,itemForActivityType:) returns should be “functionally equivalent” to the final data object, even if it’s not the same data. For example, if you are supplying an NSURL object, the scheme (http:, mailto:,file:, sms:, and so on) of the placeholder URL should be the same.

Run the app again and try different activities, as shown in Figure 13-6.

image

Figure 13-6. Activity customized content

Big Data

The alternative technique for providing activity data is to create a custom subclass of UIActivityItemProvider. This class, which already conforms to the UIActivityItemSource protocol, produces your app’s data object in the background. When the activity view controller wants your app’s data, it sets the activityType property of your provider object and then requests its item property. Your subclass must override the item property to provide the desired data, referring to activityType as needed.

UIActivityItemProvider is intended for large or complex data that’s time-consuming to create, such as a video or a PDF document. It gets an item() function call on a secondary execution thread—not on your app’s main thread, which is the thread all of your code in this book has executed on so far. This allows your provider object to work in the background, preparing the data, while your app continues to run. It also requires an understanding of multitasking and thread-safe operations.

In short, if the data you need to share isn’t particularly large, complicated, or time-consuming to construct, or you’re just not comfortable with multitasking yet, stick with adopting UIActivityItemSource.

Inventing Your Own Activities

It’s possible to invent and add your own activities to the items that appear in the activities controller. You have two choices. To create an activity specific to your app—and that appears only in your app—create a concrete subclass of UIActivity. When you’re ready to present your activity controller, pass your activity object (or objects, if you’ve created more than one) in the applicationActivities: parameter. Your custom activities will appear alongside the others.

The exciting addition in iOS 8 is the ability to design an activity that appears in other apps. You design your activity in your app. Any user who has installed your app can then use your custom activity in all the apps that presents a UIActivityViewController. In other words, you can share the sharing. You accomplish this by creating a framework, and you’ll create a new activity framework in Chapter 21.

Sharing with Specific Services

I’d like to round off this topic with some notes on other sharing services in iOS and which ones to use.

The UIActivityViewController class is relatively new and largely replaces several older APIs. If you search the iOS documentation for classes that will send e-mail, text messages (SMS), or tweets, you’re likely to find MFMailComposeViewController,MFMessageComposeViewController, and TWTweetComposeViewController. Each of these view controllers presents an interface that lets the user compose and send an e-mail message, a short text message, or a tweet, respectively. The latter two don’t offer any significant advantages over UIActivityViewController or SLComposeViewController (which I’ll explain in a moment), and their use in new apps is not recommended, although they have not been deprecated and are still fully supported.

The MFMailComposeViewController still has a trick or two to offer over UIActivityViewController. Its biggest talent is its ability to create an HTML-formatted mail message and/or pre-address the message by filling in the To, CC, and BCC fields. This allows you to create pre-addressed, richly formatted e-mail, with embedded CSS styling, animation, links, and other HTML goodies.

If you want to present your user with an interface to post to a specific social service—rather than asking them to choose—use the SLComposeViewController class. You create an SLComposeViewController object for a specific service (Twitter, Facebook, or Sina Weibo) using the composeViewControllerForServiceType(_:) function. You then configure that view controller with the data you want to share, as you did with UIActivityViewController, and present the view controller to the user. The user edits their message and away it goes.

Other Social Network Interactions

In ColorModel, we’ve explored only the sharing side of social networking. If you want your app to get information from your user’s social networks, that’s another matter altogether. Other types of interactions, such as getting contact information about a user’s Facebook friends, are handled by the SLRequest class.

An SLRequest works similarly to the way an NSURLRequest works. You used NSURLRequest objects in Chapter 3 to send a request to the X.co URL shortening service. To use a social networking system, you prepare an SLRequest object in much the same manner, providing the URL of the service, the method (POST or GET), and any required parameters. You send the request, providing a code block that will process the response.

The biggest difference between SLRequest and NSURLRequest is the account property. This property stores an ACAccount object that describes a user’s account on a specific social networking service. This property allows SLRequest to handle all of the authentication and encryption required to communicate your request to the servers. If you’ve ever written any OAuth handling code, you’ll appreciate how much work SLRequest is doing for you.

To use other social networking features you must, therefore, prepare the following:

· Service type

· Service URL

· Request method (POST, GET, DELETE)

· Request parameters dictionary

· The user’s ACAccount object

The service type is one of SLServiceTypeFacebook, SLServiceTypeSinaWeibo, SLServiceTencentWeibo, or SLServiceTypeTwitter. The URL, method, and parameters dictionary are dictated by whatever kind of request you’re making. For those details, consult the developer documentation for the specific service. Some places to start reading are listed in Table 13-1.

Table 13-1. Social Services Developer Documentation

Social Service

URL

Facebook

https://developers.facebook.com/docs/

Sina Weibo

http://open.weibo.com/wiki/

Tencent Weibo

http://dev.t.qq.com/

Twitter

https://dev.twitter.com/docs

Finally, you’ll need the ACAccount object for the user’s account. Account and login information is maintained by iOS for your app, so your app needs only to request it. Whether the user wants to authorize your app to use their account or they need to sign in, it’s all handled for you.

The following are the basic steps to obtaining an account object:

1. Create an instance of the ACAccountStore object.

2. Call the account store’s accountTypeWithAccountTypeIdentifier(_:) function to get an ACAccountType object for the service you’re interested in. An ACAccountType object is your key to the user’s accounts on a specific service.

3. Finally, you call the account store’s requestAccessToAccountsWithType(_:) function. If successful (and allowed), your app will receive an array of ACAccount objects for that user.

Services such as Facebook allow an iOS user to be logged into only one account at a time. Twitter, on the other hand, permits a user to be connected to multiple accounts simultaneously. Your app will have to decide whether it wants to use all of the account objects, selected ones, or just one. Once you have an ACAccount object, use it to set the account property of the SLRequest, and you’re ready to get social!

Summary

You’ve learned how to add yet another nifty feature to your app, allowing your users to connect and share content with friends and family around the world—and it took only a smattering of code to get it working. You learned how to tailor that content for specific services or exclude services. If you want more control over which services your app provides, you learned how to use the SLComposeViewController to create a specific sharing interface, along with the SLRequest class that provides a conduit for unlimited social networking integration.

During your journey, you also gained some practical experience in drawing into an off-screen graphics context and refactoring code. In fact, you’ve been working awfully hard so far. Why not take a break and play a fun game? You don’t have one? Well then, read the next chapter and create your own!