CocoaPods: The Objective-C Library Manager – Part 1/2

| Comments

Hi! all!! we’re back!!, some time ago i introduced you VendorKit, now is time for CocoaPods also a dependency management tool and what i can tell you about it is: i’ve already tested and is easy, is fast and works like a charm!.
In this tutorial you’ll learn how to use CocoaPods in your iOS project by doing a simple twitter search app, and CocoaPods will handle all of your dependencies.

Let’s Code!.

Roadmap

In this tutorial we’re going to cover each of these steps:

  1. Setup CocoaPods.
  2. Setup RestKit library.
  3. Fetch tweets using the Twitter Search WebService.
  4. Setup custom UITableViewCell.

CocoaPods

CocoaPods manages library dependencies for your Xcode project. You specify the dependencies for your project in one easy text file. CocoaPods resolves dependencies between libraries, fetches source code for the dependencies, and creates and maintains an Xcode workspace to build your project.

Installing CocoaPods

CocoaPods is distributed as a ruby gem, installing it is as easy as running the following commands in the terminal:

$ [sudo] gem install cocoapods
$ pod setup

Now that we’ve got CocoaPods installed it’s time to get started

Getting Started

First download the starting project from here, it’s a single view application with a UISearchBar and UITableView already set up, compile and run your project you should see something like this:

take a look at the implementation of the ViewController class, this class already handle a few things for us, like resizing the table when the keyboard shows up, when the user taps the search button the keyboard hides, all IBOutles needed are already set up.
I know there’s a lot of things already made, but this tutorial is not focus on how to set up the UI, if you want to learn how to set up the MainStoryboard with the the UISearchBar and UITableView, take a look at these tutorials:

Installing your first Library!

First close the Xcode project, next open terminal, and navigate to the folder where your project is. Type the following:

$touch Podfile

open the pod file with your favorite text editor and then copy and paste the following:

platform :ios
pod 'RestKit', '0.10.1'

In the above lines, first you set the platform to iOS and then you’ve declared your first dependency, RestKit v0.10.1.

Now, save and close the file, go back to terminal, and type the following command to install the dependencies in your project:

$ pod install

you should see output similar to the following:

Updating spec repo `master'
Installing FileMD5Hash (0.0.1)
Installing ISO8601DateFormatter (0.6)
Installing JSONKit (1.5pre)
Installing LibComponentLogging-Core (1.1.6)
Installing LibComponentLogging-NSLog (1.0.4)
Installing NSData+Base64 (1.0.0)
Installing RestKit/JSON (0.10.1)
Installing SOCKit (1.0)
Installing cocoa-oauth (0.0.1)
Generating support files
[!] From now on use `TwitterSearch.xcworkspace'.
-> Integrating `libPods.a' into target `TwitterSearch' of Xcode project `TwitterSearch.xcodeproj'.

from now remember to always open the Xcode workspace instead of the project file when you’re building.

$ open TwitterSearch.xcworkspace

installing RestKit for the first time in your project could take a while, there’s a lot of things behind Restkit, so be patient.

now your Xcode workspace should look like the following image:

your workspace now have two projects just remember that we’re working on the TwitterSearch project.

go to the ViewController.h and include the following header.

1
#import <RestKit/RestKit.h>

Click Run. If it builds without error, RestKit is now working!!!!

really cool, huh! To see all the hassle that you’ve avoided, take a look at all the steps that you need to do to install Restkit manually.

Fetching your first tweets

We are going to call the Search WebService from twitter to fetch our tweets, you can take a look at this example, the service response may look like the following:

JSON responseSource Article
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
{"completed_in":0.119,
 "max_id":223562723684913150,
 "max_id_str":"223562723684913155",
 "next_page":"?page=2&max_id=223562723684913155&q=iOS%205&rpp=5&result_type=recent",
 "page":1,
 "query":"iOS+5",
 "refresh_url":"?since_id=223562723684913155&q=iOS%205&result_type=recent",
 "results":[
    {"created_at":"Thu, 12 Jul 2012 23:41:21 +0000",
     "from_user":"iTwad",
     "from_user_id":159434131,
     "from_user_id_str":"159434131",
     "from_user_name":"iJD",
     "geo":null,
     "id":223562723684913150,
     "id_str":"223562723684913155",
     "iso_language_code":"en",
     "metadata":
        {"result_type":"recent"},
     "profile_image_url":"http://a0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png",
     "profile_image_url_https":"https://si0.twimg.com/sticky/default_profile_images/default_profile_3_normal.png",
     "source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;",
     "text":"Jailbreak: iPhone 4/4S/3Gs iOS-5.1.1 Issue Fixed - Unlock iPhone 4/4S/3Gs App ... - The Beacon Herald: The Beaco... http://t.co/XFjCM2j6",
     "to_user":null,
     "to_user_id":0,
     "to_user_id_str":"0",
     "to_user_name":null},
    {"created_at":"Thu, 12 Jul 2012 23:40:21 +0000",
     "from_user":"lin_kz",
     "from_user_id":197484898,
     "from_user_id_str":"197484898",
     "from_user_name":"lin_kz",
     "geo":null,
     "id":223562472752295940,
     "id_str":"223562472752295938",
     "iso_language_code":"de",
     "metadata":
        {"result_type":"recent"},
     "profile_image_url":"http://a0.twimg.com/profile_images/1643479712/image_normal.jpg",
     "profile_image_url_https":"https://si0.twimg.com/profile_images/1643479712/image_normal.jpg",
     "source":"&lt;a href=&quot;http://www.hootsuite.com&quot; rel=&quot;nofollow&quot;&gt;HootSuite&lt;/a&gt;",
     "text":"RT @unlockboot: Unlock iPhone 4 on iOS 5.1.1 Baseband 4.12.01 With Safera1n is Scam http://t.co/nyEATK9i",
     "to_user":null,
     "to_user_id":0,
     "to_user_id_str":"0",
     "to_user_name":null},
    {"created_at":"Thu, 12 Jul 2012 23:37:32 +0000",
     "from_user":"ebookschreiber",
     "from_user_id":350806797,
     "from_user_id_str":"350806797",
     "from_user_name":"ebookschreiber",
     "geo":null,
     "id":223561761972944900,
     "id_str":"223561761972944897",
     "iso_language_code":"de",
     "metadata":
        {"result_type":"recent"},
     "profile_image_url":"http://a0.twimg.com/profile_images/1484294015/ebookscheiber_normal.jpg",
     "profile_image_url_https":"https://si0.twimg.com/profile_images/1484294015/ebookscheiber_normal.jpg",
     "source":"&lt;a href=&quot;http://dlvr.it&quot; rel=&quot;nofollow&quot;&gt;dlvr.it&lt;/a&gt;",
     "text":"Sie sind da: iOS 5, iTunes 10.5 und iCloud http://t.co/9LeAyKJ5",
     "to_user":null,
     "to_user_id":0,
     "to_user_id_str":"0",
     "to_user_name":null},
    {"created_at":"Thu, 12 Jul 2012 23:37:29 +0000",
     "from_user":"Kindle_eReader",
     "from_user_id":40649488,
     "from_user_id_str":"40649488",
     "from_user_name":"FreshTech",
     "geo":null,
     "id":223561749968850940,
     "id_str":"223561749968850944",
     "iso_language_code":"de",
     "metadata":
        {"result_type":"recent"},
     "profile_image_url":"http://a0.twimg.com/profile_images/854338956/kindle_normal.jpg",
     "profile_image_url_https":"https://si0.twimg.com/profile_images/854338956/kindle_normal.jpg",
     "source":"&lt;a href=&quot;http://twitterfeed.com&quot; rel=&quot;nofollow&quot;&gt;twitterfeed&lt;/a&gt;",
     "text":"Jailbreak: iPhone 4/4S/3Gs iOS-5.1.1 Issue Fixed - Unlock iPhone 4/4S/3Gs App ... - The Beacon Herald http://t.co/TXlEB6RA",
     "to_user":null,
     "to_user_id":0,
     "to_user_id_str":"0",
     "to_user_name":null},
    {"created_at":"Thu, 12 Jul 2012 23:33:22 +0000",
     "from_user":"Electronics_bot",
     "from_user_id":207897530,
     "from_user_id_str":"207897530",
     "from_user_name":"전자제품 봇",
     "geo":null,
     "id":223560713917046800,
     "id_str":"223560713917046784",
     "iso_language_code":"ko",
     "metadata":
        {"result_type":"recent"},
     "profile_image_url":"http://a0.twimg.com/profile_images/1152738552/images_normal.jpeg",
     "profile_image_url_https":"https://si0.twimg.com/profile_images/1152738552/images_normal.jpeg",
     "source":"&lt;a href=&quot;http://twittbot.net/&quot; rel=&quot;nofollow&quot;&gt;twittbot.net&lt;/a&gt;",
     "text":"순순히 iOS 5 업뎃을 해주시면 유혈사태는 일어나지 않을 것입니다",
     "to_user":null,
     "to_user_id":0,
     "to_user_id_str":"0",
     "to_user_name":null}],
 "results_per_page":5,
 "since_id":0,
 "since_id_str":"0"}

