Chapter    7

Social Network Recipes

Social networking is perhaps the strongest trend on the Internet right now. People with an online presence are sharing and consuming content on one or more of the many sites that offer such services. Because forming communities is part of our nature and the Internet platform makes it so easy, we can be sure the trend will last and get stronger as more and more services join this pattern.

In iOS 6, Apple has introduced the Social Framework, making it easy to integrate your apps with social networks. The framework currently offers support for Facebook, Twitter, and the China-based Weibo networks, but the general nature of the API reveals that more is likely to come in future releases.

Note  The Twitter Framework that was introduced in iOS 5 is now deprecated and has been superseded by the Social Framework.

In this chapter we will show you how you can share the content in your app with social networking applications using the convenient UIActivityViewController. We’ll also show you how you can implement more advanced integration with Twitter and Facebook using the new Social Framework.

Recipe 7-1: Sharing Content with the Activity View

Most apps are not in the social networking business. However, many that are not do have content that their users would benefit from sharing. Fortunately, Apple has introduced an API that makes this easy. The UIActivityViewController takes the content you provide and presents the user with relevant “activities,” such as posting to Facebook or Twitter. In this recipe we’ll show you how to set up the UIActivityViewController to share a snippet of text and an URL.

Start by creating a new single-view application. Add a text view, a text field, and a navigation bar to the main view and arrange them so that the main view resembles Figure 7-1. To create the button with the activity icon you see in the upper-right corner of Figure 7-1, drag a bar button to the navigation bar. Then in the Attribute inspector, set the button’s Identifier attribute to “Action.”

The text field is used to input URL links, so set its Placeholder attribute to read “URL to share.” You may also want to change its Keyboard attribute to “URL” to better match the data that’ll be entered there.

9781430245995_Fig07-01.jpg

Figure 7-1.  A simple user interface for sharing some text and a URL

As usual you need to reference the edit controls from your code, so create outlets named messageTextView and urlTextField for the text view and the text field, respectively. You also need to create an action for when the user taps the activity button in the navigation bar. Name the action shareContent.

Setting Up an Activity View Controller

Now that you have the user interface set up you can go to ViewController.m to implement the shareContent action method. Add the following code to initialize and present a UIActivityViewController to share the text and the URL:

- (IBAction)shareContent:(id)sender
{
    NSString *text = self.messageTextView.text;
    NSURL *url = [NSURL URLWithString:self.urlTextField.text];
    NSArray *items = @[text, url];

    UIActivityViewController *vc = [[UIActivityViewController alloc]
        initWithActivityItems:items applicationActivities:nil];
    [self presentViewController:vc animated:YES completion:nil];
}

Note  Apple has introduced a new convenient syntax for creating arrays, which we have used in the preceding code. You can now write @[object1, object2] instead of [NSArray arrayWithObjects:object1, object2, nil] as you had to do before Xcode 4.5.

That’s it! That’s all the code you need for sharing the content on Facebook, Twitter, or any of the other ways Apple provides. You can now build and run the application, enter some text and a URL, and then tap the activity button to bring up the Activity View as shown in Figure 7-2.

9781430245995_Fig07-02.jpg

Figure 7-2.  The built-in Activity View for sharing the content

Once the Activity View is presented you have turned the control of the sharing over to iOS. It uses the accounts that have been set up on the device or asks the user for login details if the accounts haven’t been configured. Users new to Facebook, Twitter, or Weibo can even create new accounts and get a seamless experience. And all of this you get with only about five lines of code.

Figure 7-3 shows an example of when a user of this app has selected to share the content to Twitter. The user is allowed to change the text before sending it to the service. She can also change the account (if more than one is available) from which the tweet will be sent. The Facebook integration has a similar sheet in which the user can lay the final touch. However, due to the nature of Facebook, only one account is allowed on any single device.

9781430245995_Fig07-03.jpg

Figure 7-3.  The Tweet sheet in which the user can lay the last touch to the content before it’s shared

Excluding Activity View Items

When the Activity View is shown, iOS looks at the content and tries to make an educated guess and only show relevant options. For example, the Weibo service only shows if you have a Chinese keyboard installed.

Note  The number of options also depends on the availability of the services, for example, sending text messages isn’t available if you run your app in the iOS Simulator so it won’t show up as an option.

In addition to the system limiting options, there may be situations when you want to cut down on the number of options even more yourself. For example, if you know that your users will never email the content, it makes no sense to have that option.

Fortunately, there is a way to exclude items from the Activity View; by using the excludeActivityTypes property of the UIActivityViewController. For instance, if you want to exclude the Mail and the Copy to Pasteboard services, add the following line of code to the shareContent: method:

- (IBAction)shareContent:(id)sender
{
    NSString *text = self.messageTextView.text;
    NSURL *url = [NSURL URLWithString:self.urlTextField.text];
    NSArray *items = @[text, url];

    UIActivityViewController *vc = [[UIActivityViewController alloc]
        initWithActivityItems:items applicationActivities:nil];
    vc.excludedActivityTypes = @[UIActivityTypeMail, UIActivityTypeCopyToPasteboard];
    [self presentViewController:vc animated:YES completion:nil];
}

If you make the preceding change, and build and run the app again, you’ll see (as shown in Figure 7-4) that both Mail and Copy are no longer visible in the Activity View.

9781430245995_Fig07-04.jpg

Figure 7-4.  The Activity View without Mail and Copy options

A complete set of valid Activity Types and the type of objects you can send to them as data, can be found in Table 7-1.

Table 7-1. Activity Types

