Archive for ‘iOS’

May 10, 2011

Creating a lazy loading UIImageView for iOS

The UIImageView in the iOS SDK is a great class that allows developers to quickly and easily handle the display of an image. The UIImageView is simple a container view with a single UIImage displayed within it. The nice part is that it handles all the display and sizing of the UIImage, so that developers don’t need to spend a long time playing around with ratios to get the sizing that they require.

We can initialise the UIImageView directly with an image, and this image supports using NSData to initialise it. It would seem we could simple get NSData from an NSURL, and bingo, we could load images easily from remote addresses. However, as you will find out – although this works, it essentially blocks the UI display thread until the image has loaded. This means that the display does not update until the image has been fetched from the web, and will present the user with what seems like an unusable pause.

To get round this we can implement lazy loading; where the request to retrieve the image is fired off asynchronously, and allows the UI display thread to continue till the image is received. Apple has an example in their dev knowledge base that allows you to do this directly on models. In this article we are going to look at doing this directly on a UIImageView.

In order to do this, we first create a header file for a class that inherits from UIImageView. We can call this UILazyImageView. It will, at first, look like the below:

#import <Foundation/Foundation.h>


@interface UILazyImageView : UIImageView {
}

@end

In order to implement the delegate for NSURLConnection, which we will use to do the asynchronous fetch of the image (as in last week’s post on creating an asynchronous XML parser for iOS applications); we will need a NSMutableData variable to store the data retrieved from the URL. We use NSMutableData rather than NSData, because we can append to it since it is a mutable data type.

We will also need a new initialisation method, to initialise with a URL; and this will call off to a method to load from a URL.

Having input these the header file will now look like this:

#import <Foundation/Foundation.h>


@interface UILazyImageView : UIImageView {
    NSMutableData *receivedData;    
}

- (id)initWithURL:(NSURL *)url;
- (void)loadWithURL:(NSURL *)url;

@end

Next, in our code (.m) file for UILazyImageView we can add the init method, which calls the load method, as follows:

- (id)initWithURL:(NSURL *)url
{
    self = [self init];
    
    if (self)
    {
	    receivedData = [[NSMutableData alloc] init];
        [self loadWithURL:url];
    }

    return self;
}

Next, we need to implement the loadWithURL method. This needs to fire of an asynchronous connection; the work of dealing with the image will only happen once the connection has completed.

This will look like:

- (void)loadWithURL:(NSURL *)url    
{
    NSURLConnection *connection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:url]delegate:self];
    [connection start];
}

We now need to implement the delegate for the connection (which has been set to the UILazyImageView. For this we need to at least implement didReceiveResponse, didReceiveData and connectionDidFinishLoading.

We will implement didReceiveResponse to clear the data (as this may be called when 302 redirects occur); and didReceiveData to append to the receivedData item.

Finally in connectionDidFinishLoading we can display the image, from the receivedData.

This will look like:

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    [receivedData setLength:0];
}

-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [receivedData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    self.image = [[UIImage alloc] initWithData:receivedData];
}

If we run this, we will notice it works as planned, but the image seems to appear very suddenly. We can make this a more gentle introduction, by setting the alpha to 0 before the connection begins, and then animating it to 1 (effectively creating a fade in effect).

We would do this with code like:

- (void)loadWithURL:(NSURL *)url    
{
	self.alpha = 0;
    NSURLConnection *connection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:url]delegate:self];
    [connection start];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    self.image = [[UIImage alloc] initWithData:receivedData];
    [UIView beginAnimations:@"fadeIn" context:NULL];
    [UIView setAnimationDuration:0.5];
    self.alpha = 1.0;
    [UIView commitAnimations];
}
Advertisements
April 28, 2011

Creating an asynchronous XML parser for iOS applications

If you are anything like me, and are a developer who has been working primarily with the .NET platform for the last little while, you may have a bit of a culture shock when coming to iOS development. I have worked with Objective C in the past, but primarily for OS X, and although the paradigms for programming are similar, the APIs available to iOS are slightly more limited. Subjectively, I have to say that Visual Studio is a far better IDE than Xcode – though Xcode has improved massively with version 4.

In this article I am going to explore how to write an XML parser that parses XML from a given URL asynchronously by implementing the NSXMLParserDelegate protocol and the NSURLConnectionProtocol, and using the NSXMLParser and NSURLConnection objects.

If you do not have a good grounding in Objective C, first please go and have a read of a few tutorials; if you have a decent grounding in an object oriented language, you should pick it up fairly quickly – though the memory management side of things may be confusing (and will probably be for a while!), unless you come from a non GC language background.

For the purpose of this exercise we are going to assume we are tackling a standard RSS XML feed (SlashDot RSS), that we want to represent as a collection of objects to be used in our application.

The first thing we need is an object that we will use for the representation of each item in the RSS.

We can create this as a new object, deriving from NSObject, with instance variables and @property’s for each of the RSS items.

The object’s header file would look something like:

@interface RSSItem : NSObject {
    NSString *title;
    NSString *description;
}

@property (nonatomic, retain) NSString *title;
@property (nonatomic, retain) NSString *description;

@end

