February 6, 2010

iPhone Development – Gradient background UIViews

Filed under: iphone — Tags: , , , — wicheda @ 3:50 pm

Here’s a neat little tip I’ve found can spruce up your applications easily. It’s very easy to create a UIView with a solid background colour, but in our new rainbow coloured Web 2.0 shiny iPhone world, gradients are called for. You could of course create a gradient image and set the background image of the view, but this has several drawbacks:

  • For every different sized view, you either need to re-cut the gradient image or scale it.
  • It is a waste of space in your application, plus gradient images are often quite big files.
  • You need an image for each colour.
  • It is relatively expensive to load and render an image as a background for a view.

However using a bit of trickery, you can create a general GradientView class which extends UIView and overrides drawRect to achieve a gradient background. So I present GradientView:

//
//  GradientView.m
//  evilrockhopper
//
//  Created by Daniel Wichett on 10/12/2009.
//
//  Extension of UIView to show a gradient, generally used as a background on other views.
//  Mirrored indicates a gradient that is colour1 -> colour2 -> colour1. Non-mirrored simply goes from colour1 -> colour2.

#import "GradientView.h"

@implementation GradientView

@synthesize mirrored;

- (void)drawRect:(CGRect)rect
 {
    CGContextRef currentContext = UIGraphicsGetCurrentContext();

    CGGradientRef glossGradient;
    CGColorSpaceRef rgbColorspace;
    size_t numLocations = 2;
    CGFloat locations[2] = { 0.0, 1.0 };

    //Two colour components, the start and end colour both set to opaque.
    CGFloat components[8] = { startRed, startGreen, startBlue, 1.0, endRed, endGreen, endBlue, 1.0 };

    rgbColorspace = CGColorSpaceCreateDeviceRGB();
    glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, numLocations);

    CGRect currentBounds = self.bounds;
    CGPoint topCenter = CGPointMake(CGRectGetMidX(currentBounds), 0.0f);
    CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMaxY(currentBounds)/2.0);
    CGPoint bottomCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMaxY(currentBounds));

    if (!mirrored)
    {
        // draw a gradient from top to bottom centred.
        CGContextDrawLinearGradient(currentContext, glossGradient, topCenter, bottomCenter, 0);
    }
    else
    {
        // draw a gradient from top to middle, then reverse the colours and draw from middle to bottom.
        CGContextDrawLinearGradient(currentContext, glossGradient, topCenter, midCenter, 0);
        CGFloat components2[8] = { endRed, endGreen, endBlue, 1.0, startRed, startGreen, startBlue, 1.0 };
        CGGradientRelease(glossGradient);
        glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components2, locations, num_locations);
        CGContextDrawLinearGradient(currentContext, glossGradient, midCenter, bottomCenter, 0);
    }

    // Release our CG objects.
    CGGradientRelease(glossGradient);
    CGColorSpaceRelease(rgbColorspace);
}

// Set colours as component RGB.
- (void) setColours:(float) _startRed:(float) _startGreen:(float) _startBlue:(float) _endRed:(float) _endGreen:(float)_endBlue
{
	startRed = _startRed;
	startGreen = _startGreen;
	startBlue = _startBlue;

	endRed = _endRed;
	endGreen = _endGreen;
	endBlue = _endBlue;
}

// Set colours as CGColorRefs.
- (void) setColoursWithCGColors:(CGColorRef)color1:(CGColorRef)color2
{
	const CGFloat *startComponents = CGColorGetComponents(color1);
	const CGFloat *endComponents = CGColorGetComponents(color2);

	[self setColours:startComponents[0]:startComponents[1]:startComponents[2]:endComponents[0]:endComponents[1]:endComponents[2]];
}

- (void)dealloc
{
    [super dealloc];
}

@end

And the header file GradientView.h as so:

//
//  GradientView.h
//  evilrockhopper
//
//  Created by Daniel Wichett on 10/12/2009.
//

#import <UIKit/UIKit.h>

@interface GradientView : UIView
{
    float startRed;
    float startGreen;
    float startBlue;

    float endRed;
    float endGreen;
    float endBlue;

    BOOL mirrored;
}

@property (nonatomic) BOOL mirrored;

- (void) setColoursWithCGColors:(CGColorRef)color1:(CGColorRef)color2;
- (void) setColours:(float) startRed:(float) startGreen:(float) startBlue:(float) endRed:(float) endGreen:(float)endBlue;

@end

So an example usage could be:

    GradientView *redToBlack = [[GradientView alloc] initWithFrame:CGRectMake(0,0,320,50)];
    [gradientBg setColoursWithCGColors:[UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0].CGColor:[UIColor colorWithRed:0.0 green:0.0 blue:0.0 alpha:1.0].CGColor];

This creates a vertical gradient from red to black. If you wanted it to go from red to black to red, you can just set redToBlack.mirrored = YES.

It currently ignores the alpha value of the colours passed in, this would be trivial to add of course if required. If you want to experiment with other types of gradients, read the documentation for CGGradientCreateWithColorComponents. Feel free to use the code above, you can download the source by clicking on the menu in the top right of each code snippet.

February 5, 2010

iPhone Development – XCode quirks, bugs and provisioning problems

Filed under: iphone — Tags: , — wicheda @ 4:36 pm

Although XCode is not the worst IDE in the world, it is definitely buggy.