Constant Valid Data Items
UIActivityTypePostToFacebook NSString, NSAttributedString, UIImage, AVAsset, NSURL
UIActivityTypePostToTwitter NSString, NSAttributedString, UIImage, AVAsset, NSURL
UIActivityTypePostToWeibo NSString, NSAttributedString, UIImage, AVAsset, NSURL
UIActivityTypeMessage NSString, NSAttributedString, NSURL (with the sms: scheme)
UIActivityTypeMail NSString, UIImage, NSURL (local files, or using the mailto: scheme)
UIActivityTypePrint UIImage, NSData, NSURL (local files only), UIPrintPageRenderer, UIPrintFormatter, UIPrintInfo
UIActivityTypeCopyToPasteboard NSString, UIImage, NSURL, UIColor, NSDictionary
UIActivityTypeAssignToContact UIImage
UIActivityTypeSaveToCameraRoll UIImage, NSURL (for video)

Including Activity View Items

Excluding items from the Activity View was easy; the contrary, including activities not currently supported in iOS is also possible but requires a little more work on your behalf. What you need to do is to create a subclass of UIActivity to implement the service you want to provide.

To see how that is done we’re going to implement a simple logging service that accepts text and URL objects and sends them to stdout (the Standard Output Stream). Start by creating a new subclass of UIActivity with the name MyLogActivity (by going to File image New image File … and selecting Objective-C class).

In the MyLogActivity.h, add the following property to hold the text message that should be sent to stdout:

//
//  MyLogActivity.h
//  Sharing Text
//

#import <UIKit/UIKit.h>

@interface MyLogActivity : UIActivity

@property (strong, nonatomic)NSString *logMessage;

@end

You need to add an image to your project to use as the icon for your activity. For best results, use a 72 × 72 PNG image with only white color in combination with the alpha channel to produce a white icon. (Apple doesn’t allow multi-color icons for custom activities yet.) You can provide an image with no transparency, which is what we did in this example; however, you then get a plain all-white icon (this is shown later in Figure 7-5).

With an icon file imported to your project, you can turn your attention to the implementation of the MyLogActivity class. A UIActivity subclass must provide the following pieces of information:

  • Activity Type: A unique identifier for the activity, which is not shown to the user. Table 7-1 contains constants for the identifiers of the built-in services.
  • Activity Title: The title displayed to the user in the Activity View.
  • Activity Image: The icon displayed along with the title in the Activity View.
  • Can Perform on Activity Items: Whether the activity can handle the provided data objects.

To provide the preceding information, add the following code to the MyLogActivity.m file:

//
//  MyLogActivity.m
//  Sharing Test
//

#import "MyLogActivity.h"

@implementation MyLogActivity

-(NSString *)activityType
{
    return @"MyLogActivity";
}

-(NSString *)activityTitle
{
    return @"Log";
}

-(UIImage *)activityImage
{
    // Replace the file name with the one of the file you imported into your project
    return [UIImage imageNamed:@"log_icon.png"];
}

-(BOOL)canPerformWithActivityItems:(NSArray *)activityItems
{
    for (NSObject *item in activityItems)
    {
        if (![item isKindOfClass:[NSString class]] && ![item isKindOfClass:[NSURL class]])
        {
            return NO;
        }
    }
    return YES;
}

@end

Then, if your activity has been displayed and the user taps its button, the custom activity class will be sent a prepareWithActivityItems: message. In this case we simply append the content for each item and store the result in the logMessage property. Add the method with the following implementation to the MyLogActivity.m file:

-(void)prepareWithActivityItems:(NSArray *)activityItems
{
    self.logMessage = @"";
    for (NSObject *item in activityItems)
    {
        self.logMessage = [NSString stringWithFormat:@"%@ %@",
                           self.logMessage, item];
    }
}

Finally, the activity is asked to perform whatever it’s supposed to do. If your custom activity wants to display an additional user interface, like the Twitter activity does with its Tweet sheet, the custom activity should override the activityViewController method of UIActivity.

However, because we’re not interested in displaying a user interface for our logging service, we instead override the performActivity method, like so:

-(void)performActivity
{
    NSLog(@"%@", self.logMessage);
    [self activityDidFinish:YES];
}

Your custom log service is finished and you can now go back to the shareContent: action method in ViewController.m to setup the View Controller to include the new activity. Make the following changes:

//
//  ViewController.m
//  Sharing Text
//

#import "ViewController.h"
#import "MyLogActivity.h"

@implementation ViewController

// ...

- (IBAction)shareContent:(id)sender
{
    NSString *text = self.messageTextView.text;
    NSURL *url = [NSURL URLWithString:self.urlTextField.text];
    NSArray *items = @[text, url];
    MyLogActivity *myLogService = [[MyLogActivity alloc] init];
    NSArray *customServices = @[myLogService];

    UIActivityViewController *vc = [[UIActivityViewController alloc]
        initWithActivityItems:items applicationActivities:customServices];
    vc.excludedActivityTypes = @[UIActivityTypeMail, UIActivityTypeCopyToPasteboard];
    [self presentViewController:vc animated:YES completion:nil];
}

@end

If you run the code now and tap the activity button, you’ll see, as Figure 7-5 shows, that your Log activity is among the valid options.

9781430245995_Fig07-05.jpg

Figure 7-5.  The Activity View with a custom activity for logging the content on the standard output stream

If you tap the Log icon, you’ll see that your content is sent to the standard output stream, as Figure 7-6 shows.

9781430245995_Fig07-06.jpg

Figure 7-6.  The sharing test app after it’s sent its content to the custom log activity item

Recipe 7-2: Sharing Content Using a Compose View

The Activity View, as you saw in the previous recipe, provides a standardized way to share content through several different channels. In most cases, this is what you want; the user recognizes the user interface from other apps and can choose to share the content in all the ways that makes sense to her. However, there may be situations when you want your app to provide a more direct sharing experience, without forcing the user to select the service you know she’ll pick anyway. For these situations you can use the SLComposeViewController.

