Chapter     8

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 in.

In iOS 6, Apple introduced the Social framework, making it easy to integrate your apps with social networks. In iOS 7, the framework has been expanded to support Vimeo and Flicker as well as Facebook, Twitter, and the China-based Weibo networks.

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 8-1: Sharing Content with the Activity View

Most apps are not in the social networking business. However, many that are not do have content their users would benefit from sharing. Fortunately, Apple has 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. UIActivityViewController is a convenient view class that provides a configurable interface. The interface allows you to share content on social networks or take advantage of standard services such as sending emails or even sharing files with nearby iOS 7 devices using Airdrop. The UIActivityViewController interface is the same one that appears when you click the “share” button in Safari. The only difference is the configuration. In this recipe, we’ll show you how to set up UIActivityViewController to share a snippet of text and a URL.

Start by creating a new single view application. Select the Main.storyboard and 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 8-1. To create the button with the activity icon you see in the upper-right corner of Figure 8-1, drag a bar button to the navigation bar. In the attribute inspector, set the button’s Identifier attribute to “Action.”

9781430259596_Fig08-01.jpg

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

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

As usual, you need to reference the edit controls from your code, so create the following respective outlets and actions:

  • Outlet: messageTextView
  • Outlet: urlTextField
  • Navigation button 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 code to the shareContent: method to initialize and present a UIActivityViewController to share the text and the URL, as shown in Listing 8-1.

Listing 8-1.  Implementation of 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];
    [self presentViewController:vc animated:YES completion:nil];
}

Note   In this bit of code we are using literals for creating arrays. As of Xcode 4.5, you can write arrays as @[object1, object2] instead of [NSArray arrayWithObjects:object1, object2, nil].

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 8-2.

9781430259596_Fig08-02.jpg

Figure 8-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, Vimeo, Flickr, and other social media networks can create new accounts and get a seamless experience, depending on location. You get all of this with only about five lines of code.

Figure 8-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. The user 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.

9781430259596_Fig08-03.jpg

Figure 8-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 parse the content to show only relevant options. For example, the Weibo service shows only 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 appear as an option.

In addition to the system limiting options, there might be situations where you want to reduce 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, you can 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 code to the shareContent: method, as shown in Listing 8-2.

Listing 8-2.  Updating the shareContent: method to exclude copy and mail options

- (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 change shown in Listing 8-2 and build and run the app again, you’ll see (as shown in Figure 8-4) that both “Mail” and “Copy” are no longer visible in the activity view.

9781430259596_Fig08-04.jpg

Figure 8-4. The activity view without “Mail” and “Copy” options

Table 3-1 shows a complete set of valid activity types and the type of objects you can send to them as data.

Table 3-1. Activity Types

Constant

Valid Data Items

Usage

UIActivityTypePostToFacebook NSString, NSAttributedString, UIImage, AVAsset, NSURL For posting mostly text and images to Facebook
UIActivityTypePostToTwitter NSString, NSAttributedString, UIImage, AVAsset, NSURL For posting mostly text and images to Twitter
UIActivityTypePostToWeibo NSString, NSAttributedString, UIImage, AVAsset, NSURL For posting mostly text and images to China-based Weibo
UIActivityTypeMessage NSString, NSAttributedString, NSURL (with the sms: scheme) For sending an SMS message
UIActivityTypeMail NSString, UIImage, NSURL (local files, or using the mailto: scheme) For adding strings, images, and URLs to email messages
UIActivityTypePrint UIImage, NSData, NSURL (local files only), UIPrintPageRenderer, UIPrintFormatter, UIPrintInfo For sending a multitude of data objects to an air printer
UIActivityTypeCopyToPasteboard NSString, UIImage, NSURL, UIColor, NSDictionary For copying content to the Clipboard
UIActivityTypeAssignToContact UIImage For assigning an image to a contact
UIActivityTypeSaveToCameraRoll UIImage, NSURL (for video) For adding images or video URLs to a camera roll
UIActivityTypeAddToReadingList NSURL For adding a URL to a reading list
UIActivityTypePostToFlickr UIImage, ALAsset, NSURL (With Image and file scheme), NSData (Image Data) For posting images or URLs of images to Flickr
UIActivityTypePostToVimeo ALAsset, NSURL (File scheme and point to video), NSData (Video data) For mostly posting videos or URLs to Vimeo
UIActivityTypePostToTencentWeibo NSString, NSAttributedString, UIImage, AVAsset, NSURL (data for activity items) For posting strings, URLs, and images to Tencent Weibo
UIActivityTypeAirDrop NSString, NSAttributedString, UIImage, AVAsset, NSURL (data for activity items), NSArray (provided contents are first five items), NSDictionary (provided contents are first five items) For sharing a multitude of files with users in close proximity

Including Activity View Items

Excluding items from the activity view is easy. In contrast, including activities not currently supported in iOS requires a little more work on your behalf. You will need to create a subclass of UIActivity, an abstract class used in conjunction with UIActivityViewController, to present the user with a custom service.

To show you how that is done, we will 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 arrow.jpg New arrow.jpg File . . . and selecting Objective-C class).