The JSON output above lists 5 tweets. The first thing that we need to do is to define our data model class based on the JSON output, right now, we just need to map the from_user field from the JSON tweet data. So, create a new objective-c class and name it Tweet.

open Tweet.h and replace the contents of the file with the following code:

1
2
3
4
5
6
7
#import <Foundation/Foundation.h>

@interface Tweet : NSObject

@property (strong, nonatomic) NSString *from_user;

@end

In Tweet.m, synthesize the property:

1
2
3
4
5
6
7
#import "Tweet.h"

@implementation Tweet

@synthesize from_user;

@end

go to the ViewController.h and include the following header.

1
#import "Tweet.h"

next add the following code in the viewDidLoad method after section #1:

1
2
3
4
5
6
7
8
9
10
11
12
// 2 - set up the base URL
RKURL *baseURL = [RKURL URLWithBaseURLString:@"http://search.twitter.com/"];
RKObjectManager *objectManager = [RKObjectManager objectManagerWithBaseURL:baseURL];
objectManager.client.baseURL = baseURL;

// 3 - map Tweet class with the JSON response
RKObjectMapping *tweetMapping = [RKObjectMapping mappingForClass:[Tweet class]];
[tweetMapping mapKeyPathsToAttributes:@"from_user",@"from_user",nil];
[objectManager.mappingProvider setMapping:tweetMapping forKeyPath:@"results"];

// 4 - send search request!
[self searchRequest];

The first thing that we need to do is to define the baseURL for the TwitterSearch API, all the send request will be appended to this baseURL. Next we use the RKObjectMapping to map the properties from the JSON data to our data model class, mapKeyPathsToAttributes will map the JSON fields to your data model’s attributes.

now let’s implement the searchRequest method, copy the following code before the viewDidLoad method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- (void)searchRequest {
    // 1 - set up search params!
    NSString *q = @"iOS 5";
    NSString *rpp=@"5";
    NSString *with_twitter_user_id = @"true";
    NSString *result_type = @"recent";
    // 2 - map params in to a NSDictionary
    NSDictionary *queryParams;
    queryParams = [NSDictionary dictionaryWithObjectsAndKeys:q, @"q", rpp, @"rpp", with_twitter_user_id, @"with_twitter_user_id", result_type, @"result_type", nil];
    // 3 - send request
    RKObjectManager *objectManager = [RKObjectManager sharedManager];
    RKURL *URL = [RKURL URLWithBaseURL:[objectManager baseURL] resourcePath:@"/search.json" queryParameters:queryParams];
    [objectManager loadObjectsAtResourcePath:[NSString stringWithFormat:@"%@?%@", [URL resourcePath], [URL query]] delegate:self];

    NSLog(@"resource path: %@", [NSString stringWithFormat:@"%@?%@", [URL resourcePath], [URL query]]);
}

