Thomas Denney

Enumerate characters of an NSString

The following category method will allow you to iterate over the characters of an NSString with a block:

-(void)enumerateCharacters:(EnumerationBlock)enumerationBlock
{
    const unichar * chars = CFStringGetCharactersPtr((__bridge CFStringRef)self);
    //Function will return NULL if internal storage of string doesn't allow for easy iteration
    if (chars != NULL)
    {
        NSUInteger index = 0;
        while (*chars) {
            enumerationBlock(*chars, index);
            chars++;
            index++;
        }
    }
    else
    {
        //Use IMP/SEL if the other enumeration is unavailable
        SEL sel = @selector(characterAtIndex:);
        unichar (*charAtIndex)(id, SEL, NSUInteger) = (typeof(charAtIndex)) [self methodForSelector:sel];
        
        for (NSUInteger i = 0; i < self.length; i++)
        {
            const unichar c = charAtIndex(self, sel, i);
            enumerationBlock(c, i);
        }
    }
}

Keep Calm updated

I’ve added the following new features to Keep Calm 2.31:

  • A ton of bug fixes, the app is a lot more solid now
  • Pro users can now crop, scale and rotate their custom icons (this feature will probably be added to the background import at some point soon too)
  • Purchasing upgrades is now a lot faster. Technical explanation: Previously when you pressed to purchase an upgrade it made a request for the information about the product from Apple, meaning there was a delay of a few seconds whilst this information was received and processed. Now as soon as you enter the store the information is fetched, so payment now has one less step, making the whole thing a lot faster :)

Creating iOS app icons with a shell script

One of the more annoying parts of working on iOS project is generating all of the appropriate artwork - there are up to 13 different images that you have to create just for the app icon. Several apps exist for this, however I figured a simpler solution would be to have a simple shell script:

#!/bin/sh

ITUNES_ARTWORK="$1"
FOLDER=$(dirname "$ITUNES_ARTWORK")

sips -z 57 57 "$ITUNES_ARTWORK" --out "${FOLDER}/Icon.png"
sips -z 114 114 "$ITUNES_ARTWORK" --out "${FOLDER}/Icon@2x.png"
sips -z 120 120 "$ITUNES_ARTWORK" --out "${FOLDER}/Icon-60@2x.png"
sips -z 29 29 "$ITUNES_ARTWORK" --out "${FOLDER}/Icon-Small.png"
sips -z 58 58 "$ITUNES_ARTWORK" --out "${FOLDER}/Icon-Small@2x.png"
sips -z 40 40 "$ITUNES_ARTWORK" --out "${FOLDER}/Icon-Small-40.png"
sips -z 80 80 "$ITUNES_ARTWORK" --out "${FOLDER}/Icon-Small-40@2x.png"
sips -z 50 50 "$ITUNES_ARTWORK" --out "${FOLDER}/Icon-Small-50.png"
sips -z 100 100 "$ITUNES_ARTWORK" --out "${FOLDER}/Icon-Small-50@2x.png"
sips -z 72 72 "$ITUNES_ARTWORK" --out "${FOLDER}/Icon-72.png"
sips -z 144 144 "$ITUNES_ARTWORK" --out "${FOLDER}/Icon-72@2x.png"
sips -z 76 76 "$ITUNES_ARTWORK" --out "${FOLDER}/Icon-76.png"
sips -z 152 152 "$ITUNES_ARTWORK" --out "${FOLDER}/Icon-76@2x.png"

Here’s what you need to do to set it up:

  • Save the above into a file in your home directory called icongenerator.sh (it is imperative that it is in your home directory)
  • Open up terminal and execute ‘chmod +x icongenerator.sh’
  • Execute ‘./icongenerator.sh path/to/iTunesArtwork’
  • The script will then create the new image files

In case you haven’t come across it before, sips is a really simple way of quickly resizing images on the command line on OSX.

Blurred Camera updated