The SLComposeViewController currently has support for posting to Facebook, Twitter, and Weibo. In this recipe you build on what you did in Recipe 7-1 and add a button that uses SLComposeViewController to display a compose view populated with the content, and from which the user can post directly to Facebook.

SLComposeViewController resides in the Social Framework, so start by adding it to your project. Then add to the main view a button titled “Post to Facebook.” Make the new user interface look something like Figure 7-7.

9781430245995_Fig07-07.jpg

Figure 7-7.  A user interface with a button for direct targeting of Facebook sharing

Now, for the Touch up inside event of the new button, create an action named shareOnFacebook. Also, import the Social Framework API in your view controller’s header file. It should now look like this:

//
//  ViewController.h
//  Sharing Test
//

#import <UIKit/UIKit.h>
#import <Social/Social.h>

@interface ViewController : UIViewController

@property (weak, nonatomic) IBOutlet UITextView *messageTextView;
@property (weak, nonatomic) IBOutlet UITextField *urlTextField;

- (IBAction)shareContent:(id)sender;
- (IBAction)shareOnFacebook:(id)sender;

@end

Finally, implement the shareOnFacebook: action method. Here’s the code to do that:

//
//  ViewController.m
//  Sharing Text
//

#import "ViewController.h"
#import "MyLogActivity.h"

@implementation ViewController

// ...

- (IBAction)shareOnFacebook:(id)sender
{
    NSString *text = self.messageTextView.text;
    NSURL *url = [NSURL URLWithString:self.urlTextField.text];

    SLComposeViewController *cv =
        [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeFacebook];
    [cv setInitialText:text];
    [cv addURL:url];

    [self presentViewController:cv animated:YES completion:nil];
}

@end

Note  Besides URL objects, the Compose View also supports the adding of image objects via the addImage: method.

As you can see, you initialize the Compose View with the text from the text view and the URL from the text field. If you build and run the project now and tap the Post to Facebook button, you’ll be presented with a Facebook Compose sheet from which you can post the content to your Facebook account. Like with the Activity View, you get all the built-in integration for free, so if a user doesn’t have an account installed she’s asked whether she wants to set one up. Figure 7-8 shows an example of this.

9781430245995_Fig07-08.jpg

Figure 7-8.  If the user has no account for the selected service, iOS asks whether one should be setup

In this example you initialized the Compose View to target Facebook. The code for setting up Twitter or Weibo is nearly identical. The only thing you need to do is to replace the SLServiceTypeFacebook constant with SLServiceTypeTwitter or SLServiceTypeWeibo, respectively, for example:

    SLComposeViewController *cv =
        [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];

Recipe 7-3: Sharing Content Using SLRequest

In Recipes 7-1 and 7-2 you’ve learned how to use the built-in user interfaces for sharing content. For some apps, however, it makes sense to build a completely customized user interface; for example if you’re planning to build the best Twitter or Facebook app ever, or maybe your users aren’t interested in the last touch editing possibility that the built-in Compose views are offering, maybe they would benefit from a more automatic posting experience.

These apps want to use the native API of the respective Social Network service. Fortunately, iOS 6 offers a great help in the SLRequest API. It takes care of the complicated authentication handling for you, making your job a lot easier.

In this recipe we’ll show you how to send a tweet using SLRequest. You’ll need a new single-view application project. You’re also going to use two external frameworks, the Social Framework and the Accounts Framework, which are used by the Social Framework to handle authorizations. Make sure you link their binaries to your project before you go on.

Setting Up the Main View

Start by setting up the user interface so that it resembles Figure 7-9. You’ll need a text view, a button, and a label.

9781430245995_Fig07-09.jpg

Figure 7-9.  A simple user interface for posting to Twitter

Create outlets named textView and statusLabel for the text view and the label, respectively. Also, create an action named shareOnTwitter for the button. Finally, import the Social and the Accounts Framework APIs. Your ViewController.h should now look like this code:

//
//  ViewController.h
//  Twitter Integration
//

#import <UIKit/UIKit.h>
#import <Social/Social.h>
#import <Accounts/Accounts.h>

@interface ViewController : UIViewController

@property (weak, nonatomic) IBOutlet UITextView *textView;
@property (weak, nonatomic) IBOutlet UILabel *statusLabel;

- (IBAction)shareOnTwitter:(id)sender;

@end

Requesting Access to Twitter Accounts

The first thing you’ll need to do when preparing an SLRequest object is to request access to the account type in question. For this you need an ACAccountStore instance. What’s important with this instance is that it needs to stay alive through the whole process of sending requests. The easiest way to do that is to assign it to a property where it’ll be retained.

Add the following property declaration to your ViewController.h file:

//
//  ViewController.h
//  Twitter Integration
//

#import <UIKit/UIKit.h>
#import <Social/Social.h>
#import <Accounts/Accounts.h>

@interface ViewController : UIViewController

@property (weak, nonatomic) IBOutlet UITextView *textView;
@property (weak, nonatomic) IBOutlet UILabel *statusLabel;
@property (strong, nonatomic) ACAccountStore *accountStore;

- (IBAction)shareOnTwitter:(id)sender;

@end

Now you can start implementing shareOnTwitter: action method in ViewController.m. Begin by adding the following code to request access to the Twitter accounts registered on the device:

- (IBAction)shareOnTwitter:(id)sender
{
    self.accountStore = [[ACAccountStore alloc] init];

    ACAccountType *accountType =
        [self.accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];

    [self.accountStore requestAccessToAccountsWithType:accountType options:nil
        completion:^(BOOL granted, NSError *error)
        {
            if (granted)
            {
                //TODO: Get Twitter account and send tweet to it
            }
            else
            {
                //TODO: Handle not granted
            }
        }
    ];
}