And its simple implementation file would like something like:

#import "RSSItem.h"

@implementation RSSItem

@synthesize title, description;

- (void)dealloc
{
    [title release];
    [description release];
    
    [super dealloc];
}

@end

The above obviously only captures two of our RSS elements, but as a proof-of-concept will be fine, and can be extended to capture more elements.

Now that we have this defined, we can define our RSSParser. Our RSSParser can inherit directly from NSObject, but we will need to define it to comply to the NSXMLParserDelegate protocol, which allows us to set it to be the delegate object for an NSXMLParser object, so that it can deal with the elements therein.

Our header file for RSSParser would, therefore, look something like:

#import "RSSItem.h"

@interface RSSParser : NSObject <NSXMLParserDelegate> {
	//This variable will eventually (once the asynchronous event has completed) hold all the RSSItems in the feed
    NSMutableArray *allItems;
    
    //This variable will be used to map properties in the XML to properties in the RSSItem object
    NSMutableArray *propertyMap;
    
    //This variable will be used to build up the data coming back from NSURLConnection
    NSMutableData *receivedData;
    
    //This item will be declared and created each time a new RSS item is encountered in the XML
    RSSItem *currentItem;
    
    //This stores the value of the XML element that is currently being processed
    NSMutableString *currentValue;
    
    //This allows the creating object to know when parsing has completed
    BOOL parsing;
    
    //This internal variable allows the object to know if the current property is inside an item element
    BOOL inItemElement;
}

@property (nonatomic, readonly) NSMutableArray *allItems;
@property (nonatomic, retain) NSMutableArray *propertyMap;
@property (nonatomic, retain) NSData *receivedData;
@property (nonatomic, retain) RSSItem *currentItem;
@property (nonatomic, retain) NSMutableString *currentValue;
@property BOOL parsing;

//This method kicks off a parse of a URL at a specified string
- (void)startParse:(NSString*)url;

@end

You will notice we have NOT specified that this object complies to the NSURLConnectionProtocol. The reason for this is that NSObject informally complies to this protocol by default, so we can add the necessary methods we need, and set this object as the delegate for the NSURLConnection object too.

So, when we first kick off a parse using the startParse: method, we will need to create an NSURLConnection object, which will kick off the retrieval of the data from the specified URL. Once this data is all retrieved we can then actually parse the XML.

We can use code similar to the following to achieve this:

- (void)startParse:(NSString *)url
{
    //Set the status to parsing
    parsing = true;

	//Initialise the receivedData object
    receivedData = [[NSMutableData alloc] init];
    
    //Create the connection with the string URL and kick it off
    NSURLConnection *urlConnection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:url]] delegate:self];
    [urlConnection start];
}

As can be seen in our creation of the connection object, we have set the delegate to be the current object.

We can then define the methods we need to add the necessary data from the request as it comes back; and once it has all finished, to start the XML parser.

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
	//Reset the data as this could be fired if a redirect or other response occurs
    [receivedData setLength:0];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
	//Append the received data each time this is called
    [receivedData appendData:data];
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
	//Start the XML parser with the delegate pointing at the current object
    NSXMLParser *parser = [[NSXMLParser alloc] initWithData:receivedData];
    [parser setDelegate:self];
    [parser parse];
    
    parsing = false;
}

As can be seen, the implementation of this is very simple. In addition we should implement the method

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error

to handle the failure of the connection request.

Finally, once we have done this we can define the methods for the NSXMLParserDelegate protocol, so that the item is built and added to allItems correctly.

We can do this with code similar to the following:

- (void)parserDidStartDocument:(NSXMLParser *)parser
{
	//Create the property map that will be used to check and populate from elements
    propertyMap = [[NSMutableArray alloc] initWithObjects:@"title", @"description", nil];
    //Clear allItems each time we kick off a new parse
    [allItems removeAllObjects];
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict
{
	//If we find an item element, then ensure that the object knows we are inside it, and that the new item is allocated
    if ([elementName isEqualToString:@"item"])
    {
        currentItem = [[RSSItem alloc] init];
        inItemElement = true;
    }
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName
{
	//When we reach the end of an item element, we should add the RSSItem to the allItems array
    if ([elementName isEqualToString:@"item"])
    {
        [allItems addObject:currentItem];
        [currentItem release];
        currentItem = nil;
        inItemElement = false;
    }
    //If we are in element and we reach the end of an element in the propertyMap, we can trim its value and set it using the setValue method on RSSItem
    if (inItemElement)
    {
        if ([propertyMap containsObject:elementName])
        {
            [currentItem setValue:[currentValue stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] forKey:elementName];
        }
    }
    
    //If we've reached the end of an element then we should the scrap the value regardless of whether we've used it
    [currentValue release];
    currentValue = nil;
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string
{
	//When we find characters inside an element, we can add it to the current value, which is created if it is not initialized at present
    if (!currentValue)
    {
        currentValue = [[NSMutableString alloc] init];
    }
    [currentValue appendString:string];
}

As can be seen again, this implementation is very simple, but requires a slight degree of lateral thinking, compared to using a DOM based parser, as most programmers from a .NET background would be familiar with.