After seeing a successful opening weekend, I’ve updated Blurred Camera with the following new features:

  • Updated icon (i.e. it actually looks alright now)
  • Fixed bug with the spinner staying on screen for ages when importing a photo
  • Major performance enhancements, especially when importing a photo
  • Much smaller app bundle size (75% smaller, mostly due to PNG compression of the icons and launch images)

Introducing Blurred Camera

Blurred Camera

Blurred Camera is a new free iOS app that I’ve been working on that makes it really easy to create blurry photos that you can use as your wallpaper. The app allows you to vary how much the image is blurred by pinching and also allows you to adjust the brightness and saturation by swiping.

The app is really quite simple (I threw it together in a couple of evenings) and based off of GPUImage.

Parsing HTML with AFNetworking 2 and ObjectiveGumbo

Now that AFNetworking 2 is out and ObjectiveGumbo is now available on CocoaPods I thought I would write a simple guide showing you how you can build a simple app that uses HTML files as a data source through AFNetworking. This post shows how to write an app that will find the top read articles on BBC News, list them and allow the user to visit them. You can find the source in the ObjectiveGumbo demos repository.

Setting up CocoaPods

AFNetworking 2 currently requires iOS 7 due to its new reliance on NSURLSession (a new iOS 7 framework for working with URL requests and connections), so you’ll need to add the following to your Podfile and execute ‘pod install’ to download the dependencies:

platform :ios, '7.0'
pod "ObjectiveGumbo", "0.1"
pod "AFNetworking", "2.0.0-RC3"

If you are unfamiliar with CocoaPods, they have a setup guide on their website.

An HTML serializer

One of the great new features of AFNetworking 2 is that serializers are now abstracted away from the core URL requesting code. This means that you can easily write a response serialiser that will take an NSData object and convert it into something useful - AFNetworking ships with classes that you can subclass that will serialize XML or JSON into NSDictionarys for example.

To build a custom response serializer to decode the NSData response from the web server into an OGDocument (the base class of a document parsed by ObjectiveGumbo) you will need to subclass AFHTTPResponseSerializer into HTMLResponseSerializer and implement the following method:

-(id)responseObjectForResponse:(NSURLResponse *)response
    data:(NSData *)data 
    error:(NSError *__autoreleasing *)error
{
    return [ObjectiveGumbo parseDocumentWithData:data];
}

(The full code for the .h and .m files are in the repo linked above)

Subclassing the HTML serializer

Whilst having the data as an OGDocument might be fine for your application, you will probably want to feed it back custom objects that are relevant to your application. In this example I’m going to parse the list on the BBC site of most read articles and put it into an Article class with a link and title.

You will need to subclass the HTMLResponseSerializer into BBCResponseSerializer, and again implement the responseObjectForResponse:data:error: method:

-(id)responseObjectForResponse:(NSURLResponse *)response 
     data:(NSData *)data
     error:(NSError *__autoreleasing *)error
{
    //1
    OGDocument * document = [super responseObjectForResponse:response
                                                        data:data 
                                                        error:error];

    //2
    NSArray * panels = [document select:@".panel"];

    NSMutableArray * articles = [NSMutableArray new];

    //3
    //The list of most read articles is in the second panel
    OGElement * list = (OGElement*)[panels[1] first:@"ol"];

    //4
    for (OGElement * listItem in [list select:@"li"])
    {
        //5
        OGElement * link = (OGElement*)[listItem first:@"a"];
        NSString * href = link.attributes[@"href"];
        //Whitespace and the span are the first two elements
        NSString * title = [link.children[2] text];
        //6
        Article * article = [Article new];
        article.title = title;
        article.link = [NSURL URLWithString:href];
        [articles addObject:article];
    }

    //7
    return articles;
}
  1. The data is first parsed using the super class (HTMLResponseSerializer) to get an OGDocument object (which is a subclass of OGNode that also contains DOCTYPE information, although we won’t need it for this example)
  2. ObjectiveGumbo has a jQuery/CSS like selection system that makes it easy to select elements in the DOM. Here we select all of the elements that have the class ‘panel’ (the list of Top Shared, Top Read and Top Shared on the right all have this class)
  3. Again, use the select method to pick the first ordered list from the second panel (the top read panela)
  4. Iterate over all of the list items
  5. Get the link and its href attribute and get the title of the link as well (you may wish to look at the page source for the BBC News homepage if this is confusing)
  6. Put the data into a custom Article class that can be used by the primary view controller
  7. The superclass would ordinarily return an OGDocument, however for this request we instead return a list of articles

