Advanced programming tips, tricks and hacks for Mac development in C/Objective-C and Cocoa.

Heterogeneous cells in a UITableViewController

This post looks at writing a UITableViewController for a table view that contains behaviorally unrelated rows — a common occurrence on the iPhone for tables containing multiple groups. I will present a simple alternative to Apple's UITableViewController template code that will reduce complexity and code as well as refocus areas-of-concern for this heterogeneous arrangement.

Update: If you're interested in approaches to managing and customizing UITableViews and UITableViewCells, you might be interested in seeing my more up-to-date post: UITableView construction, drawing and management (revisited); it represents an evolution and refinement of the ideas presented in this post.

The sample app

I'm going to present the following iPhone Phone Numbers sample app:

phonenumbers.png

The top group contains a simple, statically constructed text link to a child UIViewController. The bottom group contains multi-column display of data from the iPhone's Address Book in rows with no selection behavior.

The focus of the post will be to look at the design of the UITableViewController subclass for the visible table and how it provides two distinct types of table row, with completely different layouts, data sources and behaviors.

Over-use of template code

My reason for writing this post is that I don't think the Apple-provided UITableViewController template code should be used outside of trivial examples. But Apple's example is powerful: when I see other programmers' iPhone code, they rarely use anything else.

How the Apple-provided template works

The most important method that the UITableViewController template provides is:

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView
    cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView
        dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero
            reuseIdentifier:CellIdentifier] autorelease];
    }
    
    // Set up the cell...

    return cell;
}

If you're not familiar with this code sample, I'll quickly explain: when a UITableView needs to display a row, it invokes this method on its data source (the UITableViewController) and this method constructs the UITableViewCell for the row, populates it with data and returns it.

The problem with the template

This code implies a situation where each UITableViewController you create is directly responsible for providing the configuration and behavior of every row in the table.

From a design perspective, there are three distinct issues with this:

  1. If the table contains more than one type of row, the controller must handle this and each type will increase the number of code paths for construction and behavior.
  2. There is no abstraction between the UITableViewController and the UITableViewCell, which encourages the programmer towards UITableViewCell subclasses to provide row-specific behaviors that they choose to keep out of the UITableViewController (an unnecessary subclass of a view to add controller functionality).
  3. If your program contains multiple UITableViewControllers they will each need to replicate this code.

Following the template exactly, UITableViewController subclasses become huge classes, repeated multiple times throughout the program, that spend most of their time managing the behaviors of their cells.

Reducing the responsibilities of the UITableViewController

The best way to improve the UITableViewController is to refocus it on its required behaviors. These are:

  1. View setup

(There will probably be some top-level behavior to handle in many cases but a list with just one item seemed more impactful.)

To that end, I have reduced the entire code for the table view controller in the sample app down to:

- (void)constructTableGroups
{
    NSMutableArray *linkRows = [NSMutableArray array];
    [linkRows addObject:
        [[[LinkRowCellController alloc]
            initWithLabel:@"Link to another page"
            controllerClass:[PlainViewController class]]
        autorelease]];

    NSMutableArray *phoneNumberRows = [NSMutableArray array];
    for (NSDictionary *entry in addressBookDataSource.phoneNumbers)
    {
        [phoneNumberRows addObject:
            [[[PhoneNumberCellController alloc]
                initWithPhoneNumberData:entry]
            autorelease]];
    }
    
    tableGroups =
        [[NSArray arrayWithObjects:linkRows, phoneNumberRows, nil] retain];
}

The class no longer needs to directly implement any of the standard methods and requires just a single construction method.

What I've done is reduced the responsibility of the controller down to establishing the structure of the view and populating it with the data (in this case, data from the addressBookDataSource.phoneNumbers).

Class redesign

The reduction in complexity for the UITableViewController comes from two changes: moving generic behavior into a superclass and specific behavior into child classes.

CellController

The "specific" behavior in this case is row-specific behavior. Row-specific behavior is moved into the "Cell Controllers" (LinkRowCellController and PhoneNumberCellController).

