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

| Comments

Last time, in first part of the tutorial, you created the basics of the web service, populate the TableView and you build a Custom TableView Cell. In this second and final part of the tutorial and now that you’ve already seen CocoaPods in action, it’s time to add more libraries!.

Programming mode: ON!

Roadmap

We’re going to follow each of these steps to complete this tutorial:

  1. Load images with SDWebImage library.
  2. Format date with FormatterKit library.
  3. Display loading indicator with MBProgressHUD library.
  4. Implement SSPullToRefreshView to fetch new tweets, based on the current search.
  5. Fetch tweets using the UISearchBar.
  6. Finishing touches.

Loading Images

Close your xcode workspace, open your Podfile and append the following line:

pod 'SDWebImage', '2.6'

Save the file, and install the dependencies (by using “pod install” via the Terminal, as before).

SDWEBImage: This library provides a category for UIImageVIew with support for remote images coming from the web. It provides: An UIImageView category adding web image and cache management to the Cocoa Touch framework An asynchronous image downloader An asynchronous memory + disk image caching with automatic cache expiration handling A guarantee that the same URL won’t be downloaded several times A guarantee that bogus URLs won’t be retried again and again Performances!

Let’s load the user profile picture asynchronously with the SDWEBImage library.

go to the ViewController.h and include to following imports:

1
2
#import <SDWebImage/UIImageView+WebCache.h>
#import <QuartzCore/QuartzCore.h>

now switch to the implementation ViewController.m and add the following code in the tableView:cellForRowAtIndexPath method, after section 3

1
2
3
4
// 4 - load profile image
[cell.imageView setImageWithURL:[NSURL URLWithString:tweet.profile_image_url] placeholderImage:[UIImage imageNamed:@"twitteranon0.png"]];
cell.imageView.layer.cornerRadius = 5.0;
cell.imageView.layer.masksToBounds = YES;

finally include the place holder image in your project, download from here

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

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

Date formatting

Date format from twitter is really long, and i don’t like how it looks right now, lucky for us there’s a library for Date Formatting let’s use it!

again repeat the process to install a dependency with CocoaPods but now include the following library in your Podfile:

pod 'FormatterKit', '0.7.0'

FormatterKit is a collection of well-crafted NSFormatter subclasses for things like units of information, distance, and relative time intervals. Each formatter abstracts away the complex business logic of their respective domain, so that you can focus on the more important aspects of your application.

in the ViewController.h include to following import:

1
#import "TTTTimeIntervalFormatter.h"

now switch to the implementation ViewController.m and add the following code in the tableView:cellForRowAtIndexPath method, before section 2

1
2
3
4
5
6
7
8
    // 1.1 - set up TTTTimeIntervalFormatter singleton
    static TTTTimeIntervalFormatter *_timeIntervalFormatter = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _timeIntervalFormatter = [[TTTTimeIntervalFormatter alloc] init];
        [_timeIntervalFormatter setUsesAbbreviatedCalendarUnits:YES];
        [_timeIntervalFormatter setLocale:[NSLocale currentLocale]];
    });

now, in the same method, replace the line cell.timeLabel.text = tweet.created_at; with the following code

1
2
3
4
5
6
    // 3.1 - format date with TTTTimeIntervalFormatter
    NSDateFormatter *df = [[NSDateFormatter alloc] init];
    [df setDateFormat:@"eee, dd MMM yyyy HH:mm:ss ZZZZ"]; //Tue, 10 Jul 2012 15:50:04 +0000
    NSDate *date = [df dateFromString:tweet.created_at];
    NSDate *now = [[NSDate alloc] init];
    cell.timeLabel.text = [_timeIntervalFormatter stringForTimeIntervalFromDate:now toDate:date];

First we setup a TTTTimeIntervalFormatter singleton using Grand Central Dispatch (GCD), then, date from twitter has the format Tue, 10 Jul 2012 15:50:04 +0000 we use the NSDateFormatter to match twitter date format, and finally we’re using the stringForTimeIntervalFromDate:toDate to pretty print twitter’s date.
Build and run, and you should see your date well formatted.

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

Loading indicator