Using the data

Now that we’ve written methods for parsing the data, presenting it is fairly trivial. Here is the code for my master view controller:


- (void)viewDidLoad
{
    [super viewDidLoad];

    NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://bbc.co.uk/news"]];
    AFHTTPRequestOperation * operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    operation.responseSerializer = [BBCResponseSerializer serializer];
    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        self.articles = (NSArray*)responseObject;
        [self.tableView reloadData];
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"%@", error.localizedDescription);
    }];
    [operation start];
}

#pragma mark - Table View

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return self.articles.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];

    Article * article = self.articles[indexPath.row];

    cell.textLabel.text = article.title;
    return cell;
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:@"showDetail"]) {
        NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
        Article * article = self.articles[indexPath.row];
        [[segue destinationViewController] setDetailItem:article];
    }
}

ObjectiveGumbo now available via CocoaPods

I’m pleased to announce that if you want to use ObjectiveGumbo, my Objective-C wrapper around Google’s awesome HTML5 parser Gumbo, in your iOS projects you can now install it via CocoaPods. Simply add the following to your podfile to get started:

platform :ios, '6.0'
pod "ObjectiveGumbo", "0.1"

You will then need to run pod install and open the Xcode workspace file, not your project. You can then import the ObjectiveGumbo.h file into your project to begin working with it.

I soon plan to publish a guide that will help you get started using ObjectiveGumbo with AFNetworking 2.

Multi Timer updated for iOS 7

Apple has now approved the updated version of another of my apps, Multi Timer. The new update to Multi Timer vastly enhances the UI on iOS 7, fixes a few bugs and adds a few more icons for your timers.

Head over to the App Store to download Multi Timer for $0.99.

Keep Calm updated for iOS 7

Keep Calm

The new version of Keep Calm 2.3, which has been enhanced for iOS 7, is now available. This version introduces a slightly new UI (it looks very similar to the old one, except that it is now using iOS 7, rather than custom, components) and fixes a bunch of bugs:

  • All in app purchases now work correctly across devices
  • Fewer crashes (I’ve also added crash reporting so hopefully they’ll get resolved quicker in the future)
  • A bunch of new backgrounds
  • Full support for the iPhone 5S
  • Full support for iOS 7
  • A really neat new parallax style effect where you tilt the device and the text on your poster moves, making it seem 3D
  • Posters now fill the screen so they are no longer weird proportions on iPads

You can head over to the App Store now to update (or if you’re using iOS 7 it will download automatically!).

Working with AFNetworking 2

This week Mattt Thompson announced AFNetworking 2.0 and for the past couple of days I’ve been playing around with it (along with a few techniques from objc.io for lighter view controllers) to build a simple demo app that showcases some of the new features of AFNetworking. The source for the app discussed in this post is available on my GitHub profile. To compile the code in this blog post you will need Xcode 5 and the iOS 7 SDK installed.

Getting started

With CocoaPods it was really easy to set the project up, however slightly different from the original AFNetworking. Here is my Podfile:

platform :ios, '7.0'
pod "AFNetworking", "2.0.0"
pod "AFNetworking/UIKit+AFNetworking", "2.0.0

The UIKit extensions for AFNetworking previously only extended UIImageView, however this has now been greatly expanded for other UIViews so is now a separate subspec in CocoaPods. Simply run ‘pod install’ and open up your Xcode Workspace (not the project) to get started.

Basic serializers