CellController is a protocol. In the sample app, it only contains two methods (tableView:cellForRowAtIndexPath: and tableView:didSelectRowAtIndexPath:) but in a fully fledged application, it would be exanded to further mimic UITableViewDelegate and UITableViewDataSource.

To see how these work, let's look at the cell construction code for LinkRowCellController:

- (UITableViewCell *)tableView:(UITableView *)tableView
    cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellIdentifier = @"LinkDataCell";
    
    UITableViewCell *cell =
        [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (cell == nil)
    {
        cell =
            [[[UITableViewCell alloc]
                initWithFrame:CGRectZero
                reuseIdentifier:cellIdentifier]
            autorelease];
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    }
    
    // Configure the cell
    cell.text = label;
    
    return cell;
}

You can see that it's largely the same code that might have appeared in a UITableViewController but offers the following advantages:

  • won't grow in behavioral complexity (since it manages just one row type)
  • keeps a narrow area-of-concern by connecting a single view element (cell) to a single piece of data (label)
GenericTableViewController

The "generic" behavior is moved into GenericTableViewController which provides a basic architecture for lazy construction of the tableGroups instance variable and routing of all row-specific behaviors through to the relevant CellControllers.

When tableGroups construction is needed, the method constructTableGroups is invoked, which is the only method required for custom behavior.

Limitations to this approach

This design is only useful in situations where you have the data object for every single row at all times. It is not particularly good in situations where you don't have the data for every row up-front (for example, data fetched from an NSFetchedResultsController).

Bonus features in the sample app

Unrelated to the main content of this article, the sample app also fetches the phone numbers of all contacts from the Address Book using ABAddressBookCreate, ABAddressBookCopyArrayOfAllPeople and ABRecordCopyValue. Have a look at the AddressBookDataSource class in the project if you're curious to see how this is done.

The PhoneNumberCellController shows how to perform custom layout and arrangement in the contentView of a UITableViewCell to achieve a somewhat customized appearance without subclassing. This is trivial stuff but is a reminder to programmers still stuck in the NSCell subclass habit from Mac OS X (you know who you are) that UITableViewCell subclasses are rarely required.

Conclusion

Download the complete code for the sample app in the PhoneNumbers Xcode 3.1 project (34kB).

When writing a post, it's hard for me to know if a topic is too obvious. This topic is certainly at the obvious end of the spectrum but I'm hoping it will nudge a few culprits to add a proper base-class and move their row-specific behaviors into resuable, row-specific classes.

I realize that programmers may face confidence issues when an authoritative source like Apple provides an example. Programmers may avoid obvious improvements because they don't want to question the authority's advice.

Apple's code is just a template and typical of a template, it doesn't force any particular arrangement of data upon you. However, choosing a common data arrangement allows for moving common code to handle that data into a base-class, which can further provide common behaviors throughout your application.

The design pattern of separate CellController implementations to simplify a UITableViewController should apply, in a more generalised way, throughout your programming: you shouldn't have multiple conditionals switching on the same piece of data. In this case, we avoided conditionals switched on the row index by creating an object for each row that contains row-specific behavior. This is object-oriented programming: if you're using switch statements or other compound, data-switched conditionals in your user-interface code, you're probably doing it wrong.

Read more...

OrderedDictionary: Subclassing a Cocoa class cluster

The default Cocoa collection classes are highly capable but there are situations where you may need to override them to alter their functionality. I'll explain when and how you should do this with an example class: OrderedDictionary (an NSMutableDictionary subclass that maintains ordering of its keys).

Best practice: don't subclass

This post is going to discuss subclassing NSMutableDictionary. You should note that this is a special case; most of the time when you add functionality to a collection class, you should not override it.

Most classes are not designed with subclassing in mind. If you are not the author of a class, choosing to override it incurs a risk that your additions won't maintain implicit or private values and behaviors of the object correctly.