In the MyLogActivity.h, add the property shown in Listing 8-3 to hold the text message that should be sent to stdout.

Listing 8-3.  MyLogActivity.h with the logMessage property

//
//  MyLogActivity.h
//  Recipe 8-1 Sharing content with the Activity View
//

#import <UIKit/UIKit.h>

@interface MyLogActivity : UIActivity

@property (strong, nonatomic)NSString *logMessage;

@end

Add an image to your project to use as the icon for your activity. For best results, use a 72 × 72 dpi 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 will get a plain, all-white icon (this is shown later in Figure 8-5). Create an image asset for this icon and title it “nscup” or whatever you choose to call your image. To do this, select the image.xcassets file from the project navigator and click the “+” button in the lower-left corner of the editor window. This will add a new asset to the .xcassets file. Click the asset to rename it and drag your image to one of the image placeholders. You can find more details on this procedure in Recipe 1-8 in Chapter 1.

With an icon asset created in 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 3-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, modify the MyLogActivity.m file, as shown in Listing 8-4.

Listing 8-4.  Modifying the MyLogActivity implementation to include necessary components

//
//  MyLogActivity.m
//  Recipe 8-1 Sharing content with the Activity View
//

#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:@"nscup.png"];
}

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

@end

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 implementation shown in Listing 8-5 to the MyLogActivity.m file.

Listing 8-5.  Implementation of the prepareWithActivityitems: method

-(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, as shown in Listing 8-6.

Listing 8-6.  Implementation of the performActivity override method

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

Your custom log service is finished and you can now return to the shareContent: action method in ViewController.m to set up the view controller to include the new activity. The new changes are shown in Listing 8-7.

Listing 8-7.  Adding support for the new activity in the viewController.m file

//
//  ViewController.m
//  Recipe 8-1 Sharing content with the Activity View
//

#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 that your log activity is among the valid options, as shown in Figure 8-5.

9781430259596_Fig08-05.jpg

Figure 8-5. The activity view with a custom activity for logging the content on the standard output stream

If you tap the “Log” icon, your content is sent to the standard output stream, as Figure 8-6 shows.

9781430259596_Fig08-06.jpg

Figure 8-6. The sharing test app after it sends its content to the custom log activity item

Recipe 8-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 make sense to them. However, what if you want to skip that step and take the user straight to the place where they compose the tweet? For these situations, you can use SLComposeViewController, a class that presents a standard view to the user where they can compose a post in one of the supported social networks. This is the same compose view you would see if you tap the “Twitter” button in the activity view.

The SLComposeViewController currently has support for posting to Facebook, Twitter, and Weibo. In this recipe, you build on what you did in Recipe 8-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 (refer to Recipe 1-2 in Chapter 1). Then add a button titled “Post to Facebook” to the main view. Make the new user interface look something like Figure 8-7.

9781430259596_Fig08-07.jpg

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

For the new button, create an action named “shareOnFacebook.” Also, import the Social framework API into your view controller’s header file. The ViewController.h file should look like Listing 8-8.

Listing 8-8.  The complete ViewController.h file

//
//  ViewController.h
//  Recipe 8-1 Sharing content with the Activity View
//

#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. Listing 8-9 shows the implementation file.

Listing 8-9.  Modification of the shareOnFacebook: method in the ViewController.m file

//
//  ViewController.m
//  Recipe 8-1 Sharing content with the Activity View
//

#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 by means of 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. As with the activity view, you get all the built-in integration for free, so if a user doesn’t have an account installed she will be asked whether she wants to set one up. Figure 8-8 shows an example of this.

9781430259596_Fig08-08.jpg

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

In this example, you initialized the compose view to target Facebook. The code for setting up Twitter or Weibo is nearly identical. The only action you need to take is to replace the SLServiceTypeFacebook constant with SLServiceTypeTwitter or SLServiceTypeWeibo , respectively, as shown in the following example:

SLComposeViewController *cv =
    [SLComposeViewController composeViewControllerForServiceType: SLServiceTypeTwitter];

Recipe 8-3: Sharing Content Using SLRequest

In Recipes 8-1 and 8-2 you 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 are planning to build the best Twitter or Facebook app ever, or if your users aren’t interested in the last-touch editing possibility that the built-in compose views are offering, perhaps 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 7 offers great help in the SLRequest class. 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 also will use two external frameworks, the Social framework and the Accounts framework, which is used by the Social framework to handle authorizations. Make sure you add these frameworks to your project before you continue.

Setting Up the Main View

Start by setting up the user interface so that it resembles Figure 8-9. You’ll need a text view, a button, and a label. Make the label centered, with two lines, and word wrapped.

9781430259596_Fig08-09.jpg

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

Create the following outlets and actions:

  • Outlet: textView
  • Outlet: statusLabel
  • Button action: shareOnTwitter

Finally, import the Social and the Accounts framework APIs. Your ViewController.h should now look like Listing 8-10.

Listing 8-10.  The modified ViewController.h file

//
//  ViewController.h
//  Recipe 8-3 Sharing Content Using SLRequest
//

#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 action you need to take 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 entire 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, as shown in bold in Listing 8-11, to the ViewController.h file.

Listing 8-11.  Adding an ACAccountStore property to the ViewController.h file

//
//  ViewController.h
//  Recipe 8-3 Sharing Content Using SLRequest
//

#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 the shareOnTwitter: action method in ViewController.m. Begin by adding the code in bold in Listing 8-12 to request access to the Twitter accounts registered on the device.

Listing 8-12.  Completing the implementation of the shareOnTwitter: method

- (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 in Listing 8-12 is the requestAccessToAccountsWithType:options:completion: method. It’s asynchronous, so you should 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 account was denied, simply 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 might be invoked on any arbitrary thread, but you shouldn’t update the user interface from anything but the main thread. 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, as shown in Listing 8-13.

Listing 8-13.  Adding code to the requestAccessToAccountsWithType:options:completion: method to update the label

[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;
    });
}