AFNetworking 2 has moved to a new model of serializers to add a bit more modularity. This means that requests can now have a request serializer that handles generating the request based on user parameters and doing tasks such as authorization and the response serializer can now generate user data from the server response.

In my example app I’ve written a simple response serializer that subclasses AFJSONSerializer to serialize an array from the JSON response from reddit:

@implementation RedditResponseSerializer

-(id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error
{
    NSDictionary * json = [super responseObjectForResponse:response data:data error:error];
    NSMutableArray * posts = [NSMutableArray new];
    NSDictionary * dataObject = json[@"data"];
    if (dataObject != nil)
    {
        NSArray * children = dataObject[@"children"];
        [children enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            NSDictionary * postData = [(NSDictionary*)obj objectForKey:@"data"];
            [posts addObject:[RedditPost postWithProperties:postData]];
        }];
    }
    return posts;
}

@end

The great thing about writing a custom serializer is that it is highly reusable - it isn’t going to take much work in this example to check whether the response was acutally for comments instead of posts, so it can simply return another array of objects. I’ve also written a simple class for storing post data:

@implementation RedditPost

+(id)postWithProperties:(NSDictionary *)properties
{
    return [[self alloc] initWithProperties:properties];
}

-(id)initWithProperties:(NSDictionary *)properties
{
    self = [super init];
    if (self) {
        [self setValuesForKeysWithDictionary:properties];
    }
    return self;
}

//Had to do a custom implementation of this because I didn't want all keys
-(void)setValue:(id)value forKey:(NSString *)key
{
    if ([key isEqualToString:@"title"]) self.title = value;
    else if ([key isEqualToString:@"subreddit"]) self.subreddit = value;
    else if ([key isEqualToString:@"author"]) self.username = value;
    else if ([key isEqualToString:@"thumbnail"]) self.thumbnail = value;
    else if ([key isEqualToString:@"url"]) self.url = value;
    else if ([key isEqualToString:@"ups"]) self.ups = value;
    else if ([key isEqualToString:@"downs"]) self.downs = value;
    else if ([key isEqualToString:@"score"]) self.score = value;
}

@end

As noted in the comment above, if you do not wish to have properties for all of the possible API keys it makes sense to write a custom setValue:forKey: function to only use the keys you want. This is especially sensible if in the future new keys are added to the API as otherwise your app will crash with an NSUndefinedKeyException.

Then, using the light view controller pattern described in objc.io #1, it is really easy to set up a data source for the app:

- (void)viewDidLoad
{
    [super viewDidLoad];
    NSURLRequest * request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://reddit.com/.json"]];
    AFHTTPRequestOperation * operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    operation.responseSerializer = [RedditResponseSerializer serializer];
    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        NSArray * allPosts = (NSArray*)responseObject;
        [self configureDataSource:allPosts];
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"%@", error.localizedDescription);
    }];
    [operation start];
}

-(void)configureDataSource:(NSArray*)posts
{
    self.dataSource = [[ArrayDataSource alloc] initWithItems:posts cellIdentifier:@"redditPostCell" configureCellBlock:^(id cell, id item) {
        RedditPost * post = (RedditPost*)item;
        UITableViewCell * postCell = (UITableViewCell*)cell;
        postCell.textLabel.text = post.title;
        postCell.detailTextLabel.text = [NSString stringWithFormat:@"%d points \u2022 /r/%@ \u2022 /u/%@", post.score.integerValue, post.subreddit, post.username];

    }];
    self.tableView.dataSource = self.dataSource;
    [self.tableView reloadData];
}

In this example I’ve simply requested the JSON for the front page of reddit, used my custom serializer to get an array of the posts and then configured a simple data source for the posts. It is worth noting that you must retain your ArrayDataSource object (i.e. you can’t define it in configureDataSource:) because the dataSource property of a UITableView expects only a weak reference to an id, so I instead added it as a property.

More advanced serializer

