Class Methods and Typedef Enums

Ricki » 21 June 2010 » In Uncategorized » 1 Comment

This is a nifty little “pattern” for collecting reusable elements, color, labels, settings etc. that is standard through out your program. I do use Interface Builder, but I prefer reuse approach of code.

When a projects design has been agreed upon and the basic things are laid out: font, size, color theme etc. I usually build myself an ISInterfaceElements class. This class contains a few Class Methods called getColor:(ColorType) type and getLabel:(LabelType) type. Our Proof-of-Concept Class will contain 3 different colors: BackgroundColor, ForegroundColor and TextColor. It will also contain 3 types of labels, a HeadlineLabel, TextLabel and a DetailLabel. These six components will get you pretty far in describing the text and color of an iPhone App.

How to do it “manually”.

If I were to build a UILabel, using nothing but code, and make it adhere to the design mentioned above it would probably go a bit like this:

1
2
3
4
5
6
7
8
9
10
    UILabel *label = [[UILabel alloc] init];
    [label setFrame:CGRectMake(0.0f, 0.0f, 320.0f, 20.0f)];
    [label setFont:[UIFont fontWithName:@"Helvetica" size:14]];
    [label setTextAlignment:UITextAlignmentCenter];
   
    UIColor *textColor = [UIColor colorWithRed:0.2f green:0.5f blue:0.3f alpha:1.0f];
    [label setTextColor:textColor];
   
    [label setShadowColor:[UIColor darkGrayColor]];
    [label setShadowOffset:CGSizeMake(0.0f, 1.0f)];

If you do stuff like this manually and scattered around your program you will at some point encounter the “Let’s change the font and color of the app!” witch will leave you with a couple of hours of “Sherlock Holmes copy-paste”, as I like to call it.
Instead we will try to gather common functionality in a single class so that any change made here will propagate outwards to all the parts of the code.
We will use the approach that Apple often uses in their frameworks, e.g. when initializing a UIButton you could do it like this:

1
      UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];

The way to go about this is to write a Class that has Class Methods for getting a UILabel and UIColor back, and instead of giving them ID’s like 0034, 1234 etc. we write a typedef enum with nice, easy-to-remember names like UIButtonTypeCustom above. This will make sure code completion and compiler checking will work with you instead of against you.

The Class:

As I wrote at the beginning our class will have 3 label and 3 colors, so we start out by writing typedef enums for these elements and we define a (UILabel*) getLabel:(LabelType) type and (UIColor*)getColor:(ColorType) type method.

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
//
//  ISInterfaceElements.h
//  BlogPostCode
//
//  Created by RickiG on 6/21/10.
//  Copyright 2010 www.rickigregersen.com. Use as you please:).
//

#import <Foundation/Foundation.h>

typedef enum {
   
    BackgroundColor = 0,    //This will give the BackgroundColor the ID 0 and ForeGroundColor the ID 1 etc.
    ForegroundColor,        //it is not needed, if left out it will default to 0,1,2...
    TextColor,
   
} ColorType;

typedef enum {
   
    HeadlineLabel = 3,      //This will give the HeadlineLabel the ID 3, the TextLabel 4 etc.
    TextLabel,              //use this only if the enums can not have the same value, in our case
    DetailLabel,            //they are not needed, but shown to illustrate that enums just corresponds to integers
   
} LabelType;

@interface ISInterfaceElements : NSObject {
   
}

+ (UIColor*) getColor:(ColorType) type;
+ (UILabel*) getLabel:(LabelType) type;

@end

Now we implement these two methods. The logic of determining which kind of Color of Label is returned is done with a switch case (as the enums are integers):

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
//
//  ISInterfaceElements.m
//  BlogPostCode
//
//  Created by RickiG on 6/21/10.
//  Copyright 2010 www.rickigregersen.com. Use as you please:).
//

#import "ISInterfaceElements.h"

#define APP_FONT @"HelveticaNeue"               //Defined once so that it is simple to change the font of the app
#define APP_FONT_BOLD @"HelveticaNeue-Bold"

@implementation ISInterfaceElements

+ (UIColor*) getColor:(ColorType) type {
   
    int value; //We deal with the Color in terms of Hex, this is what a designer will often return.
   
    switch (type) {
           
        case BackgroundColor:
            value = 0x2d3c3f;
            break;         
        case ForegroundColor:
            value = 0x627376;
            break;
        case TextColor:
            value = 0xDDDDDD;
            break;
        default:
            value = 0x000000;
            break;
    }
   
    int r, g, b;
    b = value & 0x0000FF;
    g = ((value & 0x00FF00) >> 8);
    r = ((value & 0xFF0000) >> 16);
   
    return [UIColor colorWithRed:r/255.0f green:g/255.0f blue:b/255.0f alpha:1.0f];
}