What is an app that fetch data without a loading indicator???
include the following library in your Podfile, and don’t forget to installing it!.

pod 'MBProgressHUD', '0.5'

MBProgressHUD is an iOS drop-in class that displays a translucent HUD with an indicator and/or labels while work is being done in a background thread. The HUD is meant as a replacement for the undocumented, private UIKit UIProgressHUD with some additional features.

in the ViewController.h include to following import:

1
#import "MBProgressHUD.h"

now switch to the implementation ViewController.m, add the following code in the searchRequest method, after section 3:

1
2
3
 // 4 - display loading indicator
  MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
  hud.labelText = @"Loading";

now, in both objectLoader:didFailWithError and request:(RKRequest*)request didLoadResponse methods, add the following line of code:

1
2
 // 2 - hide loading indicator
  [MBProgressHUD hideHUDForView:self.view animated:YES];

Build and run, and you should see the MBProgressHUD in action!.

This app is looking good!!!, but wait…… there’s more!!. we need to fetch new tweets based on the current search and to accomplish that, we’re going to use another library!

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

Refresh TableView!

Let’s add the last library that we’ll use in tutorial, open your Podfile, add add the following line:

pod 'SSPullToRefresh', '1.0.1'

run for the last time the command pod install in your terminal, to install this dependency.

in the ViewController.h include to following import:

1
#import "SSPullToRefreshView.h"

SSPullToRefresh also will send back responses using delegate methods, we must adopt the SSPullToRefreshViewDelegate protocol, so, add it in your interface declaration, also we need to hold a reference to the pull to refresh view:

1
2
3
4
5
6
@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate, RKObjectLoaderDelegate, SSPullToRefreshViewDelegate>  {
    __weak IBOutlet UISearchBar *_searchBar;
    __weak IBOutlet UITableView *_tableView;
    __weak SSPullToRefreshView *_pullToRefreshView;
}
@property(weak, nonatomic) SSPullToRefreshView *pullToRefreshView;

switch to ViewController.m and synthesize the pullToRefreshView property:

1
@synthesize pullToRefreshView = _pullToRefreshView;

next, paste the following code before section 4 in the viewDidLoad method:

1
2
// 3.1 - set up  SSPullToRefreshView
self.pullToRefreshView = [[SSPullToRefreshView alloc] initWithScrollView:self.tableView delegate:self];

add the following code at the end of the ViewController.m

1
2
3
4
5
6
#pragma mark -
#pragma mark SSPullToRefreshViewDelegate implementation

- (void)pullToRefreshViewDidStartLoading:(SSPullToRefreshView *)view {
    [self searchRequest];
}

now when you compile and run your project, you should see the SSPullToRefresh!!

but….. there’s two issues, first SSPullToRefresh hangs up, it never get closed!!!! and second the MBProgressHUD also is shown at the same time of the SSPullToRefresh loading state, i don’t want two loading indicators!! let’s fix it.

in the ViewController.h add the next property BOOL usingPullToRefresh;

now go to the ViewController.m, in the method pullToRefreshViewDidStartLoading add the following line, before calling the searchRequest:

1
usingPullToRefresh = YES;

in the method searchBarSearchButtonClicked add the following line:

1
usingPullToRefresh = NO;

in the methods objectLoader:didFailWithError and objectLoader:didLoadObjects replace:

1
2
// 2 - hide loading indicator
[MBProgressHUD hideHUDForView:self.view animated:YES];

with this:

1
2
3
4
5
6
7
8
// 2 - hide loading indicator
if (usingPullToRefresh) {
    [self.pullToRefreshView finishLoading];
} else {
    [MBProgressHUD hideHUDForView:self.view animated:YES];
}

usingPullToRefresh = NO;

next in the searchRequest method, replace:

1
2
3
// 4 - display loading indicator
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
hud.labelText = @"Loading";

with this:

1
2
3
4
5
6
7
// 4 - display loading indicator
if (usingPullToRefresh) {
    [self.pullToRefreshView startLoading];
} else {
    MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.view animated:YES];
    hud.labelText = @"Loading";
}

And that should fix it!!

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

