Now if you want to get what the CMColorView object draws on the screen as an image, you simply fetch its image property. Use this in the -share: method. Select the CMColorViewController.m file and change the first fewstatements with the following code (changes in bold):
- (IBAction)share:(id)sender
{
NSString *shareMessage = [NSString stringWithFormat:
@"I wrote an iOS app to share a color!"
@" RGB=%@"
@" @LearniOSAppDev",
[self.colorModel rgbCodeWithPrefix:nil]];
UIImage *shareImage = self.colorView.image;
NSArray *itemsToShare = @[shareMessage,shareImage,shareURL];
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-6.
Figure 13-6. 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 datayou provide. The Mail activity will attach images and documents to a message, Facebook will upload image 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.
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 activitiesthat 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: method in CMColorViewController.m. Immediately after the statementthat creates the activityViewController, add this statement:
activityViewController.excludedActivityTypes = @[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-7).
Figure 13-7. Activities with some excluded
The Curse of the Lowest Common Denominator
The activity view controller is a fantastic iOS feature, and it’s likely to get 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 ofcustomizing 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 method to the CMColor 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 signal keywords in tweets. My color (#f16c14) would be interpreted as an “f16c14” tag, which doesn’t make any sense.
To avoid this, I rewrote the method so I couldobtain the RGB value with, or without, the hash and
purposely left it out from the message passed to UIActivityViewController. That way, if the user decided to
share with Twitter, it wouldn’t tweet a confusing message.
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
As it happens, 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 tools iOS provides are:
n UIActivityItemSource
n 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 send your object these two (required) messages:
- (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
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 theactivityType 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 CMViewController object into a sharing message proxy object. Select your CMViewController.h file. Adopt the UIActivityItemSource protocol in your CMViewController class (changes in bold): @interface CMViewController : UIViewController <UIActivityItemSource>
Switch to CMViewController.m. Add the first of UIActivityItemSource’s two required methods:
- (id)activityViewController:(UIActivityViewController *)activityViewController itemForActivityType:(NSString *)activityType
{
CMColor *color = self.colorModel; NSString *message = nil;
if ([activityType isEqualToString:UIActivityTypePostToTwitter] || [activityType isEqualToString:UIActivityTypePostToWeibo])
{
message = [NSString stringWithFormat:
@"Today's color is RGB=%@."
@"I wrote an iOS app to do this!"
@"@LearniOSAppDev",
[color rgbCodeWithPrefix:nil]];
}
else if ([activityType isEqualToString:UIActivityTypeMail])
{
message = [NSString stringWithFormat:
@"Hello,\n\n"
@"I wrote an awesome iOS app that lets me share"
@"a color with my friends.\n\n"
@"Here's my color (see attachment): hue=%.0f\u00b0,"
@"saturation=%.0f%%, "
@"brightness=%.0f%%.\n\n"
@"If you like it, use the code %@ in your design.\n\n"
@"Enjoy,\n\n", color.hue, color.saturation, color.brightness,
[color rgbCodeWithPrefix:@"#"]];
} else
{
message = [NSString stringWithFormat:
@"I wrote a great iOS app to share this color: %@", [color rgbCodeWithPrefix:@"#"]];
}
return message;
}
This method 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 (NSString 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, itprepares 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. ForFacebook, SMS, and any other activity, you create a medium-length message that doesn’t worry about hash tags.
Find the -share: method and change the beginning of it so it looks like this (removing shareMessage
and adding the new code in bold):
- (IBAction)share:(id)sender
{
UIImage *shareImage = self.colorView.image;
NSURL *shareURL = [NSURL URLWithString:@"http://www.learniosappdev.com/"]; NSArray *itemsToShare = @[self,shareImage,shareURL];
Instead of preparing a message before the activity view controller is presented, you now pass your
CMViewController object with a promise to provide the message. Once the user has decided what
they want to do (print, Tweet, Message, and so on), your view controller will receive a -activityView Controller:itemForActivityType: message and produces 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 theuser 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 method, and you need to add that now:
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
{
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 of the object that -activityView Controller:itemForActivityType: willreturn in the future. Since your -activityViewController:item ForActivityType: returns an NSString, all this method has to do is return any NSString object.
Run the app again and try out different activities, as shown in Figure 13-8.
Figure 13-8. 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 thebackground.
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 method to provide thedesired 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 receives the -item message 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 multi-tasking and thread-safe operations, topics that I visit in Chapter 24.
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 multi-tasking yet, stick with adopting UIActivityItemSource.
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 findMFMailComposeViewController, MFMessageComposeViewController, and TWTweetComposeViewController. Each of these view controllers presents an interfacethat 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.
The MFMailComposeViewController still has a trick or two to offer. 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 allowsyou 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 specificservice (Twitter, Facebook, or Sina Weibo) using the+composeViewControllerForServiceType: message.
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 only explored 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, like gettingcontact information about a user’s Facebook friends, are handled by the SLRequest class.
An SLRequest works very 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 anSLRequest 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 allowsSLRequest 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:
n Service Type
n Service URL
n Request method (POST, GET, DELETE)
n Request parameters dictionary
n 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/
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 only needs to request it. Whether the user wants to authorize your app to usetheir account, or they need to sign in, it’s all handled for you.
The basic steps to obtaining an account object are:
1. Create an instance of the ACAccountStore object.
2. Send the account store an -accountTypeWithAccountTypeIdentifier:
message 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 send the account store a -requestAccessToAccountsWithType: message. If successful (and allowed) your app will receive an array of ACAccount objects for that user.
Services like 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 if it wantsto 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 only took a smattering of code to get it working. You learnedhow 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 unlimitedsocial networking integration.
During your journey, you also gained some practical experience in drawing into an off-screen graphics context, and using Xcode’s refactoring tool. The refactoring command contains a powerful set of code maintenance tools. Ifyou plan to rename or relocate a method or property, you should make friends with the refactoring tools and other global editing commands. To read more about them, search for “Make Projectwide Changes” in Xcode’sDocumentation and API Reference window.
Sharing posts with your friends and colleagues isn’t the only way iOS apps communicate. In Chapter 3 you wrote an app that uses an Internet URL shortening service. In the next chapter, you’re going to write an app that talks toanother iOS device directly, via Wi-Fi or Bluetooth.



No comments:
Post a Comment