Now we’re going to switch gears and implement the sendText:toTwitterAccount: helper method. This method is what actually posts to Twitter. It’s the main lesson of this recipe, so we’ll explain its parts.

First, it builds an SLRequest object for the operation. In this case, you will ask Twitter to update the status text, as shown in Listing 8-14.

Listing 8-14.  Adding a Twitter request to the sendText:toTwitterAccount: method

- (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. Listing 8-15 shows the addition of the new code in bold.

Listing 8-15.  Adding code to Listing 8-14 to set the Twitter account

- (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];

    // ...
}

Finally, the method sends the request asynchronously and provides a code block that will be invoked on completion. This addition is shown in Listing 8-16.

Listing 8-16.  Adding code to Listing 8-15 for sending an asynchronous request

- (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 an 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 about all the various error codes.

Now that the sendText:toTwitterAccount is finished, you’ll complete the “TODO” case from Listing 8-12. Here you will take advantage of the new method, although you can use the accountsWithAccountType method of the account store to get an array of available accounts. A user might have several Twitter accounts installed on the device. For now, you will make it simple and grab the first in the list, as shown in Listing 8-17. Later, you will add code that allows a user to choose which Twitter account to use.

Listing 8-17.  Completing the “granted if” statement in the requestToAccountsWithType: method completion block

- (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;
         });
     }];
}

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

9781430259596_Fig08-10.jpg

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

Handling Multiple Accounts

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

You will use an alert view to present the available accounts from which the user will pick. This will require the following steps:

  1. Promote the availableTwitterAccounts array, which is currently a local variable in the shareOnTwitter: method, into an instance variable.
  2. Reference that array from the delegate method of the alert view.
  3. Make the view controller an alert view delegate by adding the UIAlertViewDelegate protocol to your view controller class.

To perform these steps in code, add the bold items in Listing 8-18 to your ViewController.h file.

Listing 8-18.  Setting up the view controller header to allow for account selection alert view

//
//  ViewController.h
//  Recipe 8-3 Sharing Content Using SLRequest
//

#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 changes shown in Listing 8-19.

Listing 8-19.  Updating the shareOnTwitter method to handle multiple accounts

- (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 might experience problems.

The only task left is to respond when the user selects one of the accounts in the alert view. Do that by adding the delegate method shown in Listing 8-20.

Listing 8-20.  Implementation of the alertView:clickedButtonAtIndex: 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). The next time you tap the “Tweet” button, you’ll get to choose which of your accounts to send the tweet to. Figure 8-11 shows an example of the alert view.