Countless times I have come up against a bug, almost always to do with provisioning profiles, build failures or sync issues which make no sense whatsoever. To prevent you banging your head constantly against a wall, when a problem seems totally illogical I always take these steps.

  1. Stop the app
  2. Delete from the device / simulator as appropriate
  3. Restart XCode
  4. Clean all builds
  5. Pray

If that doesn’t work:

  1. As above
  2. Restart Mac
  3. Restart iPhone

Trust me, more often than not that will fix all sorts of problems.

January 29, 2010

iPhone Development – Keeping the UI responsive and a background thread pattern

Filed under: iphone — wicheda @ 5:13 pm

One of the biggest mistakes you can make as an iPhone developer is running any long tasks on the main thread.

Let’s take a concrete example, say you want to retrieve some data from a web server somewhere. If you run this method on the main thread, all user interface activity is blocked until it finishes. The app will cease to respond to any user input (bar a reset / program kill) and the screen will freeze. Not good.

As any programmer who has worked with interfaces should know, all long running tasks need to be run on a background thread. One more vital point is that any changes to the user interface (ANY changes to a UI component) must always be run back on the MAIN thread. That is because those components are not thread safe and should not be changed by multiple threads. So let’s say after you have fetched your data, you want to change a label on the screen, this must be done on the main thread. I’ve come up with a little pattern I use in these cases, for example:

- (IBAction) performLongTaskOnClick: (id)sender
{
    statusMessage.text = @"Running long task";
    [self performSelectorInBackground:@selector(performLongTaskInBackground) withObject:nil];
}

- (void) performLongTaskInBackground
{
    // Set up a pool for the background task.
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

    // perform some long task here, say fetching some data over the web.
    //...

    // Always update the components back on the main UI thread.
    [self performSelectorOnMainThread:@selector(completeLongRunningTask) withObject:nil waitUntilDone:YES];

    [pool release];
}

// Called once the background long running task has finished.
- (void) completeLongRunningTask
{
    statusMessage.text = @"Finished long task";
}

To summarise, we have 3 methods, performLongTaskOnClick might be called in response to a button click say, and kicks off the long running task on a new thread with performLongTaskInBackground. Then when that finished it calls completeLongRunningTask back on the main thread. Notice that we set up an autoreleasepool in the background method, you must do that or you will be leaking memory from that thread. Now you should keep your iphone app nice and responsive.

iPhone Development – Reverse Geocoding

Filed under: iphone — Tags: , , — wicheda @ 4:48 pm

A rather nifty new feature of the iPhone SDK is the ability to reverse geocode a location. Some of you may be wondering what normal geocoding is! Well, geocoding is turning an address into a latitude and longitude. So reverse geocoding is, without surprise, turning a latitude and longitude into an address.

So, in combination with the normal iPhone lookup services, you can find out the lat,long of someone and then convert that into a friendly place name such as the town they are in. It’s very easy to do, firstly you need to implement the MKReverseGeocoderDelegate protocol in your chosen class which has two callbacks, one for when a reverse geocode is successful and one for when there is an error:

- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder  didFindPlacemark:(MKPlacemark *)placemark;
- (void)reverseGeocoder:(MKReverseGeocoder *)geocoder didFailWithError:(NSError *)error;

So we create and initialise a reverse Geocoder with the following code. Note the delegate is set to self so the methods defined above should be places in the same class. locationToLookup defines where the location name we want to look up is (as a latitude, longitude coordinate).

        // use whatever lat / long values or CLLocationCoordinate2D you like here.
        CLLocationCoordinate2D locationToLookup = {52.0,0};
        MKReverseGeocoder *reverseGeocoder = [[MKReverseGeocoder alloc] initWithCoordinate:locationToLookup];
        reverseGeocoder.delegate = self;
        [reverseGeocoder start];

The first time I tried this I got the very cryptic error message below:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[LBSGAddressComponent _mapkit_cache_heapTime]: unrecognized selector sent to instance 0x166c00'

After a lot of head scratching I realised this is because you must only ever have one reverse geocoder running at once. So I made the reverse geocoder a class variable (to keep track of it) and added:

        if (reverseGeocoder != nil)
        {
            // release the existing reverse geocoder to stop it running
            [reverseGeocoder release];
        }

        // use whatever lat / long values or CLLocationCoordinate2D you like here.
        CLLocationCoordinate2D locationToLookup = {52.0,0};
        MKReverseGeocoder *reverseGeocoder = [[MKReverseGeocoder alloc] initWithCoordinate:locationToLookup];
        reverseGeocoder.delegate = self;
        [reverseGeocoder start];

This will stop any existing lookups. And so if successful your callback method will be called with an instance of MKPlacemark that represents where your lookup is. Easy.

September 30, 2009

Localsalefinder – BView’s iPhone App

Filed under: iphone — wicheda @ 9:53 pm

Eating OutLondon Bridge results


To accompany my technical ADD, as part of my job at BView we’ve now released another product in another language entirely, an iPhone application.

Called Localsalefinder and powered by the BView API, it’s a fantastic iPhone app for finding local offers and redeeming them without printing out or clipping coupons.

Once we finish off all the finishing touches to the vouchers, this is going to be one powerful app. It includes integrations from the main restaurant booking engines (Toptable / Livebookings and more) plus our own vouchers and those sourced by 3rd parties.

If you’ve got an iPhone check it out! In terms of Objective-C development, I’m going to start a series of articles about the common problems and pitfalls of iPhone development.

Dan.