Increased risk due to subclassing does not apply to classes that are intended to be subclassed before use. Prominent examples include NSObject, NSWindowController, NSView, NSControl and NSCell. With classes designed for use through subclasses, any risk associated with the subclassing itself is more than outweighed by the well-tested base functionality that they provide, which cannot be accessed unless the class is subclassed.

Aside: design patterns that customize behavior without subclassing
  1. Put the custom functionality in the parent which contains the non-overridden object, rather than in the non-overridden object itself. This is "has-a" design instead of "is-a".
  2. Use a Decorator object instead. A Decorator is a wrapper around the non-overridden class. All messages to the contained class go through the Decorator first, so the Decorator can control or supplement the behavior of the contained class.
  3. Use an Observer to keep the non-overridden object synchronized with dependent objects so that their combined state achieves custom behavior, even though each of the objects remains non-custom. In this case, the Observer acts as a Controller/Manager to the dependent objects.

Choosing to override a collection class

The collection classes in Cocoa are class clusters. In some respects, this means that they are intended to be overridden, since the base class is functionally abstract and contains none of the required concrete functionality.

Default concrete functionality for a class cluster is provided by hidden subclasses, returned transparently by the base class. Since this is transparent, you are not normally expected to write the subclasses yourself. Despite this expectation, the interface has been designed with subclassing in mind, so we can easily implement the required subclass behavior ourselves instead of relying upon one of the default implementations.

The functionality which constitutes "concrete" behavior for a class cluster is given in Apple's documentation as the "primitive" methods of the cluster. For collection classes (like NSMutableDictionary), these "primitive" methods provide all behavior for the collection's storage. Since we inherit no default behavior for these primitive methods, this means that the decision to make subclass of a collection class in Cocoa is a decision to reimplement the storage.

Design of an Ordered NSMutableDictionary subclass

I have chosen to implement an ordered version of NSMutableDictionary, named OrderedDictionary. This requires ordered storage of dictionary keys but NSDictionary stores its keys in a hash table, which is unordered by design. Since this lack of order is fundamental to the hash table storeage, our subclassing of NSMutableDictionary (and hence reimplementation of the storage) is appropriate.

However, I will reimplement basic storage for my subclass by storing all objects and keys in an unmodified NSMutableDictionary. From a design point-of-view, this will make my subclass a Decorator around the unmodified NSMutableDictionary, rather than a pure subclass.

As messages are passed from the "primitive" methods on my subclass through to the contained NSMutableDictionary, I will also store the dictionary's keys in a separate NSMutableArray, thereby allowing the OrderedDictionary to keep the ordering for the keys, in addition to standard NSMutableDictionary functionality.

Implementing the primitive methods

Apple's documentation lists the following "primitive" methods for NSMutableDictionary:

  • count
  • objectForKey:
  • keyEnumerator
  • setObject:forKey:
  • removeObjectForKey:

In reality, the following method is also required, despite not being listed:

  • initWithCapacity:

In accordance with the Decorator pattern that I've chosen, almost all of these are implemented by passing the message through to the contained NSMutableDictionary (named "dictionary"), with keys also added to the NSMutableArray (named "array") and the keyEnumerator method returning the objectEnumerator of "array":

- (id)initWithCapacity:(NSUInteger)capacity
{
    self = [super init];
    if (self != nil)
    {
        dictionary = [[NSMutableDictionary alloc] initWithCapacity:capacity];
        array = [[NSMutableArray alloc] initWithCapacity:capacity];
    }
    return self;
}

- (void)dealloc
{
    [dictionary release];
    [array release];
    [super dealloc];
}

- (void)setObject:(id)anObject forKey:(id)aKey
{
    if (![dictionary objectForKey:aKey])
    {
        [array addObject:aKey];
    }
    [dictionary setObject:anObject forKey:aKey];
}

- (void)removeObjectForKey:(id)aKey
{
    [dictionary removeObjectForKey:aKey];
    [array removeObject:aKey];
}

- (NSUInteger)count
{
    return [dictionary count];
}