The searchRequest method is very self explanatory, first we declare all the query params that we’re going to need to make the search call, and then we make the call based on the baseURL adding all of these query params.
RestKit will send back responses using delegate methods. To receive those responses, we must adopt the RKObjectLoaderDelegate protocol. To do this, add this to your interface declaration in ViewController.h:

1
@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, RKObjectLoaderDelegate>

Then in ViewController.m, add the following RKObjectLoaderDelegate methods:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#pragma mark -
#pragma mark RKObjectLoaderDelegate implementation

- (void)objectLoader:(RKObjectLoader *)objectLoader didFailWithError:(NSError *)error {
    NSLog(@"Error: %@", [error localizedDescription]);
    // 1 - display error message
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"Error" message:@"Error retrieving Tweets" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Ok", nil];
    [alert show];

}

- (void)request:(RKRequest*)request didLoadResponse:(RKResponse*)response {
    NSLog(@"response code: %d", [response statusCode]);
}

- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {

    NSLog(@"objects[%d]", [objects count]);
    [tweets removeAllObjects];
    for (Tweet *t in objects) {
      [tweets addObject:t];
    }
    [self.tableView reloadData];
}

The only required method is objectLoader:didFailWithError, but we also need objectLoader:didLoadObjects to retrieve our request data. It returns an array of Tweet objects based on the object mapping you registered in viewDidLoad.

now change this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#pragma mark -
#pragma mark UITableViewDelegate, UITableViewDataSource protocols implementations

-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return 15;
}

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

    // 1 - set up cell id
    static NSString *CellIdentifier = @"Cell";

    // 2 - setup custom cell
    UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }

    cell.textLabel.text = @"Detail";

    return cell;
}

to this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#pragma mark -
#pragma mark UITableViewDelegate, UITableViewDataSource protocols implementations

-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [tweets count];
}

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

    // 1 - set up cell id
    static NSString *CellIdentifier = @"Cell";

    // 2 - setup custom cell
    UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }

    // 3 - start filling all labels!
    Tweet *tweet = [tweets objectAtIndex:indexPath.row];

    cell.textLabel.text = [NSString stringWithFormat:@"@%@", tweet.from_user];

    return cell;
}

The only changes here, is how we’re filling cell labels, also numberOfRowsInSection method now returns the number of Tweets based on the data source, all the rest keeps the same.

Finally let’s add the NSMutableArray that will hold our Tweet data.
switch to the interface declaration ViewController.h and add the following property:

1
@property (strong, nonatomic) NSMutableArray *tweets;

In ViewController.m, synthesize the property:

1
@synthesize tweets;

and don’t forget to allocate your tweets array!, add the following line just before section 2 in the viewDidLoad method:

1
2
3
4
5
6
tweets = [[NSMutableArray alloc] init];
// 2 - set up the base URL
.
.
.
.

if everything went well, when you run the project you should see something like the following screen:

Having trouble? if you get stuck somewhere, download the working project from here!

Customizing TableViewCells

Right now, the TableViewCells it’s very raw, it only contains the user name. let’s change that! first create a new objective-c class inherit from UITableViewCell and name it TweetCell.

go to the TweetCell.h and replace the contents of the file whit the following code:

1
2
3
4
5
6
7
8
9
10
11
#import <UIKit/UIKit.h>

@interface TweetCell : UITableViewCell

@property (strong, nonatomic) IBOutlet UILabel *userLabel;
@property (strong, nonatomic) IBOutlet UILabel *usernameLabel;
@property (strong, nonatomic) IBOutlet UILabel *timeLabel;
@property (strong, nonatomic) IBOutlet UILabel *textLabel;
@property (strong, nonatomic) IBOutlet UIImageView *userImageView;

@end

now switch to the TweetCell.m and synthesize those properties:

1
2
3
4
5
@synthesize userLabel;
@synthesize usernameLabel;
@synthesize timeLabel;
@synthesize textLabel;
@synthesize userImageView;

Later, we are going to connect all IBOutlets with our custom cell view.
In MainStoryboard, select the table view, click the size inspector ( ⌥ ⌘ 5), change the Row Height from 44 to 70.