What’s interesting here is the requestAccessToAccountsWithType:options:completion: method. It’s asynchronous so you provide a block which will be invoked when the method is finished. You then know whether the request was granted by checking the argument with the same name.

If access to the Twitter accounts was denied, just update the status label. However, the fact that you are in a code block complicates things a bit. The problem is that the completion block may be invoked on any arbitrary thread, but you shouldn’t update user interface from anything but the main thread.

So, to handle that, use the dispatch_async function, which also takes a code block argument, to perform the updating of the status label from the main thread:

[self.accountStore requestAccessToAccountsWithType:accountType options:nil
completion:^(BOOL granted, NSError *error)
{
    __block NSString *statusText = @"";
    if (granted)
    {
         //TODO: Get Twitter account and send tweet to it
    }
    else
    {
         statusText = @"Access to Twitter accounts was not granted";
    }

    dispatch_async(dispatch_get_main_queue(), ^(void)
    {
        self.statusLabel.text = statusText;
    });
}

If access were granted, though, you can use the accountsWithAccountType method of the Account Store to get an array of available accounts. A user may have several Twitter accounts installed on the device and later you will add code that allows her to choose which one to use, but for now you are going to make it simple and grab the first in the list:

- (IBAction)shareOnTwitter:(id)sender
{
    self.accountStore = [[ACAccountStore alloc] init];

    ACAccountType *accountType =
        [self.accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];

    [self.accountStore requestAccessToAccountsWithType:accountType options:nil
     completion:^(BOOL granted, NSError *error)
     {
         __block NSString *statusText = @"";

         if (granted)
         {
             NSArray *availableTwitterAccounts =
                 [self.accountStore accountsWithAccountType:accountType];

             if (availableTwitterAccounts.count == 0)
             {
                 statusText = @"No Twitter accounts available";
             }
             else
             {
                 ACAccount *account = [availableTwitterAccounts objectAtIndex:0];
                 [self sendText:self.textView.text toTwitterAccount:account];
             }
         }
         else
         {
             statusText = @"Access to Twitter accounts was not granted";
         }

         dispatch_async(dispatch_get_main_queue(), ^(void)
         {
             self.statusLabel.text = statusText;
         });
     }];
}

The actual posting to Twitter is performed by the sendText:toTwitterAccount: helper method. It’s actually the core of this recipe so we’ll explain its parts.

First, it builds an SLRequest object for the operation. In this case you are going to ask Twitter to update the status text:

- (void)sendText:(NSString *)text toTwitterAccount:(ACAccount *)twitterAccount
{
    NSURL *requestURL = [NSURL URLWithString:@"http://api.twitter.com/1/statuses/update.json"];
    SLRequest *tweetRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter
        requestMethod:SLRequestMethodPOST URL:requestURL
        parameters:[NSDictionary dictionaryWithObject:text forKey:@"status"]];

    // ...
}

Next, it assigns the account to the request. This step is really important because it’s what allows the Service Framework to handle all the authentication communication with Twitter.

- (void)sendText:(NSString *)text toTwitterAccount:(ACAccount *)twitterAccount
{
    NSURL *requestURL = [NSURL URLWithString:@"http://api.twitter.com/1/statuses/update.json"];
    SLRequest *tweetRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter
        requestMethod:SLRequestMethodPOST URL:requestURL
        parameters:[NSDictionary dictionaryWithObject:text forKey:@"status"]];

    [tweetRequest setAccount:twitterAccount];

    // ...
}

Then it sends the request asynchronously and provides a code block that will be invoked on completion. As you can see, this block uses the same technique for updating the status label as you saw previously.

- (void)sendText:(NSString *)text toTwitterAccount:(ACAccount *)twitterAccount
{
    NSURL *requestURL = [NSURL URLWithString:@"http://api.twitter.com/1/statuses/update.json"];
    SLRequest *tweetRequest = [SLRequest requestForServiceType:SLServiceTypeTwitter
        requestMethod:SLRequestMethodPOST URL:requestURL
        parameters:[NSDictionary dictionaryWithObject:text forKey:@"status"]];

    [tweetRequest setAccount:twitterAccount];

    [tweetRequest performRequestWithHandler:
     ^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
     {
         __block NSString *status;

         if ([urlResponse statusCode] == 200)
         {
             status = [NSString stringWithFormat:@"Tweeted successfully to %@",
                 twitterAccount.accountDescription];
         }
         else
         {
             status = @"Error occurred!";
             NSLog(@"%@", error);
         }

         dispatch_async(dispatch_get_main_queue(), ^(void)
         {
             self.statusLabel.text = status;
         });
     }];
}

Note  As shown in the preceding method, you can evaluate the results of a SLRequest by checking the statusCode of the urlResponse. If this value is 200, the request was successfully completed. Otherwise, there was some sort of error. Refer to the Twitter API at https://dev.twitter.com/docs/error-codes-responses for specific details on all the various error codes.

If you build and run now (and have at least one Twitter account set up on the device) you should, as Figure 7-10 shows, be able to tweet from your app.

9781430245995_Fig07-10.jpg

Figure 7-10.  A tweet successfully sent from the app

Handling Multiple Accounts

What if the user has more than one Twitter account setup on the device? Currently this app uses the first in the list, but you are going to add a feature to it that allows the user to actually choose which to use.

You are going to use an Alert View to present the available accounts from which the user will pick one. So the first thing you need to do is to promote the availableTwitterAccounts array, which is currently a local variable in the shareOnTwitter: method, into an instance variable. You’ll need to reference that array from the delegate method of the Alert View. You’ll also need to make the view controller an Alert View Delegate by adding the UIAlertViewDelegate protocol to your view controller class. To do these things, add the following code to your ViewController.h file:

//
//  ViewController.h
//  Twitter Integration
//

#import <UIKit/UIKit.h>
#import <Social/Social.h>
#import <Accounts/Accounts.h>

@interface ViewController : UIViewController<UIAlertViewDelegate>
{
    @private
    NSArray *availableTwitterAccounts;
}

@property (weak, nonatomic) IBOutlet UITextView *textView;
@property (weak, nonatomic) IBOutlet UILabel *statusLabel;
@property (strong, nonatomic) ACAccountStore *accountStore;

- (IBAction)shareOnTwitter:(id)sender;

@end

And in the shareOnTwitter: method in ViewController.m, make the following changes:

- (IBAction)shareOnTwitter:(id)sender
{
    self.accountStore = [[ACAccountStore alloc] init];

    ACAccountType *accountType =
        [self.accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];
    [self.accountStore requestAccessToAccountsWithType:accountType options:nil
     completion:^(BOOL granted, NSError *error)
     {
         __block NSString *statusText = @"";

         if (granted)
         {
             availableTwitterAccounts= [self.accountStore accountsWithAccountType:accountType];

             if (availableTwitterAccounts.count == 0)
             {
                 statusText = @"No Twitter accounts available";
             }
             if (availableTwitterAccounts.count == 1)
             {
                 ACAccount *account = [availableTwitterAccounts objectAtIndex:0];
                 [self sendText:self.textView.text toTwitterAccount:account];
             }
             else if (availableTwitterAccounts.count > 1)
             {
                 dispatch_async(dispatch_get_main_queue(), ^(void)
                 {
                     UIAlertView *alert =
                         [[UIAlertView alloc] initWithTitle:@"Select Twitter Account"
                             message:@"Select the Twitter account you want to use."
                             delegate:self
                             cancelButtonTitle:@"Cancel"
                             otherButtonTitles:nil];

                     for (ACAccount *twitterAccount in availableTwitterAccounts)
                     {
                         [alert addButtonWithTitle:twitterAccount.accountDescription];
                     }

                     [alert show];
                 });
             }
         }
         else
         {
             statusText = @"Access to Twitter accounts was not granted";
         }

         dispatch_async(dispatch_get_main_queue(), ^(void)
         {
             self.statusLabel.text = statusText;
         });
     }];
}

Note that you are wrapping the Alert View in the same dispatch_async() call as you did with the updating of the status label. The reason is the same, Alert Views must run on the main thread or you may experience problems.

The only thing left now is to respond to when the user selects one of the accounts in the Alert View. Do that by adding the following delegate method:

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
    if (buttonIndex == 0)
    {
        // User Canceled
        return;
    }

    NSInteger indexInAvailableTwitterAccountsArray = buttonIndex - 1;
    ACAccount *selectedAccount = [availableTwitterAccounts
                                  objectAtIndex:indexInAvailableTwitterAccountsArray];
    [self sendText:self.textView.text toTwitterAccount:selectedAccount];
}

Before you build and run your app again, make sure you have more than one Twitter account set up on the device (or the iOS Simulator). Then the next time you tap the Tweet button, you’ll get to choose which of your accounts to send the tweet to. Figure 7-11 shows an example of the Alert View.

9781430245995_Fig07-11.jpg

Figure 7-11.  An Alert View that allows the user to pick the Twitter account to post to

Recipe 7-4: Retrieving Tweets

Now that you have covered several different methods with which you can post updates to Twitter, you can apply the concepts used in the previous recipe, revolving around the SLRequest class, to build an application that can acquire and display tweets.

You build a simple Twitter reader app that allows the user to view the recent tweets from the public Twitter timeline, or from the Twitter accounts she has installed on the device. You set up an application consisting of a Navigation Controller in combination with Table View Controllers for the basic tweet navigation.

Setting Up a Navigation-Based Application

Start by setting up the new project from scratch. This time use the Empty Application template. You can name the project “My Tweet Reader.”

As in the previous recipe you use both the Social and the Accounts Framework so go ahead and link those to your project.

Next, you’re going to create the main view controller. It displays a list of Twitter Feeds, so create a new UITableViewController class with the name MainTableViewController. You do not need a .xib file to design any user interface, so be sure the option “With XIB for user interface” is unchecked.

Before you go on to implement the new view controller, you’re going to go to the App Delegate to hook it up with a Navigation Controller. Open the AppDelegate.h file and add a property for the Navigation Controller:

//
//  AppDelegate.h
//  My Tweet Reader
//

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) UINavigationController *navigationController;

@end

Then, in AppDelegate.m make the following changes:

//
//  AppDelegate.m
//  My Tweet Reader
//

#import "AppDelegate.h"
#import "MainTableViewController.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    UITableViewController *mainViewController =
        [[MainTableViewController alloc] initWithStyle:UITableViewStyleGrouped];
    self.navigationController =
        [[UINavigationController alloc] initWithRootViewController:mainViewController];
    self.window.rootViewController = self.navigationController;
    [self.window makeKeyAndVisible];
    return YES;
}

// ...

@end

Displaying Available Feeds

Now, with the Navigation Controller and the Main Table View Controller hooked up, you can start implementing the Table View. It displays a number of Twitter feeds that the user may choose to view. In this recipe, you add the public Twitter feed and the feeds of the currently installed Twitter accounts.

To retrieve the installed accounts, you need an Account Store and an array to store the available accounts. Go to MainTableViewController.h and add the following declarations:

//
//  MainTableViewController.h
//  My Tweet Reader
//

