field-theory.org
the blog of wolfram schroers

Colors and fonts on the iPhone/iPad

This article describes two extensions to the UIColor and the UIFont classes on iOS for the iPhone and the iPad. The first implements the NSCopying protocol for UIColor and adds the colors defined by the CSS standard. The second implements NSCopying for UIFont.

Table of contents:

Colors, fonts and copying
The WSColor category
The WSFont category
Introducing CSS standard colors
Download and conclusions

Colors and copying

Copying vs. retaining object instances

Why would you want to copy instances of UIColor when developing on the iPhone or the iPad? Why copy UIFont? I came across these questions when I started to use colors and fonts as properties in other objects. Typically, local instance variables like strings (i.e., instances of NSString) would be declared as properties using the copy option:

  @property (copy, nonatomic) NSString *hello;

For all objects that act as instance variables in your class you need to decide if the object itself should be copied or if merely a reference should be tracked. The decision is very important if the objects are mutable and can be changed at other places. It depends on the problem which solution, reference or copy, is adequate — in case of a bank account it would certainly be in any bank's interest to copy the reference only, thus allowing the containing object to always refer to the same account (accounts are mutable since one can deposit and withdraw money). In case of a sandwich it might be wise for a restaurant to give every customer a new copy of a sandwich and not let everybody eat the same one (sandwiches are mutable, too, as you can verify yourself at your next dinner).

Commonly, an object that is logically tied locally to another object should be copied. A common situation case are string properties. Just like in the case of NSString, a UIColor instance commonly is relevant to a single object and thus should be copied rather than retained. In a language with dynamic object resolution (like Objective-C) it may even happen that you do not know the class of an object at compile time. Having the ability to copy rather than merely retain an arbitrary object from a given collection gives you much more flexibility and power when designing your code.

This advice holds generally unless memory consumption is a concern – which is not common for NSString but almost certainly irrelevant for UIColor and UIFont.

When synthesizing the getters and setters for copy-objects in Xcode, the setter will internally call the copy method on the object. However, with the standard UIKit this approach will cause the program to crash as soon as the setter method of a color property is used. This is because the UIColor and UIFont classes do not implement the NSCopying protocol and thus objects cannot handle the dispatch of a copy message.

Fortunately, with Objective-C we can simply use categories to extend their functionality such that they do.

Minimal solution: The WSColor category

Defining the interface

The interface of the category satisfying a protocol simply looks like this (named WSColor.h):

    #import <Foundation/Foundation.h>
    @interface UIColor (WSColor) <NSCopying>
    @end

Except naming the category we do not need to do anything ourselves here. The actual method definition is taken from the protocol itself.

Implementing the category

The implementation is also straightforward (named WSColor.m):

    #import "WSColor.h"
    @implementation UIColor (WSColor)

    #pragma mark NSCopying
    - (id)copyWithZone:(NSZone *)zone {
        CGColorRef quartzColor = CGColorCreateCopy
                                  ([self CGColor]);
        UIColor *copy = [[[self class]
                          allocWithZone:zone]
                         initWithCGColor:quartzColor];
        CGColorRelease(quartzColor);

        return copy;
    }

    @end

You will notice that we are using the basic Quartz functions for doing the actual work of duplicating an instance of a color. This is far superior to using workarounds such as serializing the object to a string or a data pool and then immediately deserializing it.

The WSFont category

Defining the interface

The interface definition is again very simple:

    #import <Foundation/Foundation.h>
    @interface UIFont (WSFont) <NSCopying>
    @end

Implementing the category

Again, implementation is straightforward. Note that unlike in the case of colors we do not need to use the Quartz functions to extract all information since the functionality of UIFont is limited and all necessary information can be extracted from its properties. The implementation is thus:

    #import "WSFont.h"
    @implementation UIFont (WSFont)

    #pragma mark NSCopying
    - (id)copyWithZone:(NSZone *)zone {
        return [[UIFont fontWithName:[self fontName]
                                size:[self pointSize]] retain];
    }

    @end

Introducing CSS standard colors

I was also dissatisfied with the default color constants that Cocoa provides. So while being at it I have added a couple of factory methods to implement all colors defined in the CSS standard.

These methods are:

    + (UIColor *)CSSColorWhite;
    + (UIColor *)CSSColorSilver;
    + (UIColor *)CSSColorGray;
    + (UIColor *)CSSColorBlack;
    + (UIColor *)CSSColorRed;
    + (UIColor *)CSSColorMaroon;
    + (UIColor *)CSSColorYellow;
    + (UIColor *)CSSColorOlive;
    + (UIColor *)CSSColorLime;
    + (UIColor *)CSSColorGreen;
    + (UIColor *)CSSColorAqua;
    + (UIColor *)CSSColorTeal;
    + (UIColor *)CSSColorBlue;
    + (UIColor *)CSSColorNavy;
    + (UIColor *)CSSColorFuchsia;
    + (UIColor *)CSSColorPurple;
    + (UIColor *)CSSColorOrange;

Download and conclusions

The source code shown above licensed under the GPLv3 can be downloaded as a gzipped tar-ball. Alternatively, you can just copy&paste the minimal code for WSColor from the category implementation without the CSS colors from this page.

Note that it is not necessary to use these categories on MacOS X for Cocoa development since both the NSColor and NSFont classes implement the NSCopying protocol right out of the box.