This example is similiar to the last, however adopts a few new features:

  • Use a custom AFHTTPSessionManager which makes it easy to perform custom requests. In this example I’ll show a simple way to get stock market quotes from Yahoo Finance
  • Use a custom request serializer to set up the URL request based on an array of stock symbols
  • Use a custom response serializer and YahooStockValue object to parse the CSV data from Yahoo
  • Return the data to the main view controller so it can be displayed in a list

Custom AFHTTPSessionManager

AFHTTPSessionManager is the new version of AFHTTPClient, and subclasses AFURLSessionManager which implements methods of the new NSURLSession (introduced in iOS 7). Clients/Session managers are recommended to return a singleton instance, as below:

//YahooFinanceClient.h
#import <Foundation/Foundation.h>
#import <AFNetworking.h>
#import "YahooFinanceRequestSerializer.h"
#import "YahooFinanceResponseSerializer.h"

typedef void(^FetchedSymbols)(NSArray*symbols);

@interface YahooFinanceClient : AFHTTPSessionManager

+(instancetype)client;

-(void)fetchSymbols:(NSArray*)symbols completion:(FetchedSymbols)fetchedSymbolsBlock;

@end

//YahooFinanceClient.m
#import "YahooFinanceClient.h"

@implementation YahooFinanceClient

#pragma mark - Singleton

+(instancetype)client
{
    static YahooFinanceClient * requests = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        requests = [[YahooFinanceClient alloc] initWithBaseURL:[NSURL URLWithString:@"http://download.finance.yahoo.com/d/quotes.csv"]];
    });
    return requests;
}

#pragma mark - custom initialization

-(id)initWithBaseURL:(NSURL *)url
{
    self = [super initWithBaseURL:url];
    if (self)
    {
        self.requestSerializer = [YahooFinanceRequestSerializer new];
        self.responseSerializer = [YahooFinanceResponseSerializer new];
    }
    return self;
}

#pragma mark - Custom fetching functions

-(void)fetchSymbols:(NSArray *)symbols completion:(FetchedSymbols)fetchedSymbolsBlock
{
    [self GET:@"" parameters:@{@"symbols": symbols} success:^(NSURLSessionDataTask *task, id responseObject) {
        fetchedSymbolsBlock(responseObject);
    } failure:^(NSURLSessionDataTask *task, NSError *error) {
        fetchedSymbolsBlock([NSArray new]);
        NSLog(@"ERROR: %@", error);
    }];
}

@end

This sets up a client with a base URL of the API endpoint we will be using. Because this is only fetching data from one endpoint, we will use that one. If we were requesting from Twitter, for example, we would use https://api.twitter.com/1.1/ and then when we wanted to perform a request rather than using a URL of @"" we would use the appropriate API endpoint (i.e. @“statuses/mentions_timeline.json”).

I won’t include all of the source code for the request serializer here as it is relatively simple, so please view the source on GitHub.

Custom response serializer

There are two components to the response serializer: the first is the serializer that returns an array of all of the stock symbols and the second is some code in YahooStockValue that will receive an array of the parsed CSV values that it can then build an object from:

//YahooFinanceResponseSerializer.m implementation

-(id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error
{
    NSString * strData = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];

    NSMutableArray * stockValues = [NSMutableArray new];

    [strData enumerateLinesUsingBlock:^(NSString *line, BOOL *stop) {
        NSArray * components = [self parseLine:line];
        YahooStockValue * stockValue = [[YahooStockValue alloc] initWithArray:components];
        [stockValues addObject:stockValue];
    }];

    return stockValues;
}

-(NSArray*)parseLine:(NSString*)line
{
    NSMutableArray * allComponents = [NSMutableArray new];
    NSMutableString * currentComponent = [NSMutableString new];
    BOOL inQuotes = NO;
    BOOL lastWasBackslash = NO;
    for (NSInteger i = 0; i < line.length; i++)
    {
        unichar chr = [line characterAtIndex:i];
        if (chr == '"' && !lastWasBackslash) inQuotes = !inQuotes;
        else if (chr == '\\') lastWasBackslash = YES;
        else if (!inQuotes && chr == ',')
        {
            [allComponents addObject:currentComponent];
            currentComponent = [NSMutableString new];
        }
        else
        {
            lastWasBackslash = NO;
            [currentComponent appendFormat:@"%c", chr];
        }
    }

    if (currentComponent.length > 0) [allComponents addObject:currentComponent];

    return allComponents;
}