9781430259596_Fig08-11.jpg

Figure 8-11. An alert view that allows the user to pick which Twitter account to post to

Recipe 8-4: Retrieving Tweets

Now that you have covered several different methods by which you can post updates to Twitter, you can apply the concepts from the preceding recipe that revolve around the SLRequest class to build an application that can acquire and display tweets.

In this recipe, you will build a simple Twitter reader app that allows the user to view the recent tweets from a Twitter timeline. This timeline will display based on available accounts the user has installed on the device. This will be done using a new 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 “Recipe 8-4 Retrieving Tweets”. For this recipe, we’ll create the interface programmatically. As in the preceding recipe, you use both the Social and the Accounts frameworks, so go ahead and link those to your project. We’ll need to import those frameworks into the view controller we will create shortly.

Next, create the main view controller. It displays a list of Twitter feeds, so create a new class titled MainTableViewController and give it a subclass of UITableViewController. 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 implement the new view controller, go to the app delegate to put in code necessary to implement a navigation controller. Open the AppDelegate.h file and add a property for the navigation controller, as shown in Listing 8-21.

Listing 8-21.  Modifying the AppDelegate.h file to include the UINavigationController property

//
//  AppDelegate.h
//  Recipe 8-4 Retrieving Tweets
//

#import <UIKit/UIKit.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>

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

@end

The corresponding implementation for the AppDelegate.m file is shown in bold in Listing 8-22. Here we are simply creating an instance of the MainTableViewController class we created. We are also setting it to the root view controller, as well as setting up a navigation controller.

Listing 8-22.  Modifying the appDelegate.m file to create a MainTableViewController instance

//
//  AppDelegate.m
//  Recipe 8-4 Retrieving Tweets
//

#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.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];
    return YES;
}

// ...

@end

Displaying Available Feeds

Now that you have hooked up the navigation controller and the main table view controller, you can start implementing the table view. The table view displays a number of Twitter feeds that the user can choose to view. In this recipe, you add 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 declarations, as shown in bold in Listing 8-23.

Listing 8-23.  Modifying the MainTableViewController.h file to include properties for managing accounts

//
//  MainTableViewController.h
//  Recipe 8-4 Retrieving Tweets
//

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

@interface MainTableViewController : UITableViewController

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

@end

Next, go to MainTableViewController.m and add code to the viewDidLoad method to set the navigation title as well as initialize and call the retrieveAccounts method, which we’ll create next. Modify the viewDidLoad method, as shown in Listing 8-24.

Listing 8-24.  Modifying the viewDidLoad method to set the navigation title and initialize and retrieve accounts