New searches with UISearchBar

In the ViewController.h add the property NSString *searchQuery;

now switch to the ViewController.h and replace the searchBarSearchButtonClicked method with the following code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-(void) searchBarSearchButtonClicked:(UISearchBar *)searchBar {

    // 1 - hide keyboard
    [self.searchBar resignFirstResponder];

    // 2 - save search textfield value
    searchQuery = [self.searchBar text];
    usingPullToRefresh = NO;

    // 3 - this is a new search, remove all previous tweets!
    [tweets removeAllObjects];
    [self.tableView reloadData];

    // 4 - perform search
    [self searchRequest];
}

now go to the searchRequest method and change the following line:

1
 NSString *q = @"iOS 5";

to:

1
 NSString *q = searchQuery;

finally in the viewDidLoad method include the following code before section #2:

1
2
3
// 1.1 - Initialize UISearchBar
[self.searchBar setText:@"iOS 5"];
searchQuery = [self.searchBar text];

compile and run, the UISearchBar should be now working!!!

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

Finishing touches.

Right now when you load new tweets using the pull to refresh action, all previous tweets get erased, i don’t want that, so why not use beginUpdates and endUpdates methods of the UITableView class to insert new rows, also to avoid duplicates Tweets we need to check if a Tweet is already added in the data source.

include the following method before the searchRequest method:

1
2
3
4
- (BOOL)isTweetAlreadyAdded:(Tweet *)tweet {
    NSArray *duplicates = [tweets filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"id_str == %@", tweet.id_str]];
    return [duplicates count] > 0 ? YES : NO;
}

now replace the objectLoader:didLoadObjects method with the following code:

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
- (void)objectLoader:(RKObjectLoader *)objectLoader didLoadObjects:(NSArray *)objects {

    NSLog(@"objects[%d]", [objects count]);

    // 1 - populate TableView
    [self.tableView beginUpdates];
    NSArray* reversedArray = [[objects reverseObjectEnumerator] allObjects];            //revert fetched tweets!
    NSMutableArray *insertion = [[NSMutableArray alloc] init];                          //this array will hold all NSIndexPaths objects
    int insertIdx = 0;
    for (Tweet *t in reversedArray) {
        if (![self isTweetAlreadyAdded:t]) {                                            //check is tweet is already added in array
            [tweets insertObject:t atIndex:insertIdx];                                  //insert new tweet
            [insertion addObject:[NSIndexPath indexPathForRow:insertIdx inSection:0]];  //insert NSIndexPath
            insertIdx++;
        }
    }
    [self.tableView insertRowsAtIndexPaths:insertion withRowAnimation:UITableViewRowAnimationRight];
    [self.tableView endUpdates];

    // 2 - hide loading indicator
    if (usingPullToRefresh) {
        [self.pullToRefreshView finishLoading];
    } else {
        [MBProgressHUD hideHUDForView:self.view animated:YES];
    }

    usingPullToRefresh = NO;

}

as you can see, now we are adding Tweets instead of replacing the array and avoiding duplicates using the isTweetAlreadyAdded method based on the id_str of the Tweet data model from the JSON data.

Also i want the userLabel position right at the end of the usernameLabel width, to accomplish that we need to override the Cell layout method.
Include the following code in the TweetCell.m:

1
2
3
4
5
6
7
8
- (void) layoutSubviews {
    [super layoutSubviews];
    CGRect usernameRect = usernameLabel.frame;
    CGRect useRect = userLabel.frame;
    useRect.origin.x = usernameRect.origin.x + usernameRect.size.width + 3;
    userLabel.frame = useRect;
    self.imageView.frame = CGRectMake( 10, 8, 48, 48 );
}

now go to ViewController.m to the tableView:cellForRowAtIndexPath method and paste the following line after cell.usernameLabel.text = tweet.from_user; :

1
[cell.usernameLabel sizeToFit];

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

and we’re done!!
What do you thing about CocoaPods?? nice isn’t it??

you can always take a look at all the libraries available here.
also checkout another cool libraries like:

and many more!!

I hope that you have enjoyed this tutorial.
Happy Coding.

Comments