- (id)objectForKey:(id)aKey
{
    return [dictionary objectForKey:aKey];
}

- (NSEnumerator *)keyEnumerator
{
    return [array objectEnumerator];
}

You can download the complete OrderedDictionary class here (2kb). I've further added init, insertObject:forKey:atIndex:, keyAtIndex:, reverseKeyEnumerator and descriptionWithLocale:indent: methods to the downloadable version to fill out some non-essential functionality.

Conclusion

Creating a subclass of NSMutableDictionary is made relatively simple by the clean set of "primitive" methods for the class cluster. With these implemented, the remaining rich functionality of NSDictionary and NSMutableDictionary become available; no further work required.

My point about the risks of subclassing should become apparent with the undocumented requirement of initWithCapacity:. The necessity of this method is not mentioned in the documentation, so I was forced to use feedback from debugging information to rework the design of the class to reach a functional implementation — trivial in this case but annoying nonetheless.

The inclusion of the descriptionWithLocale:indent: method in the class is another lesson along the same lines: the default implementation of this method assumes it can reorder the keys returned from keyEnumerator without causing problems. After seeing erroneous results during testing, I was required to replace this method to remove the incorrect assumption.

Read more...

Drawing a custom window on Mac OS X

Occasionally, you may want a window to look completely different to the standard window styles provided by Apple. This post will show you how to draw a custom window and implement close, resize and drag functionality.

Introduction

In this post, I will present a custom window and frame class that will draw the following window:

roundwindow.png

Clicking and dragging in any part of the frame of this window will drag the window, except the gray square (which operates as a resize thumb) and the close box (which operates in a standard manner).

You can download the complete Xcode 3.1 project for RoundWindow (63kB).

Constructing a transparent window

Making a custom window starts with a transparent window. I will use a custom NSWindow subclass named RoundWindow. The constructor for this subclass looks like this:

- (id)initWithContentRect:(NSRect)contentRect
    styleMask:(NSUInteger)windowStyle
    backing:(NSBackingStoreType)bufferingType
    defer:(BOOL)deferCreation
{
    self = [super
        initWithContentRect:contentRect
        styleMask:NSBorderlessWindowMask
        backing:bufferingType
        defer:deferCreation];
    if (self)
    {
        [self setOpaque:NO];
        [self setBackgroundColor:[NSColor clearColor]];
    }
    return self;
}

The three changes made to the window by this constructor are fairly obvious:

  • NSBorderlessWindowMask (a window without standard window framing)
  • setOpaque:NO (so that any part of the window can be transparent)
  • setBackgroundColor:[NSColor clearColor] (if we do nothing else, this will paint the window transparent)

The result is a transparent, rectangular window. This method can be invoked directly (if creating a window in code). It will also be invoked by the NIB loader when loading the window from a NIB.

Since this window uses the NSBorderlessWindowMask style, we must override the canBecomeKeyWindow and canBecomeMainWindow methods to return YES. These overrides will allow the window to be the keyboard focus and primary application window respectively.

Space for the window frame

Now that we have a clean, blank slate, we need to draw the frame of the window. The first requirement is some space around the content of the window to draw the frame.

To create this space, we override the content to frame conversion methods:

- (NSRect)contentRectForFrameRect:(NSRect)windowFrame
{
    windowFrame.origin = NSZeroPoint;
    return NSInsetRect(
        windowFrame, WINDOW_FRAME_PADDING, WINDOW_FRAME_PADDING);
}

+ (NSRect)frameRectForContentRect:(NSRect)windowContentRect
    styleMask:(NSUInteger)windowStyle
{
    return NSInsetRect(
        windowContentRect, -WINDOW_FRAME_PADDING, -WINDOW_FRAME_PADDING);
}

This will add WINDOW_FRAME_PADDING (75) pixels border around the content rectangle. Actually, since this is a borderless window, the content view itself will be expanded by 75 pixels on each side. The next part will compensate for this, so the content view will be the expected, non-expanded size.