- (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 permission to access the Twitter accounts. You might recognize the implementation, shown in Listing 8-25, from the preceding recipe.

Listing 8-25.  Implementing the retrieveAccounts method

- (void)retrieveAccounts
{
    ACAccountType *accountType =
        [self.accountStore accountTypeWithAccountTypeIdentifier:ACAccountTypeIdentifi
erTwitter];
    
    [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 table view data source delegate methods that let the table view know how many rows and sections it should display. You are using only one section, in which you display the public feed plus the available Twitter accounts. Listing 8-26 shows the two methods.

Listing 8-26.  Implementation of delegate methods needed to display the correct amount of rows and sections

- (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.twitterAccounts.count;
}

Next, create a table view cell class that displays the items in the table view. To do this, create a new UITableViewCell class and name it “TwitterFeedCell.” Open the header file of the new class and add the code shown in bold in Listing 8-27.

Listing 8-27.  Modifying the AppDelegate.h file to include the UINavigationController property

//
//  TwitterFeedCell.h
//  Recipe 8-4 Retrieving Tweets
//

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

extern NSString * const TwitterFeedCellId;

@interface TwitterFeedCell : UITableViewCell

@property (strong, nonatomic) ACAccount *twitterAccount;

@end

Note   In Listing 8-27, you’ll see a new way of creating a variable using extern. This creates a variable that can be accessed anywhere in the application. Other programming languages might call this a global variable. You’ll see later in this recipe that we use this variable from a different class.

Now you will set up a very simple cell with the default look. However, you need to add a disclosure indicator that signals the user that there’s a detailed view waiting if the user taps the cell. Open the TwitterFeedCell.m file and change the initWithStyle:reuseIdentifier: method that Xcode generated for you, as shown in Listing 8-28.

Listing 8-28.  Creating a cell disclosure indicator in the initializer

- (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. Listing 8-29 shows the line of code you need to add.

Listing 8-29.  Setting the cell reuse constant

//
//  TwitterFeedCell.m
//  Recipe 8-4 Retrieving Tweets
//

#import "TwitterFeedCell.h"

NSString * const TwitterFeedCellId = @"TwitterFeedCell";

@implementation TwitterFeedCell

// ...

@end

Finally, you’ll add a custom setter method for the twitterAccount property in the twitterFeedCell.m file. The setter sets the property and also updates the label text of the cell. If the account property is nil, you can assume the cell is representing the public Twitter feed. Listing 8-30 shows the implementation of the setter.

Listing 8-30.  Implementing the custom setter method, setTwitterAccount:

- (void)setTwitterAccount:(ACAccount *)account
{
    _twitterAccount = account;
    if (_twitterAccount)
    {
        self.textLabel.text = _twitterAccount.accountDescription;
    }
    else
    {
        NSLog(@"No Twitter Account Given!");
    }
}

With the cell class finished, you can return 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, as shown in Listing 8-31.

Listing 8-31.  Registering the TwitterFeedCell class in the MainTableViewController class

//
//  MainTableViewController.m
//  Recipe 8-4 Retrieving Tweets
//

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

@implementation MainTableViewController

// ...

- (void)viewDidLoad
{
    [super viewDidLoad];

    //Note the use of the extern variable here
    [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, as shown in Listing 8-32. This code is relatively simple. First, create an instance of the cell for each account; because these are registered cells, they will use the custom class we created. For each cell, set the twitterAccount property, which in turn calls the custom setter that sets the cell label text.

Listing 8-32.  Implementation of the tableView:cellForRowAtIndexPath: delegate method

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    TwitterFeedCell *cell = [tableView dequeueReusableCellWithIdentifier:TwitterFeedCellId forIndexPath:indexPath];
    // Configure the cell...

    cell.twitterAccount = [self.twitterAccounts objectAtIndex:indexPath.row];
    
    return cell;
}

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

9781430259596_Fig08-12.jpg

Figure 8-12. A simple Twitter reader displaying 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 will 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 a .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 code shown in bold in Listing 8-33 to the TweetTableViewController.h file.

Listing 8-33.  Setting up the TweetTableViewController.h header file

//
//  TweetTableViewController.h
//  Recipe 8-4 Retrieving Tweets
//

#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)initWithStyle:(UITableViewStyle)style;
-(id)initWithTwitterAccount:(ACAccount *)account;

@end

In the implementation file, start by implementing the initialization methods. Add the code shown in bold in Listing 8-34.

Listing 8-34.  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 into two methods, you make sure the view controller works in a default setup scenario as well. This might be useful if you decide to reuse this class and try to initialize it without specifying the account, such as [[TweetTableViewController alloc] init].

In the viewDidLoad method, update the title of the navigation bar and call the method to retrieve the timeline, which we will implement next. Modify the viewDidLoad method, as shown in Listing 8-35.

Listing 8-35.  Modifying the viewDidLoad method to set the navigation title and call the retrieveTweets method

    - (void)viewDidLoad
{
    [super viewDidLoad];

        self.navigationItem.title = self.twitterAccount.accountDescription;
    
    [self retrieveTweets];
}

The retrieveTweets method is the essence of this recipe. It’s the one that retrieves the tweets from the given Twitter feed. The Twitter feed will be the home timeline of the user. Listing 8-36 shows the method implementation.

Listing 8-36.  Implementing the retrieveTweets method

-(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.1/statuses/home_timeline.json"];
        request = [SLRequest requestForServiceType:SLServiceTypeTwitter
            requestMethod:SLRequestMethodGET URL:requestURL parameters:nil];
        [request setAccount:self.twitterAccount];
    }
    else
    {

        NSLog(@"Uh oh, there's no Twitter account!");

    }
    
    [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];
                        });
     }];
}

There’s a lot going on in Listing 8-36, so we’ll take a moment to break it down. The first thing we’re doing is removing any tweets from the tweets array. If there are available accounts, then we create a request using the Twitter API URL to get statuses: “http://api.twitter.com/1.1/statuses/home_timeline.json.” Then we perform the request and populate the tweets array once it is finished.

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.

With the data in place, you can start implementing the table view to display it. As in the main table view, you will have only one section that contains the recent tweets. Make the changes to the numberOfSectionsInTableView: and the tableView:numberOfRowsInSection: methods, as shown in Listing 8-37.