now select the table view cell, in the size inspector ( ⌥ ⌘ 5), change the Row Height to Custom and from 44 to 70.

in the attributes inspector ( ⌥ ⌘ 4) change the Style to Custom, set the cell identifier to TweetCell.

now go to the identity inspector ( ⌥ ⌘ 3) and set the Class from UITableViewCell to TweetCell.

now add 4 UILabels and 1 UIImage in to the table view cell, like this:

and set the following properties to the UI:

UIImage View
Size: 48w 48h
Position: 10x 8y

UILabel user name
Font: System Bold 12.0
AutoShrink: disabled
Size: 67w 21h
Position: 61x 6y

UILabel @user:
Font: System 11.0
Color: Dark Gray Color
Size: 109w 21h
Position 176x 3y

UILabel time
Font: System 11.0
Alignment: left
Size: 68w 21h
Position: 246x 3y.

UILabel text
Font: System 13.0
Lines: 0
Size: 250w 42h
Position: 61x 20y.

Now connect all the outlets in TweetCell. In the connections inspector, connect the outlets: usernameLabel, userLabel, timeLabel and textLabel to their respective UILabels, and don’t forget the userImageView!.

Add an import for TweetCell at ViewController.h:

1
#import "TweetCell.h"

now switch to the implementation file ViewController.h and change this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    // 1 - set up cell id
    static NSString *CellIdentifier = @"Cell";

    // 2 - setup custom cell
    UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }

    // 3 - start filling all labels!
    Tweet *tweet = [tweets objectAtIndex:indexPath.row];

    cell.textLabel.text = [NSString stringWithFormat:@"@%@", tweet.from_user];

    return cell;
}

to:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    // 1 - set up cell id
    static NSString *CellIdentifier = @"TweetCell";

    // 2 - setup custom cell
    TweetCell *cell = (TweetCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[TweetCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
    }

    // 3 - start filling all labels!
    Tweet *tweet = [tweets objectAtIndex:indexPath.row];

    cell.usernameLabel.text = tweet.from_user;
    cell.userLabel.text = [NSString stringWithFormat:@"@%@", tweet.from_user];
    cell.timeLabel.text = tweet.created_at;
    cell.textLabel.text = tweet.text;

    return cell;
}

this wont work if you try to build and run, because Tweet doesn’t have the rest of the properties. So let’s update our Tweet data model.
go to the Tweet.h and add the following properties:

1
2
3
4
5
@property (strong, nonatomic) NSString *created_at;
@property (strong, nonatomic) NSString *from_user_name;
@property (strong, nonatomic) NSString *profile_image_url;
@property (strong, nonatomic) NSString *text;
@property (strong, nonatomic) NSString *id_str;

switch to the implementation Tweet.m and synthesize those properties:

1
2
3
4
5
@synthesize created_at;
@synthesize from_user_name;
@synthesize profile_image_url;
@synthesize text;
@synthesize id_str;

now in the ViewController.m, update the step three (tweetMapping) in the viewDidLoad method to:

1
2
3
4
5
6
7
8
9
// 3 - map Tweet class with the JSON response
RKObjectMapping *tweetMapping = [RKObjectMapping mappingForClass:[Tweet class]];
[tweetMapping mapKeyPathsToAttributes:@"created_at",@"created_at",
                                         @"from_user",@"from_user",
                                         @"from_user_name",@"from_user_name",
                                         @"profile_image_url",@"profile_image_url",
                                         @"text",@"text",
                                         @"id_str",@"id_str",nil];
[objectManager.mappingProvider setMapping:tweetMapping forKeyPath:@"results"];

Build and run, and you should see something like this:

Having trouble? if you get stuck somewhere, download the working project from here!

Now you’ve the app talking to Twitter Search WebService and you’ve already set up your custom TableView Cell, you can play around a bit with sending data over and maybe adjusting the query params. But, you’re far from finished! So stay tuned for Part 2 of this tutorial, where you’ll load more cool libraries using CocoaPods and make an even better app.

Comments