+ (UILabel*) getLabel:(LabelType) type {
   
    UILabel *label = [[[UILabel alloc] init] autorelease];
    [label setBackgroundColor:[UIColor clearColor]];   
   
    switch (type) {
        case HeadlineLabel:
            [label setFont:[UIFont fontWithName:APP_FONT_BOLD size:14]];
            [label setTextColor:[self getColor:TextColor]];
            label.shadowColor = [UIColor darkGrayColor];
            label.shadowOffset = CGSizeMake(0.0, 1.0);         
            break;
        case TextLabel:
            [label setFont:[UIFont fontWithName:APP_FONT size:12]];
            [label setTextColor:[UIColor whiteColor]];
            label.shadowColor = [UIColor blackColor];
            label.shadowOffset = CGSizeMake(0.0, 1.0);
            break;
        case DetailLabel:
            [label setFont:[UIFont fontWithName:APP_FONT size:10]];
            [label setTextColor:[UIColor darkGrayColor]];
            [label setTextAlignment:UITextAlignmentRight];
            break
        default:
            break;
    }
   
    return label;
}

@end

Using the ISInterfaceElements Class:

Now that the class is set up it is really simple to obtain a UILabel or UIColor in a way that 1) does not clutter up your code with repeating setup-code 2) makes your design consistent, fonts and colors are easily changed in one location 3) follows the approach you know from Apples frameworks.

Make sure to import the ISInterfaceElements.h header then do the highlighted part below:

1
2
3
4
5
6
7
8
9
10
11
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

    // Override point for customization after application launch
   
    UILabel *headline = [ISInterfaceElements getLabel:HeadlineLabel];
    [headline setText:@"Hello, World"];
    [window addSubview:headline];
    [window makeKeyAndVisible];
   
    return YES;
}

Continue reading...

Customizing the UINavigationController

Ricki » 16 June 2010 » In Cocoa Touch, Objective C » 1 Comment

I had to add some new tags to my blog as the content will mostly be Cocoa Touch, Objective C, iPhone stuff from now on.

I think the UINavigationController is by far the most widely used Controller-controller.

Out of the box the API’s for customizing the looks of the UINavigatioController are limited to setting a tint for the background color.

I am doing an iPhone app and I would like to have the freedom to set the title label, the color, background image and the buttons to whatever I like. I, however, do not want to get rejected by the App Store approval process or implement my own controller, so in this post I’ll deliver some Proof-of-Concept code for how to do this.

The product:

Yes you are a pretty girl..

The finished product

The caveats:

There are two different approaches involved: one is to build a category on the UINavigationBar, which is the view of the UINavigationController, to set the background to either a static color or a custom image. The other approach is to build a utility Class with one class method that alters the the look of a UIViewControllers UINavigationController. Notice that I wrote that we alter the look of the UINavigationController for each ViewController. There are caveats involved! This is just a Proof of Concept (feel free to add to the code and feel especially free to show me afterwards).

  • It does not support rotation!
  • The size of the navigationBar buttons are fixed’!
  • The background color/image for the UINavigationBar can not be changed at runtime.
  • You need to implement a tap handler method to catch the actions from the buttons on the navigationBar.

Probably other things… but it can be added and maybe it will.

The code:

First off, let’s construct a category for the UINavigationBar:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//
//  UINavigationBar+customLook.h
//  UINavigationBarOverrideDrawRectPOC
//
//  Created by Ricki Gregersen on 14/6/10.
//  Copyright 2010 www.rickigregersen.com. Use as you please:)
//

#import

@interface UINavigationBar (customLook)

- (void) drawRect:(CGRect)rect;

@end

And the implementation:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#import "UINavigationBar+customLook.h"

@implementation UINavigationBar (customLook)