Listing 8-37.  Implementing the delegate methods for displaying the correct number of rows and sections

- (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 changes shown in Listing 8-38.

Listing 8-38.  Setting up the TweetCell.h file

//
//  TweetCell.h
//  Recipe 8-4 Retrieving Tweets
//

#import <UIKit/UIKit.h>

extern 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), as shown in Listing 8-39.

Listing 8-39.  Implementing the custom TweetCell initializer

- (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, as shown in Listing 8-40.

Listing 8-40.  Implementing the setTweetData: method

-(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"];
}

Let’s complete the implementation of the table view. Go back to TweetTableViewController.m and make changes, as shown in Listing 8-41.

Listing 8-41.  Registering the TweetCell class and adding the cells to the table

//
//  TweetTableViewController.m
//  Recipe 8-4 Retrieving Tweets
//

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

@implementation TweetTableViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [self.tableView registerClass:TweetCell.class forCellReuseIdentifier:TweetCellId];

        self.navigationItem.title = self.twitterAccount.accountDescription;
    
    [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, so let’s return to the main table view and implement the code that displays the new view controller. Open MainTableViewController.m and add the tableView:didSelectRowAtIndexPath: delegate method, as shown in Listing 8-42.

Listing 8-42.  Adding code to the tableView:didSelectRowAtIndexPath: method to display the tweet table view

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

@interface MainTableViewController ()
//...

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Navigation logic may go here. Create and push another view controller.
    ACAccount *account = nil;

        account = [self.twitterAccounts objectAtIndex:indexPath.row];
    
    TweetTableViewController *detailViewController = [[TweetTableViewController alloc] initWithTwitterAccount:account];
    // ...
    // Pass the selected object to the new view controller.
    [self.navigationController pushViewController:detailViewController animated:YES];
}
//...

It’s again time 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 8-13 shows an example of this new table view.

9781430259596_Fig08-13.jpg

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

Showing Individual Tweets

When the user taps a tweet cell, we should display a detailed view of that tweet. This will be a simple view controller, so you will create a new UIViewController subclass with the name “TweetViewController.” This time, however, you will 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 “Translucent Navigation Bar” (see Figure 8-14). This displays a navigation bar in the view, which is helpful when you’re creating your layout.

9781430259596_Fig08-14.jpg

Figure 8-14. Simulating a navigation bar to help in user interface design

You’re going to create a user interface for displaying the tweet details. You’ll need an image view, four labels, and a text view to display the actual tweet. Drag those items onto the view from the object library and arrange them as shown in Figure 8-15. Make sure you change your “Description” label to two lines and make the size of the label a little bigger so it has room to grow. The image view shown in Figure 8-15 has a size of 64 points x 64 points.

9781430259596_Fig08-15.jpg

Figure 8-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 respective names (refer to Recipe 1-4 in Chapter 1 for creating outlets):

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

With the outlets created, you should see property outlets for each one of them in the TweetViewController.h file. In addition to those, add an NSDictionary property and declare a custom initializer method, as shown in Listing 8-43.

Listing 8-43.  Adding an NSDictionary property and custom initializer to TweetViewController.h

//
//  TweetViewController.h
//  Recipe 8-4 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. Add the initializer to the TweetViewController.m file, as shown in Listing 8-44.

Listing 8-44.  Adding the custom initializer method to the TweetViewController.m file

//
//  TweetViewController.m
//  Recipe 8-4 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 implementation shown in Listing 8-45.

Listing 8-45.  Implementing the setTweetData method

-(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. Implement this method, as shown in Listing 8-46.

Listing 8-46.  Implementation of the updateView method

-(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 call the method from Listing 8-46. The viewDidLoad method should now look like Listing 8-47.

Listing 8-47.  The modified viewDidLoad method

- (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. Return to TweetTableViewController.m and modify the implementation of the tableView:didSelectRowAtIndexPath: method, as shown in Listing 8-48. This step should look very familiar, as it is nearly the same as adding the TweetTableViewController to the MainTableViewController’s table view cells.

Listing 8-48.  Adding code to display the tweetViewController

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

@interface TweetTableViewController ()
//...

- (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];
}
//...

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 8-16.

9781430259596_Fig08-16.jpg

Figure 8-16. A view displaying a tweet

Summary

In this chapter, you learned the three major ways in which you can implement social networking features in iOS 7. 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, and other social media using SLComposeViewController; and, finally, you’ve seen how you can use SLRequest to use 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 7 has the tools you need to help your users share and connect through your apps.

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

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