#import <UIKit/UIKit.h>
#import <Accounts/Accounts.h>

@interface MainTableViewController : UITableViewController

@property (strong, nonatomic) ACAccountStore *accountStore;
@property (strong, nonatomic) NSArray *twitterAccounts;

@end

Now, go to MainTableViewController.m and add the following code to the viewDidLoad method:

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.navigationItem.title = @"My Twitter Reader";
    self.accountStore = [[ACAccountStore alloc] init];
    [self retrieveAccounts];
}

In the retrieveAccounts helper method we’re going to ask for permissions to access the Twitter accounts. Here’s the implementation, which you might recognize from the previous recipe:

- (void)retrieveAccounts
{
    ACAccountType *accountType =
        [self.accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifierTwitter];

    [self.accountStore requestAccessToAccountsWithType:accountType options:nil
                                            completion:^(BOOL granted, NSError *error)
     {
         if (granted)
         {
             self.twitterAccounts = [self.accountStore accountsWithAccountType:accountType];
             dispatch_async(dispatch_get_main_queue(), ^(void)
                            {
                                [self.tableView reloadData];
                            });
         }
     }];
}

Next, implement the following Table View Data Source delegate methods that let the Table View know how many cells it should display. You are use only one section in which you display the public feed plus the available Twitter accounts:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return 1 + self.twitterAccounts.count;
}

Next, you’re going to create a Table View Cell class that displays the items in the Table View. Create a new UITableViewCell class and name it TwitterFeedCell. Then open the header file of the new class and add the following code:

//
//  TwitterFeedCell.h
//  My Tweet Reader
//

#import <UIKit/UIKit.h>
#import <Accounts/Accounts.h>

extern NSString * const TwitterFeedCellId;

@interface TwitterFeedCell : UITableViewCell

@property (strong, nonatomic)ACAccount *twitterAccount;

@end

You’re going to set up a very simple cell with the default look. However, you’re going to add a disclosure indicator that signals to the user that there’s a detailed view waiting if she taps the cell. Open the TwitterFeedCell.m file and make the following change to the initWithStyle:reuseIdentifier: method that Xcode generated for you:

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        // Initialization code
        self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }
    return self;
}

You’ll also set up the reuse constant that you’ll use later to “dequeue” cells from the cell cache of the Table View. Add the following lines of code:

//
//  TwitterFeedCell.m
//  My Tweet Reader
//

#import "TwitterFeedCell.h"

NSString * const TwitterFeedCellId = @"TwitterFeedCell";

@implementation TwitterFeedCell

// ...

@end

Finally, you’ll add a custom setter method for the twitterAccount property. The setter sets the property, but also updates the label text of the cell. If the account property is nil, you are going to assume that the cell is representing the public Twitter feed. Here’s the implementation of the setter:

- (void)setTwitterAccount:(ACAccount *)account
{
    _twitterAccount = account;
    if (_twitterAccount)
    {
        self.textLabel.text = _twitterAccount.accountDescription;
    }
    else
    {
        self.textLabel.text = @"Public";
    }
}

With the cell class finished you can go back to the Main Table View Controller to finish the implementation. First, you’ll need to register the TwitterFeedCell class with the Table View. You can do that in the viewDidLoad method, like so:

//
//  MainTableViewController.m
//  My Tweet Reader
//

#import "MainTableViewController.h"
#import "TwitterFeedCell.h"

@implementation MainTableViewController

// ...

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self.tableView registerClass:TwitterFeedCell.class
        forCellReuseIdentifier:TwitterFeedCellId];

    self.navigationItem.title = @"Twitter Feeds";
    self.accountStore = [[ACAccountStore alloc] init];
    [self retrieveAccounts];
}

// ...

@end

Now, you can implement the creation of the cells in the tableView:cellForRowAtIndexPath: delegate method:

- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    TwitterFeedCell *cell =
        [tableView dequeueReusableCellWithIdentifier:TwitterFeedCellId forIndexPath:indexPath];
    // Configure the cell...
    if (indexPath.row == 0)
    {
        // Public Feed
        cell.twitterAccount = nil;
    }
    else
    {
        cell.twitterAccount = [self.twitterAccounts objectAtIndex:indexPath.row - 1];
    }

    return cell;
}

If you build and run your app now, you should see a screen similar to the one in Figure 7-12.

9781430245995_Fig07-12.jpg

Figure 7-12.  A simple Twitter Reader displaying three available Twitter Feeds

Displaying Tweets

With the main view in place and working you can move on to add another view to display the selected timeline. You also implement this view with a Table View, so begin by creating a new UITableViewController class; this time with the name TweetTableViewController. Again you do not need an .xib file for the user interface, so leave that option unselected.

The new view takes a Twitter account and displays its timeline in the Table View. You’ll start by setting up the header file. Add the following to TweetTableViewController.h:

//
//  TweetTableViewController.h
//  My Tweet Reader
//

#import <UIKit/UIKit.h>
#import <Accounts/Accounts.h>
#import <Social/Social.h>

@interface TweetTableViewController : UITableViewController

@property (strong, nonatomic) ACAccount *twitterAccount;
@property (strong, nonatomic) NSMutableArray *tweets;

-(id)initWithTwitterAccount:(ACAccount *)account;

@end

In the implementation file you’ll start by implementing the initialization methods:

- (id)initWithStyle:(UITableViewStyle)style
{
    self = [super initWithStyle:style];
    if (self) {
        // Custom initialization
        self.tweets = [[NSMutableArray alloc] initWithCapacity:50];
    }
    return self;
}

-(id)initWithTwitterAccount:(ACAccount *)account
{
    self = [self initWithStyle:UITableViewStylePlain];
    if (self) {
        self.twitterAccount = account;
    }
    return self;
}