//YahooStockValue.m implementation
-(id)initWithArray:(NSArray *)array
{
    self = [super init];
    if (self)
    {
        NSNumberFormatter * f = [[NSNumberFormatter alloc] init];
        [f setNumberStyle:NSNumberFormatterDecimalStyle];

        if (array.count >= 1) self.name = array[0];
        if (array.count >= 2) self.symbol = array[1];
        if (array.count >= 3) self.latestValue = [f numberFromString:array[2]];
        if (array.count >= 4) self.openValue = [f numberFromString:array[3]];
        if (array.count >= 5) self.closeValue = [f numberFromString:array[4]];
    }
    return self;
}   

Hopefully you will find the majority of this code reasonably familiar and I won’t focus on the CSV parser itself. In the example app you can type in the stock symbols for various companies separated by spaces and the data will be fetched, parsed and presented in a table view.

Image loading

AFNetworking has always had a really awesome extension to UIImageView that allows you to easily load images from the web, however there are now a few more additions so they a now in a separate CocoaPod subspec (as mentioned in the introduction). Simply do the following to load an image from the web:

#import <UIImageView+AFNetworking.h>

[imageView setImageWithURL:[NSURL URLWithString:@"http://programmingthomas.com/item/52356701e4b0bfcfa19b4b3b?format=1500w"]];

There are a few more methods on the category that also allow you to setup placeholder images and use callbacks for success/failure. It is particularly useful to use this category for all image loading from the web because it also manages caching for you as well with a singleton cache, so you don’t need to worry about requesting images multiple times.

Notes on migrating to and using AFNetworking 2

Whilst AFNetworking 2 is similiar to the original version it introduces a lot of new features that break compatibility, so there are a few things to keep note of:

  • You will need to target iOS 7 upwards because of the migration towards NSURLSession
  • AFHTTPClient has now been been replaced with various classes inheriting from AFURLSessionManager instead (including AFHTTPSessionManager)
  • When working with web APIs:
    • Your session manager should provide convenience methods for getting data from an API endpoint. It should also provide a singleton method so you do not recreate it unnecessarily
    • Your session manager should use custom request and response serializers
    • Your request serializer will convert the options in your application into parameters to add to the URL. It should also add and handle authentication parameters. In some cases, if you do not need to handle authentication or complex parameters, you may be able to use one of the standard AFNetworking request serializers instead
    • Your response serializer should convert the NSData response from the server into something more useful for your application to use such as an array of/or custom objects. If you are handling XML or JSON data you probably want to base it off of AFXMLResponseSerializer or AFJSONResponseSerializer as these will handle parsing for you

Conclusion

I haven’t covered all of the new features (including Rocket support for real-time events from the server) of AFNetworking 2 but the changes have helped to make it even easier to write applications on top of it.

At first I did feel that the model for session manager + request serializer + response serializer was a little verbose, however it makes perfect sense:

One of the major criticisms of AFNetworking is how bulky it is. Although its architecture lent itself well to modularity on a class level, its packaging didn’t allow for individual features to be selected à la carte. Over time, AFHTTPClient in particular became overburdened in its responsibilities (creating requests, serializing query string parameters, determining response parsing behavior, creating and managing operations, monitoring network reachability).

http://nshipster.com/afnetworking-2/

This new modularity means that the amount of code in the session manager (formally the HTTP client) is a much smaller because tasks are delegated out into more appropriate areas, and whilst my example may have been a bit too small to demonstrate this fully, it makes perfect sense if you have a much larger API to deal with.

AFNetworking 2 is really awesome, and hopefully you’ll be able to begin using it in your apps soon.