- (void)drawRect:(CGRect)rect {

/* comment in this part to use a color instead of an image
    UIColor *color = [UIColor redColor];
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColor(context, CGColorGetComponents( [color CGColor]));
    CGContextFillRect(context, rect);
    self.tintColor = [UIColor darkGrayColor];// color;
*/

// comment out this part to use a color instead of an image
    UIImage *img    = [UIImage imageNamed: @"nav_bar_back.png"];
    [img drawInRect:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
}

@end

The drawRect method is invoked every time a UIViewController is pushed onto the screen. You could therefore make global variable that held a reference to the image or color if you needed it to change at runtime.

Now onto the the UINavigaitonModifier Class. This contains one class method that takes a UIViewController, a title string for the left and right button, and a boolean for the left and right button determining if it is a square or an “arrow”. (the usual back button is an arrow). The class also defines a protocol for receiving actions when either button is tapped.

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
//  ISNavigationModifier.h
//  UINavigationBarOverrideDrawRectPOC
//
//  Created by Ricki Gregersen on 14/6/10.
//  Copyright 2010 www.rickigregersen.com. Use as you please:)
//

#import

@protocol ISNavigationModifierProtocol

@optional

- (void) leftBarButtonHandler:(id) sender;
- (void) rightBarButtonHandler:(id) sender;

@end

@interface ISNavigationModifier : NSObject {

}

+ (void) modifyNavigationBarFor:(UIViewController*) target : (NSString*) leftButtonString : (BOOL) isLeftArrow : (NSString*) rightButtonString : (BOOL) isRightArrow;

@end

The implementation:

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
//  ISNavigationModifier.m
//  UINavigationBarOverrideDrawRectPOC
//
//  Created by Ricki Gregersen on 14/6/10.
//  Copyright 2010 www.rickigregersen.com. Use as you please:)
//

#import "ISNavigationModifier.h"

@implementation ISNavigationModifier

+ (void) modifyNavigationBarFor:(UIViewController*) target : (NSString*) leftButtonString : (BOOL) isLeftArrow : (NSString*) rightButtonString : (BOOL) isRightArrow {

    UIButton *rightButton = nil;
    UIButton *leftButton = nil;

    if (leftButtonString) {

        leftButton = [UIButton buttonWithType:UIButtonTypeCustom];
        [leftButton addTarget:target action:@selector(leftButtonHandler:) forControlEvents:UIControlEventTouchUpInside];
        [leftButton.titleLabel setFont:[UIFont fontWithName:@"Thonburi-Bold" size:12]];
        [leftButton setTitle:leftButtonString forState:UIControlStateNormal];

        if (isLeftArrow) {
            [leftButton setFrame:CGRectMake(0.0f, 0.0f, 79.0f, 37.0f)];
            [leftButton setBackgroundImage:[UIImage imageNamed:@"arrow_square_btn.png"] forState:UIControlStateNormal];
        } else {
            [leftButton setFrame:CGRectMake(0.0f, 0.0f, 79.0f, 37.0f)];
            [leftButton setBackgroundImage:[UIImage imageNamed:@"square_btn.png"] forState:UIControlStateNormal];
        }
   }

    if (rightButtonString) {

        rightButton = [UIButton buttonWithType:UIButtonTypeCustom];
        [rightButton addTarget:target action:@selector(rightButtonHandler:) forControlEvents:UIControlEventTouchUpInside];
        [rightButton.titleLabel setFont:[UIFont fontWithName:@"Thonburi-Bold" size:12]];
        [rightButton setTitle:rightButtonString forState:UIControlStateNormal];

        if (isLeftArrow) {
            [rightButton setFrame:CGRectMake(0.0f, 0.0f, 79.0f, 37.0f)];
            [rightButton setBackgroundImage:[UIImage imageNamed:@"arrow_square_btn.png"] forState:UIControlStateNormal];
        } else {
            [rightButton setFrame:CGRectMake(0.0f, 0.0f, 79.0f, 37.0f)];
            [rightButton setBackgroundImage:[UIImage imageNamed:@"square_btn.png"] forState:UIControlStateNormal];
        }
    }

    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 150.0f, 20.0f)];
    [label setFont:[UIFont fontWithName:@"Thonburi-Bold" size:14]];
    [label setBackgroundColor:[UIColor clearColor]];
    [label setTextAlignment:UITextAlignmentCenter];
    [label setText:target.title];
    [target.navigationItem setTitleView:label];
    [label release];

    UIBarButtonItem *leftItem = [[UIBarButtonItem alloc] initWithCustomView:leftButton];
    UIBarButtonItem *rightItem = [[UIBarButtonItem alloc] initWithCustomView:rightButton];
   
    [target.navigationItem setLeftBarButtonItem:leftItem];
    [target.navigationItem setRightBarButtonItem:rightItem];

    [leftItem release];
    [rightItem release];
}

@end

The modifier class uses the reference to the UIViewController to change the navigationBar.

Example:

To use the above classes; put the UINavigationBar+customLook.h and UINavigationBar+customLook.m files somewhere in your project. Change the images or color property to fit your custom style.
Also add the ISNavigationModifier.h and ISNavigationModifier.m.
In a viewController that is pushed onto a NavigationController; import the ISNavigationModifier.h file, implement the methods: leftButtonHandler and rightButtonHandler.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (void) viewDidLoad {

    [super viewDidLoad];
    self.title = @"I am a pretty girl";
    [ISNavigationModifier modifyNavigationBarFor:self :@"Back" :NO :@"Next" :NO];
}

- (void) leftButtonHandler:(id) sender {

    [self.navigationController popViewControllerAnimated:YES];
    NSLog(@"Left Button Tapped!");
}
- (void) rightButtonHandler:(id) sender {

    ISViewControllerThree *vcThree = [[ISViewControllerThree alloc] initWithNibName:@"ISViewControllerThree" bundle:nil];
    [self.navigationController pushViewController:vcThree animated:YES];
    [vcThree release];
    NSLog(@"Right Button Tapped!");
}

Feel free to put  rants, comments, questions and improvements in the comments below.

Continue reading...

Tags: , , ,