Creating the window frame

The normal structure of a window on Mac OS X is:

  • NSWindow
    • NSThemeFrame (draws the standard window frame)
      • NSView (the window's contentView)

My first thought was to try replacing the existing theme frame view. However, this quickly proved to be too difficult. This private subclass of NSView handles many quirks associated with view hierarchy, layout and window drawing that I didn't want to implement myself.

Instead, I chose to override the setContentView: and contentView: accessors on the window so that my frame view is always inserted between the theme frame and the contentView:

  • NSWindow
    • NSNextStepFrame (the theme frame for a borderless window)
      • RoundWindowFrameView (the contentView as seen by the NSWindow)
        • NSView (the contentView as seen by other classes)

The setContentView: override on the RoundWindow looks like this:

- (void)setContentView:(NSView *)aView
{
    if ([childContentView isEqualTo:aView])
    {
        return;
    }
    
    NSRect bounds = [self frame];
    bounds.origin = NSZeroPoint;

    RoundWindowFrameView *frameView = [super contentView];
    if (!frameView)
    {
        frameView =
            [[[RoundWindowFrameView alloc]
                initWithFrame:bounds]
            autorelease];
        
        [super setContentView:frameView];
    }
    
    if (childContentView)
    {
        [childContentView removeFromSuperview];
    }
    childContentView = aView;
    [childContentView setFrame:[self contentRectForFrameRect:bounds]];
    [childContentView
        setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable];
    [frameView addSubview:childContentView];
}

So this method inserts the RoundWindowFrameView in between the content view and the NSNextStepFrame. The childContentView is an instance variable on our RoundWindow class, used to find this view again later (if we are asked to fetch, replace or resize it).

Creating this disparity between the internal content view (our RoundWindowFrameView) and the external content view (the childContentView) creates some issues. Other NSWindow methods like setContentSize: will also need to be overridden if they are to behave as expected. These overrides should be relatively straightforward and simple, compared to the work that would have been required to replace the NSNextStepView with our RoundWindowFrameView.

Drawing the frame

We can draw the window frame however we want: it's just a custom NSView. We draw the background of the view with [NSColor clearColor] again. The shadow behind the window is drawn automatically for whatever shape we draw. Any part of the window that is left completely clear will not receive mouse clicks (they will fall through the window).

For my custom window, I add a standard close box. The standard close control can be created using the method:

closeButton = [NSWindow
    standardWindowButton:NSWindowCloseButton forStyleMask:NSTitledWindowMask];

There are two quirks. First: it doesn't update itself when the window becomes/resigns mainWindow status (so we must detect this and tell it to redisplay). Second: the mouse "rollover" effect doesn't seem to work (I was unable to address this problem).

Handling window controls

The close button will close the window without us doing anything else. Drag and resize behavior will require work on our part.

Both dragging and resizing the window involve changing its frame. So they can both be done by calling:

[window setFrame:newFrame display:YES animate:NO];

If we continuously call this method in a focus locked event loop that tracks NSLeftMouseDraggedMask operations, then the updates will happen smoothly as the mouse is dragged.

This event loop looks like this:

while (YES)
{
    NSEvent *newEvent = [window
        nextEventMatchingMask:(NSLeftMouseDraggedMask | NSLeftMouseUpMask)];
    
    if ([newEvent type] == NSLeftMouseUp)
    {
        break;
    }
    
    // Determine the frame changes for the mouse operation and apply...

}

The code for determining the frame changes, while straightforward, is a little long to include here but you can look at the mouseDown: method in the RoundWindowFrameView to see how it's done.

Conclusion

You can download the complete Xcode 3.1 project for RoundWindow (63kB).

Drawing a custom window with no behavior is simple but as with other types of custom controls, numerous little behavioral additions may be needed to make it feel truly polished.

As a resizable circular window, the RoundWindow suffers from its fixed 75 by 75 pixel frame width. This is too big for small windows and not enough for large windows to properly enclose the content view in a circle. To properly enclose a rectangular content view in a circular frame, the frame would need a variable width (something I didn't want to set up for this simple post).

A custom window may also want to handle truncation of the title when it gets too long. Other window controls like the toolbar button, toolbar, minimize and zoom buttons and the "unsaved changes" status on the close button are all responsibilities of the window frame that have not been investigated here.

Paramanoir has posted an article that draws a custom window in a different way: by swizzling a new drawRect: method into the default NSThemeFrame class at runtime. This has a few advantages (NSThemeFrame continues to handle the window controls) so you may want to consider that approach as an alternative.
Read more...

Instance variable to synthesized property (an Xcode user script)

Xcode user scripts take the repetition out of many aspects of programming. To show you how this can work, here's a script I wrote to turn an instance variable into a property (complete with declaration and synthesis) just by selecting the variable and invoking the script.

If you just want the solution, download the script here: PropertyFromInstanceVariable.zip.

Update 2008-09-29: the script now contains additions from Yung-Luen Lan & Mike Schrag (multiple line support) and Pierre Bernard (underbar storage name, behavior, dealloc).

The repetition that I want to eliminate

For a class with a header file that looks like this:

@interface MyClass : NSObject
{
    NSString *someString;
}

@end

turning someString into an Objective-C 2.0 property involves two steps. First, add the property declaration to the header:

@interface MyClass : NSObject
{
    NSString *someString;
}

@property (nonatomic, retain) NSString *someString;

@end

and second, add the

@synthesize someString;

line to the implementation.

This isn't very much work but it's still menial and boring stuff. Also, even though it's simple, it is one of the most commonly performed tasks in Objective-C 2.0 programming, since every class will likely have multiple properties that will need to be created and configured in a similar fashion.

Instead of creating these two lines of code in the two different files, I would prefer to select the text "NSString *someString;" (by triple clicking on that line) and telling Xcode to instantly make it into a property with no further effort on my part.

Xcode user scripts

Xcode Text Macros are good for inserting boilerplate text at the insertion point but for this problem, we'll need something more programmable: Xcode User Scripts.

You can create and edit Xcode's user scripts by selecting the "Edit User Scripts..." item from the bottom of the Scripts menu.

The main method of operation for these scripts is that Xcode replaces various macros (identifiers) in the scripts before the script is invoked (macros like %%%{PBXAllText}%%% and %%%{PBXSelectionStart}%%%) and on completion, the "standard out" from the script can be applied to the document (to replace the current selection or document or insert text at the current insertion point).

I'm not going to talk too much about the basics of Xcode's user scripts here. You can read about them in Apple's documentation Xcode Workspace Guide: User Scripts if you need to know more.

Instead I'm going to highlight what I consider to be their biggest limitation: they aren't really intended for making changes to multiple documents. The macros that Xcode user scripts can include allow easy access to the current document and current selection but for a script to access data from other documents is difficult.

However, this is exactly what I want to do: change both the header file and the implementation file for a class in order to add the two additions to make the variable into a property.

An Applescript inside a Perl script

To get around the limitations of the Xcode user scripts setup, I'm going to use the current file name (available to user scripts as %%%{PBXFilePath}%%%) to look for the ".m" or ".mm" file with the same name in the same directory.

To complicate things, I can't just open the file on disk. Since Xcode is necessarily open when the script is invoked and the class is currently being edited, I have to assume that there may be unsaved changes to the file. For this reason, I use an Applescript to ask Xcode to select the file and give me its current text (which will include any unsaved changes).

In my Perl user script, getting the path to the implementation file and using Applescript to get the text of the implementation file looks like this:

my $implementationFilePath = "%%%{PBXFilePath}%%%";

# Replace the current file's extension with an "m"
$implementationFilePath =~ s/\.[hm]*$/.m/;
if (!(-e $implementationFilePath))
{
    # If no implementation file is found, try replacing with "mm"
    $implementationFilePath =~ s/.m$/.mm/;
}

# If still doesn't exist, don't continue
if (!(-e $implementationFilePath))
{
    exit 1;
}

# Applescript to read 1st argument as path, open in Xcode and get contents
my $getFileContentsScript = <<'GETFILESCRIPT';
on run argv
    set fileAlias to POSIX file (item 1 of argv)
    tell application "Xcode"
        set doc to open fileAlias
        set docText to text of doc
    end tell
    return docText
end run
GETFILESCRIPT

# Invoke the Applescript and get the results
open(SCRIPTFILE, '-|') || exec 'osascript', '-e', $getFileContentsScript, $implementationFilePath;
my $implementationFileContents = do {local $/; };
close(SCRIPTFILE);

Yes, I know this is a Cocoa blog and that's Perl and Applescript. I'm sorry. I felt dirty writing it.

Parsing the variable declaration

The other important step for a user script to turn a variable declaration into a property is the step of parsing the declaration itself.

This will serve two purposes:

  • Verify that the user has actually selected a variable declaration
  • Detect a pointer (we will assume pointer variables are objects) and use the access specifiers (nonatomic, retain) instead of default access.

The regular expression I use to match is:

/([_A-Za-z][_A-Za-z0-9]*\s*)+([\s\*]+)([_A-Za-z][_A-Za-z0-9]*);/

This will match three different sections:

  1. A series of C identifiers, separated by spaces (the variable type and qualifiers)
  2. Asterisks and spaces in the middle
  3. Another C identifier (the variable name)

The regular expression requires a semi-colon at the end of the selection but the semi-colon character is not extracted into any of the three match values.

These three matched values are used to build the property declaration and the synthesize statement. The property declaration will be (nonatomic, retain) if the second matched value contains exactly one asterisk.

Limitations

This isn't a particularly thorough attempt to match a variable declaration. Lots of valid variable declarations will fail to be matched by this pattern. These include:

  • Variables with "const" written to the right of the pointer asterisk (i.e. SomePointerType * const myPointer;)
  • Most C-style function pointers
  • Any variable declaration with a character that isn't an underscore, alphanumeric or asterisk
  • Any declaration where the last non-whitespace character is not a semi-colon
  • Any declaration containing a comment

For any of these cases, the match will fail and the script won't do anything.

Beyond this, many type qualifiers used for variables should not be included in the property declaration. This approach will always include them with the type name, so they may need to be manually removed afterwards.

Putting it all together

The script performs the following tasks in order:

  1. Get the full text of the current file (which is assumed to be the header file)
  2. Grab the current selection from the full text and parse it using the regular expression (shown above)
  3. Build a property declaration as appropriate for the matched variable declaration and insert it after the first closing brace found at the start of a line following the selection (this is assumed to be the end of the variables section of the class declaration)
  4. Apply this modification to the header file
  5. Get the contents of the implementation file, using the Perl/Applescript shown above
  6. Insert a synthesize statement after the first @implementation statement found in the file (it is assumed that the first statement is the appropriate one
  7. Commit the changes to the implementation file using Applescript again

To see how this is done, download the PropertyFromInstanceVariable.zip and have a look. It should be adequately readable — I've commented most of it and Perl is relatively C-like.

Installation

To install, open the "Edit User Scripts" item in the "Scripts Menu" of Xcode, create a new shell script, delete any content in the new script and paste in the contents of PropertyFromInstanceVariable.pl.

You will also need to set the "Input" popup menu to "Entire Document". You should also set the "Directory" popup menu to "Home Directory", the "Output" popup menu to "Discard Output" and the "Errors" popup menu to "Display in Alert".

You can set a keyboard shortcut for your script by double clicking in the "Command" column next to your script's name, otherwise you can select its entry from the "Script Menu" at the top of the screen.

Conclusion

Writing an Xcode user script like this can involve a couple hours of setup and may still make many assumptions in order to work.

Despite this effort, taking a couple hours to turn your most repetitive tasks into completely automated actions can save you time in the long run — and can make you feel in control of your programming environment.

Read more...