As you’ll see later, you use the initWithTwitterAccount: method to set up the view controller. However, by separating the initialization in two methods, you make sure the view controller works in a default setup scenario as well (using the initWithStyle: method).

Now, in the viewDidLoad method you update the title of the Navigation Bar and order a retrieving of the timeline.

- (void)viewDidLoad
{
    [super viewDidLoad];

    if (self.twitterAccount)
    {
        self.navigationItem.title = self.twitterAccount.accountDescription;
    }
    else
    {
        self.navigationItem.title = @"Public Tweets";
    }
    [self retrieveTweets];
}

The retrieveTweets method holds the essence of this recipe. It’s the one that retrieves the tweets from the given Twitter feed. The Twitter feed can be either the home timeline of a Twitter account, or the public timeline of Twitter. Here is the method’s implementation:

-(void)retrieveTweets
{
    [self.tweets removeAllObjects];

    SLRequest *request;

    if (self.twitterAccount)
    {
        // Get home timeline of the Twitter account
        NSURL *requestURL =
            [NSURL URLWithString:@"http://api.twitter.com/1/statuses/home_timeline.json"];
        request = [SLRequest requestForServiceType:SLServiceTypeTwitter
            requestMethod:SLRequestMethodGET URL:requestURL parameters:nil];
        [request setAccount:self.twitterAccount];
    }
    else
    {
        // Get the public timeline of Twitter
        NSURL *requestURL =
            [NSURL URLWithString:@"http://api.twitter.com/1/statuses/public_timeline.json"];
        request = [SLRequest requestForServiceType:SLServiceTypeTwitter
            requestMethod:SLRequestMethodGET URL:requestURL parameters:nil];
    }

    [request performRequestWithHandler:
    ^(NSData *responseData, NSHTTPURLResponse *urlResponse, NSError *error)
     {
         if ([urlResponse statusCode] == 200)
         {
             NSError *jsonParsingError;
             self.tweets = [NSJSONSerialization JSONObjectWithData:responseData
                 options:0 error:&jsonParsingError];
         }
         else
         {
             NSLog(@"HTTP response status: %i ", [urlResponse statusCode]);
         }
         dispatch_async(dispatch_get_main_queue(), ^(void)
                        {
                            [self.tableView reloadData];
                        });
     }];
}

Note that the response from Twitter comes in a JSON format, which you decode using the NSJSONSerialization class. The result is an array of dictionaries, one for each tweet.

Note  If you are unsure of the format of your JSON data retrieved from Twitter, you can simply take the URL used to make your request and enter it in your web browser. You will receive a text view of the exact same data your code would have gotten. It can be a little difficult to read due to a lack of space formatting, but you can look for the necessary layers of content needed to build your application.

With the data in place you can start implementing the Table View to display it. As in the main Table View, you are going to have only one section. It contains the recent tweets, so make the following changes to the numberOfSectionsInTableView: and the tableView:numberOfRowsInSection: methods:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return self.tweets.count;
}

Because the cells in this Table View have a slightly different look and content than the ones in the main Table View, you need to create a new UITableViewCell subclass. This time, name the class TweetCell.

Open TweetCell.h and make the following changes:

//
//  TweetCell.h
//  My Tweet Reader
//

#import <UIKit/UIKit.h>

NSString * const TweetCellId = @"TweetCell";

@interface TweetCell : UITableViewCell

@property (strong, nonatomic)NSDictionary *tweetData;

@end

This cell type uses the standard look with a subtitle and a disclosure indicator. Make these changes to the initWithStyle:reuseIdentifier: method (in the TweetCell.m file):

- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
    self = [super initWithStyle:UITableViewCellStyleSubtitlereuseIdentifier:reuseIdentifier];
    if (self)
    {
        // Initialization code
        self.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }
    return self;
}

The cell updates its labels when it receives new tweet data. To accomplish that you’ll add a custom setter method for the tweetData property, like so:

-(void)setTweetData:(NSDictionary *)tweetData
{
    _tweetData = tweetData;
    // Update cell
    NSDictionary *userData = [_tweetData objectForKey:@"user"];
    self.textLabel.text = [userData objectForKey:@"name"];
    self.detailTextLabel.text = [_tweetData objectForKey:@"text"];
}

Now, let’s complete the implementation of the Table View. Go back to TweetTableViewController.m and make the following changes:

//
//  TweetTableViewController.m
//  My Tweet Reader
//

#import "TweetTableViewController.h"
#import "TweetCell.h"

@implementation TweetTableViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self.tableView registerClass:TweetCell.class forCellReuseIdentifier:TweetCellId];

    if (self.twitterAccount)
    {
        self.navigationItem.title = self.twitterAccount.accountDescription;
    }
    else
    {
        self.navigationItem.title = @"Public Tweets";
    }
    [self retrieveTweets];
}

// ...

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    TweetCell *cell = [tableView dequeueReusableCellWithIdentifier:TweetCellIdforIndexPath:indexPath];
    // Configure the cell...
    cell.tweetData = [self.tweets objectAtIndex:indexPath.row];

    return cell;
}

// ...

@end

You’re done for now with the Tweet Table View, let’s go back to the main Table View and implement the code that displays the new view controller. Open MainTableViewController.m and make the following changes to the tableView:didSelectRowAtIndexPath: delegate method:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Navigation logic may go here. Create and push another view controller.
    ACAccount *account = nil;
    if (indexPath.row > 0)
    {
        account = [self.twitterAccounts objectAtIndex:indexPath.row - 1];
    }
    TweetTableViewController *detailViewController =
        [[TweetTableViewController alloc] initWithTwitterAccount:account];

    // Pass the selected object to the new view controller.
    [self.navigationController pushViewController:detailViewController animated:YES];
}

Don’t forget to import the TweetTableViewController.h file:

#import "TweetTableViewController.h"

It’s time again to build and run your app. This time you should be able to select one of the timelines and get a list of the most recent tweets. Figure 7-13 shows an example of this new Table View.

9781430245995_Fig07-13.jpg

Figure 7-13.  A simple tweet reader app displaying the public tweets feed of Twitter

Showing Individual Tweets

When the user taps a tweet cell, you want to display a detailed view of that tweet. This will be a simple view controller, so create a new UIViewController subclass with the name TweetViewController. This time, however, you’ll build its user interface in Interface Builder so make sure to select the “With XIB for user interface” option.

Open the new TweetViewController.xib file to bring up Interface Builder. The first thing you’re going to do is to make sure you design the user interface with the Navigation Bar in mind. Select the view and go to the Attribute inspector. In the Simulated Metrics section, change the value of the Top Bar attribute from “None” to “Navigation Bar” (see Figure 7-14). This displays a Navigation Bar in the view, which is helpful when you’re creating your layout.

9781430245995_Fig07-14.jpg

Figure 7-14.  Simulating a Navigation Bar to help in user interface design

While you’re still in the Attribute inspector for the view, go to the View section and change the background color to Light Grey. It makes a better contrast against the text view that you’ll add in a minute.

You’re going to create a user interface like the one in Figure 7-15. You’ll need an image view, four labels, and a text view to display the actual tweet.

9781430245995_Fig07-15.jpg

Figure 7-15.  A user interface for showing individual tweets

When you’re done with the layout of the user interface, create outlets for all the components. Give the outlets the following names:

  • userImageView
  • userNameLabel
  • userScreenNameLabel
  • userDescriptionLabel
  • tweetTextView
  • retweetCountLabel

Now, add the following declarations to the header file of the new class:

//
//  TweetViewController.h
//  Testing Retrieving Tweets
//

#import <UIKit/UIKit.h>
@interface TweetViewController : UIViewController

@property (weak, nonatomic) IBOutlet UIImageView *userImageView;
@property (weak, nonatomic) IBOutlet UILabel *userNameLabel;
@property (weak, nonatomic) IBOutlet UILabel *userScreenNameLabel;
@property (weak, nonatomic) IBOutlet UILabel *userDescriptionLabel;
@property (weak, nonatomic) IBOutlet UITextView *tweetTextView;
@property (weak, nonatomic) IBOutlet UILabel *retweetCountLabel;

@property (strong, nonatomic) NSDictionary *tweetData;

-(id)initWithTweetData:(NSDictionary *)tweetData;

@end

The implementation of the initializer is pretty straightforward:

//
//  TweetViewController.m
//  Testing Retrieving Tweets
//

#import "TweetViewController.h"

@implementation TweetViewController

-(id)initWithTweetData:(NSDictionary *)tweetData
{
    self = [super initWithNibName:nil bundle:nil];
    if (self) {
        _tweetData = tweetData;
    }
    return self;
}

// ...

@end

If someone changes the tweetData property, you need to update the view with the new data. Therefore, add a custom setter with the following implementation:

-(void)setTweetData:(NSDictionary *)tweetData
{
    _tweetData = tweetData;
    [self updateView];
}

The updateView helper method takes the tweet data and updates the controls in the view, including the image view:

-(void)updateView
{
    NSDictionary *userData = [self.tweetData objectForKey:@"user"];

    NSString *imageURLString = [userData objectForKey:@"profile_image_url"];
    NSURL *imageURL = [NSURL URLWithString:imageURLString];
    NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
    self.userImageView.image = [UIImage imageWithData:imageData];
    self.userNameLabel.text = [userData objectForKey:@"name"];
    self.userScreenNameLabel.text = [userData objectForKey:@"screen_name"];
    self.userDescriptionLabel.text = [userData objectForKey:@"description"];

    self.tweetTextView.text = [self.tweetData objectForKey:@"text"];
    self.retweetCountLabel.text = [NSString stringWithFormat:@"Retweet Count: %@",
        [self.tweetData objectForKey:@"retweet_count"]];
}

Finally, in the viewDidLoad method, set the title of the Navigation Bar and update the other controls with the tweet data:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    self.navigationItem.title = @"Tweet";
    [self updateView];
}

Your Tweet View Controller is now ready to use. Let’s implement the code that will display it. Go back to TweetTableViewController.m and add the following implementation of the tableView:didSelectRowAtIndexPath: method

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Navigation logic may go here. Create and push another view controller.
    NSDictionary *tweetData = [self.tweets objectAtIndex:indexPath.row];
    TweetViewController *detailViewController =
        [[TweetViewController alloc] initWithTweetData:tweetData];
    // ...
    // Pass the selected object to the new view controller.
    [self.navigationController pushViewController:detailViewController animated:YES];
}

Finally, all you have left to complete this recipe is to import the Tweet View Controller:

#import "TweetViewController.h"

With the individual tweet view, your app is finished. You can now select a feed, then a tweet, and see its detail as shown in Figure 7-16.

9781430245995_Fig07-16.jpg

Figure 7-16.  A view displaying a tweet

Summary

In this chapter you have gone through the three major ways in which you can implement Social Networking features in iOS 6; you’ve seen UIActivityViewController, which is the easiest way to share your app’s content, you’ve looked at a more direct way to target Twitter, Facebook, or Weibo using the SLComposeViewController, and finally you’ve seen how you can use SLRequest to utilize every aspect of the native APIs of these social networks. Social networking is a thing of the past, the present, as well as the future, and iOS 6 has the tools you need to help your users keep sharing and connecting, even when they’re using your apps.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset