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

Using libxml2 for XML parsing and XPath queries in Cocoa

NSXMLDocument is the normal tree-based XML parser in Cocoa. But if you're writing for the iPhone, this class isn't available. Even on the Mac, sometimes you want tree-based parsing without the full overhead of NSXMLDocument. Here's how to use libxml2 to perform tree-based parsing in a Cocoa-friendly way.

Introduction

NSXMLDocument is an excellent XML parser and XML document generator. Sadly, Apple have chosen not to include it in the current iPhone SDK. Apple instead recommend NSXMLParser on the iPhone.

Personally, I don't like the "event-driven" parsing of NSXMLParser. For the types of project I find myself writing, it is time-consuming and fiddly. I also like throwing HTML at my XML parsers and NSXMLParser (which is a strict, non-correcting parser) requires that HTML be cleaned-up first (using libtidy or similar), which eliminates much of the performance gain from this type of parsing anyway.

Fortunately, libxml2 exists on the iPhone and we can use it to perform much of the same parsing that NSXMLDocument performs for us on the Mac.

Other programmers have noted that libxml2 can be faster and more memory efficient than NSXMLDocument on the Mac, so there may be reasons to use libxml2 directly, even when NSXMLDocument is available.

Downside to libxml2

libxml2 itself is a fairly simple, clean library but the official documentation is famously confusing. The documentation is really just a slightly commented version of the header files — not a great way to learn. Being pure C, the structure and style of the declarations and the datatypes used are also a long way from what is normally expected in Cocoa.

If you want to use libxml2 in Cocoa, you'll want a wrapper around it.

Proposed solution

Reflecting the manner in which I use XML, my solution will have two functions declared as follows:

NSArray *PerformXMLXPathQuery(NSData *document, NSString *query);
NSArray *PerformHTMLXPathQuery(NSData *document, NSString *query);

For an entire XML document, contained in the NSData object "document", this function executes the XPath query in the NSString "query" and returns an NSArray of NSDictionary node objects for nodes that match the query.

The only difference between the two listed functions is that the the first expects proper XML data and the second expects HTML data.

Each result in the array of nodes returned will be an NSDictionary with the following structure:

  • nodeName — an NSString containing the name of the node
  • nodeContent — an NSString containing the textual content of the node
  • nodeAttributeArray — an NSArray of NSDictionary where each dictionary has two keys: attributeName (NSString) and nodeContent (NSString)
  • nodeChildArray — an NSArray of child nodes (same structure as this node)

Any of these fields may absent if not found in the libxml2 result.

If you don't know how or why to use an XPath query on an XML document, please look at my previous post titled A Cocoa application driven by HTTP data which shows how XPath queries can be used to extract specific sections of data from an HTML document.

The implementation

Download the full solution here: XPathQuery.m and XPathQuery.h as a 2kb zip file.

The implementation is very straightforward. The entry point looks like this:

NSArray *PerformXMLXPathQuery(NSData *document, NSString *query)
{
    xmlDocPtr doc;
    
    /* Load XML document */
    doc = xmlReadMemory(
        [document bytes], [document length], "", NULL, XML_PARSE_RECOVER);
    
    if (doc == NULL)
    {
        NSLog(@"Unable to parse.");
        return nil;
    }
    
    NSArray *result = PerformXPathQuery(doc, query);
    xmlFreeDoc(doc); 
    
    return result;
}

The only real difference in the PerformHTMLXPathQuery version is that it calls htmlReadMemory instead of xmlReadMemory.

The query itself is then performed in an internal function common to both entry functions:

NSArray *PerformXPathQuery(xmlDocPtr doc, NSString *query)
{
    xmlXPathContextPtr xpathCtx; 
    xmlxpathObjectPtr xpathObj; 

    /* Create XPath evaluation context */
    xpathCtx = xmlXPathNewContext(doc);
    if(xpathCtx == NULL)
    {
        NSLog(@"Unable to create XPath context.");
        return nil;
    }
    
    /* Evaluate XPath expression */
    xmlChar *queryString =
        (xmlChar *)[query cStringUsingEncoding:NSUTF8StringEncoding];
    xpathObj = xmlXPathEvalExpression(queryString, xpathCtx);
    if(xpathObj == NULL) {
        NSLog(@"Unable to evaluate XPath.");
        return nil;
    }
    
    xmlNodeSetPtr nodes = xpathObj->nodesetval;
    if (!nodes)
    {
        NSLog(@"Nodes was nil.");
        return nil;
    }
    
    NSMutableArray *resultNodes = [NSMutableArray array];
    for (NSInteger i = 0; i < nodes->nodeNr; i++)
    {
        NSDictionary *nodeDictionary = DictionaryForNode(nodes->nodeTab[i], nil);
        if (nodeDictionary)
        {
            [resultNodes addObject:nodeDictionary];
        }
    }

    /* Cleanup */
    xmlXPathFreeObject(xpathObj);
    xmlXPathFreeContext(xpathCtx); 
    
    return resultNodes;
}

The work done is here simple: create the working space for the XPath query on the document, evalute the XPath query, get all the nodes from the result and use the DictionaryForNode function to parse them into our NSDictionary objects, and clean up when done.

The implementation of the DictionaryForNode function is the only one I haven't shown. If you download the full solution, you can see how it's done. It's a bit bigger than I want to dump into my blog's text but it really just traverses the libxml2 xmlNodePtr structures, getting the fields it needs and converting them to NSString, NSArray and NSDictionary as appropriate.

Setting up your project file

You need to add libxml2.dylib to your project (don't put it in the Frameworks section). On the Mac, you'll find it at /usr/lib/libxml2.dylib and for the iPhone, you'll want the /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.0.sdk/usr/lib/libxml2.dylib version.

Since libxml2 is a .dylib (not a nice friendly .framework) we still have one more thing to do. Go to the Project build settings (Project->Edit Project Settings->Build) and find the "Search Paths". In "Header Search Paths" add the following path:

$(SDKROOT)/usr/include/libxml2

Conclusion

This solution will let you get the results of an XPath query on the iPhone in nice Cocoa friendly objects.

I've only tested this on textual data — I don't know how it will behave on XML CDATA.

If you don't want an XPath query (for example: if you need the whole document) you can either run the query "/" to get the root node or drop the PerformXPathQuery function and instead run DictionaryForNode on the children of the xmlDocPtr or even cast the xmlDocPtr to an xmlNodePtr and run it directly on that (in either case, pass NULL in as the parentResult to DictionaryForNode).

Read more...

Debugging tips for Objective-C programming

This post is about getting extra information from your program at runtime. Xcode and gdb both support a wide range of information access tools — but you need to know that they're there. Here are some Objective-C specific gdb tips and commands that all Cocoa programmers should know.

Talking to gdb directly

The Debugger Console window is the way to talk to gdb. Show the console window in Xcode from the Run menu (or type command-shift-R).

consolewindow.png

You can only send commands to gdb when the program is paused (stopped at a breakpoint). When you have the (gdb) prompt, then you can talk to gdb.

Most commands that gdb accepts, Xcode handles automatically for you by showing the values in the Debugger window. So I'm going to ignore most of them.

"po" : print object

The print object command displays a textual representation of an Objective-C object.

Imagine you wanted to know why your call to:

- (id)getFirstObjectFrom:(NSDictionary *)stringDictionary
{
    return [stringDictionary objectForKey:@"FirstKey"];
}

is returning nil. Set a breakpoint on the line and when the debugger stops at that point, go to the Debugger Console and type:

po stringDictionary

Hit return and gdb will give the result. In my case, it was:

{
    firstKey = firstObject;
    secondKey = secondObject;
    thirdKey = thirdObject;
}

The key name I should have used was @"firstKey" with a lowercase 'f'. Problem solved.

In this case, gdb is invoking the description method on the NSDictionary to generate a string. The description method is used all throughout Cocoa to generate strings from objects and you can override it to provide a string representation of your objects.

Xcode data formatters

If the debugger is still stopped at the same line and you open the Debugger window in Xcode, the "Arguments" list of variables will contain an entry for stringDictionary. For an NSDictionary object like this, Xcode will show "3 key/value pairs" in the "Summary" column.

This information comes from a "Data Formatter" that is set up for NSDictionary by default. You can learn about them in Apple's Xcode Debugging Guide: Using Data Formatters. Essentially, a data formatter tells Xcode how to fetch some data to display in the column.

A different data formatter can be seen if we right-click on the stringDictionary row in the Debugger window and choose "Print Description to Console" from the context menu.

Printing description of stringDictionary:
<CFDictionary 0x35edd0 [0xa0b06174]>{type = immutable, count = 3, capacity = 3, pairs = (
	0 : <CFString 0x2090 [0xa0b06174]>{contents = "secondKey"} = <CFString 0x2080 [0xa0b06174]>{contents = "secondObject"}
	1 : <CFString 0x20b0 [0xa0b06174]>{contents = "thirdKey"} = <CFString 0x20a0 [0xa0b06174]>{contents = "thirdObject"}
	3 : <CFString 0x2070 [0xa0b06174]>{contents = "firstKey"} = <CFString 0x2060 [0xa0b06174]>{contents = "firstObject"}
)}

Without a data formatter, "Print Description to Console" will output the same information as the "po" command. In this case though, it is clearly outputting a much more detailed description of the dictionary, with full type information and indices. Exactly where this CFDictionary data formatter resides is unknown to me (it's not in the standard locations for Xcode Data Formatters).

Other "print" commands

The po command in gdb only ever shows the result from invoking description on an object but the more general print command will let us do other things.

I could have used the command:

print (char*)[[stringDictionary description] cString]

which would have output:

$1 = 0x360031 "{\n    firstKey = firstObject;\n    secondKey = secondObject;\n
    thirdKey = thirdObject;\n}"

which is the same data representation as the po example, minus the nice formatting.

I could also use the command:

print (int)[stringDictionary retainCount]

to work out the retain count of the stringDictionary to help me work out why memory is or isn't getting released.

As with the po command and the "Print Description to Console" equivalent in Xcode, the print command has an Xcode equivalent too. You can open the Expressions window from the Run->Show->Expressions menu.

"info symbol": get the symbolic name for an address

The final gdb command I'm going to discuss here is info symbol address which returns the name of any variable or code associated with the memory location given by address.

Imagine, for example, that you were confused by the memory address "0xa0b06174" shown by the "Print Description to Console" output for stringDictionary that I showed above. All you need to do then is type:

info symbol 0xa0b06174

into the Debugger Console and gdb will tell you:

__kCFAllocatorSystemDefault in section LC_SEGMENT.__DATA.__data of /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation

It's the system default CoreFoundation memory allocator. Maybe that still doesn't mean a whole lot but at least we know the name of the object declared at that address.

More useful is when you get an exception log that looks like this:

2008-10-26 13:25:43.381 CrashExample[41720:20b] *** -[TransitionView doesntExist]: unrecognized selector sent to instance 0xf4fbb0
2008-10-26 13:25:43.383 CrashExample[41720:20b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[TransitionView doesntExist]: unrecognized selector sent to instance 0xf4fbb0'
2008-10-26 13:25:43.385 CrashExample[41720:20b] Stack: (
    2528013804,
    2478503148,
    2528042920,
    2528036272,
    2528036920,
    11076,
    11880,
    816174880,
    816174880,
    816504036,
    816500960,
    816258792,
    816202128,
    816199508,
    829005520,
    829014772,
    2527564456,
    829007724,
    816173016,
    816212336,
    9888,
    9668
)

The log tells us the the problem occurred because an unrecognized selector was sent to an object but we might not know where in the program this occurred.

You can look at the stack trace, see that the highest address that is "small" (i.e. likely to be in your code, not the default libraries) is "11076", then give gdb the command:

info symbol 11076

and gdb will tell you:

-[CrashExampleAppDelegate performTransition] + 88 in section LC_SEGMENT.__TEXT.__text of /Users/matt/Projects/CrashExample.app/CrashExample

which tells us that the problem occurred in the method performTransition.

Update: As noted by "g" in the comments, there is a better way to determine the method and line of code for an address in this case. info line *11076 will give the line in the original source code file, not just the byte offset from the start of the function.

If you look at the 5 addresses above 11076 in the list, they are all associated with throwing the exception itself — which makes it annoying that the Mac OS X 10.5 objc_exception_throw only returns the top 5 addresses because these 5 addresses are often the same exception throwing addresses (they don't say anything about why the exception is thrown).

Addresses and symbols outside the debugger

Since I mentioned reading crash files and looking at memory addresses: gdb isn't the best way to do this if someone else gives you the memory address. To do this, you should have the .dSYM file from the exact build that they are using.

If you don't know how to generate .dSYM files, go to Project->Edit Project Settings->Build->Build Options->Debug Information Format and make sure you have a dSYM file. You should keep these files around for every build you release. The iPhone SDK generates these files by default but you will need to turn in on manually for Mac builds.

Invoke the following on the command-line:

dwarfdump -a NameOfdSYMFile

This will tell you every know address in the file. All you need to do is find the closest preceeding address to your crash location and that will be the culprit method or function.

However, that's a very heavy-handed approach. To get single addresses at a time, put the .dSYM and the .app to which it refers in the same directory and you can use the atos command to get a single symbol for a single address. For the CrashExample crash bug shown above, you could invoke this command like this:

atos -o CrashExample.app/Contents/MacOS/CrashExample -arch ppc 11076

which would give the result:

-[CrashExampleAppDelegate performTransition] (in CrashExample) (CrashExampleAppDelegate.m:94)

Finally, if you want to get all the address from a crash log, you can use Apple's symbolizecrashlog script. This finds the .app and .dSYM files for a .crash.log file and invokes atos to get every possible symbol contained within. Thanks to millenomi for pointing this out in the comments.

Conclusion

There's a lot more information to squeeze out when debugging than just the raw values in your variables. Having access to this information, during debugging, can make tracking down bugs just that little bit faster.

Read more...

Synthesizing a touch event on the iPhone

The iPhone lacks specific methods to create UIEvent and UITouch objects. I'll show you how to add this functionality so you can write programmatically driven user-interfaces.

Update #1: Added a "Device SDK" version that will link correctly outside of the "Simulator SDK".
Update #2: Two bugs have been fixed since the code was originally posted... the objects in the UIEvent member _keyedTouches are now NSSets and the _view and _window in UITouch are now retained.
Update #3: changes to the UITouch and UIEvent categories as well as performTouchInView: to support SDK 2.2 changes.
Update #4: Support for SDK 3.0.

A warning before we begin...

The content of this post is for debugging and testing only. Do not submit this code in an application to the App Store. Doing so will likely result in:

  • A bad UI experience for your users.
  • An app that breaks on every OS update.
  • Rejection of your application.

Synthesized touches are never the right way to trigger actions in a real application. The only useful use for this post is in automated user testing (see my later post Automated User Interface Testing on the iPhone).

User-interface testing

When running application tests, it is helpful to be able to generate user events to test your user-interface. That way, you can run user-interface tests automatically instead of manually.

On Cocoa Senior (also known as "the Mac") we have methods like the gargantuan:

    mouseEventWithType:location:modifierFlags:timestamp:
      windowNumber:context:eventNumber:clickCount:pressure:

to generate events.

Cocoa Junior on the iPhone doesn't have any methods like this, so we must work out how to achieve it ourselves.

UITouch category

A basic touch event normally consists of three objects:

  • The UITouch object — which will be used for the touch down and touch up
  • A first UIEvent to wrap the touch down
  • A second UIEvent to wrap the touch up

Lets look first at creating the UITouch object. Since most of the fields in this object are private, we can't sublcass it or set them directly — everything must be done on a category. My category goes something like this:

@implementation UITouch (Synthesize)

- (id)initInView:(UIView *)view
{
    self = [super init];
    if (self != nil)
    {
        CGRect frameInWindow;
        if ([view isKindOfClass:[UIWindow class]])
        {
            frameInWindow = view.frame;
        }
        else
        {
            frameInWindow =
                [view.window convertRect:view.frame fromView:view.superview];
        }
         
        _tapCount = 1;
        _locationInWindow =
            CGPointMake(
                frameInWindow.origin.x + 0.5 * frameInWindow.size.width,
                frameInWindow.origin.y + 0.5 * frameInWindow.size.height);
        _previousLocationInWindow = _locationInWindow;

        UIView *target = [view.window hitTest:_locationInWindow withEvent:nil];
        _view = [target retain];
        _window = [view.window retain];
        _phase = UITouchPhaseBegan;
        _touchFlags._firstTouchForView = 1;
        _touchFlags._isTap = 1;
        _timestamp = [NSDate timeIntervalSinceReferenceDate];
    }
    return self;
}

- (void)changeToPhase:(UITouchPhase)phase
{
    _phase = phase;
    _timestamp = [NSDate timeIntervalSinceReferenceDate];
}

@end

This method builds a touch in the center of the specified UIView (window coordinates must be used).

You should note that this category includes the changeToPhase: method. This phase (set to UITouchPhaseBegan in the initInView: method) refers to the begin/drag/ended state of the touch operation. We need a method to change the state because the same UITouch object must be used for touch began and touch ended events (otherwise the whole windowing system crashes).

UIEvent category

The UIEvent object is mostly handled through an existing private method (_initWithEvent:touches:). There are two difficulties with this method though:

  • We must provide it a GSEvent object (or something very close to it)
  • We must allocate the object as a UITouchesEvent on SDK 3.0 and later but as a UIEvent on earlier versions.

Here's how all that will look:

@interface UIEvent (Creation)
- (id)_initWithEvent:(GSEventProxy *)fp8 touches:(id)fp12;
@end

@implementation UIEvent (Synthesize)

- (id)initWithTouch:(UITouch *)touch
{
    CGPoint location = [touch locationInView:touch.window];
    GSEventProxy *gsEventProxy = [[GSEventProxy alloc] init];
    gsEventProxy->x1 = location.x;
    gsEventProxy->y1 = location.y;
    gsEventProxy->x2 = location.x;
    gsEventProxy->y2 = location.y;
    gsEventProxy->x3 = location.x;
    gsEventProxy->y3 = location.y;
    gsEventProxy->sizeX = 1.0;
    gsEventProxy->sizeY = 1.0;
    gsEventProxy->flags = ([touch phase] == UITouchPhaseEnded) ? 0x1010180 : 0x3010180;
    gsEventProxy->type = 3001;    
    
    //
    // On SDK versions 3.0 and greater, we need to reallocate as a
    // UITouchesEvent.
    //
    Class touchesEventClass = objc_getClass("UITouchesEvent");
    if (touchesEventClass && ![[self class] isEqual:touchesEventClass])
    {
        [self release];
        self = [touchesEventClass alloc];
    }
    
    self = [self _initWithEvent:gsEventProxy touches:[NSSet setWithObject:touch]];
    if (self != nil)
    {
    }
    return self;
}

@end

You can see that most of the setup is simply concerned with filling in the fields of the GSEventProxy object which is the pretend object that we substitute in place of the actual GSEvent object (which can't be easily allocated).

The fields and values used are determined simply by staring at real GSEvent structures in the debugger until the values for fields could be determined.

The definition of this object follows. You'll need to place it before the previous UIEvent category implemention.

@interface GSEventProxy : NSObject
{
@public
    unsigned int flags;
    unsigned int type;
    unsigned int ignored1;
    float x1;
    float y1;
    float x2;
    float y2;
    unsigned int ignored2[10];
    unsigned int ignored3[7];
    float sizeX;
    float sizeY;
    float x3;
    float y3;
    unsigned int ignored4[3];
}
@end
@implementation GSEventProxy
@end

Sending the event

There is no API to route the events to the appropriate view — so we will just invoke the methods directly on the view ourselves.

Using the above categories to create the UITouch and UIEvent objects, dispatching a touch event to a UIView looks like this:

- (void)performTouchInView:(UIView *)view
{
    UITouch *touch = [[UITouch alloc] initInView:view];
    UIEvent *eventDown = [[UIEvent alloc] initWithTouch:touch];
    
    [touch.view touchesBegan:[eventDown allTouches] withEvent:eventDown];
    
    [touch setPhase:UITouchPhaseEnded];
    UIEvent *eventUp = [[UIEvent alloc] initWithTouch:touch];
    
    [touch.view touchesEnded:[eventUp allTouches] withEvent:eventUp];
    
    [eventDown release];
    [eventUp release];
    [touch release];
}

Legality of use and risks

The approach used in this post constitutes using an undisclosed API — it is therefore illegal to submit applications to the App Store that use this approach, according to the iPhone SDK Agreement.

In terms of risks, this type of undisclosed API use has a high probability of breaking on every update to the iPhone OS — yet another reason why this code is for in-house developer use only.

If you use this code, only use it in a separate target for testing purposes only. Do not submit this code to the App Store.

Conclusion

You can download a copy of TouchSynthesis.m as part of the SelfTesting project (from my later post Automated User Interface Testing on the iPhone).

I have only tested this for performing touch events in UITableViewCells in a UINavigationController — navigating a hierarchy to verify that the hierarchy works. Of course, once you've programmatically navigated, you must also read back from the hierarchy to ensure that required features are present — but that's a post for a different time.

Working directly with the fields of a class is always a little risky. I'm sure there are UIViews that won't work well with this type of synthetic touch. Apple is also free to change the meaning of any fields at any time so this code is prone to break frequently.

Finally, remember to keep this type of testing code in a separate target so it isn't included in the application you submit to the App Store. I don't want to see your projects break or be rejected because you're trying to invoke use undisclosed APIs in your final application.

Read more...

WorldTimeConverter: Dates and timezones in Cocoa

Recently, I searched for a world time converter — one that would handle future dates as well as the current time. There are web versions but I didn't find a genuine Mac OS X application that matched my desires. How hard could it be? The answer is 1 subtraction — provided you can find the right values to subtract. Read on and I'll show you how it's done.

Update 2008-10-15: The original project contained a timing error that could cause a crash on startup. I've now fixed this bug in the project. See the comments for more details.

Introduction

There are at least a few Mac OS X programs that will show the current time around the world but I couldn't find one that specialized in source to destination timezone conversion of an abitrary time (apologies if you have released one — I didn't find it).

worldtimeconverter.png

This is a screenshot of the application running. You can download the Xcode 3.1 project for WorldTimeConverter here.

Dates and timezones in Foundation

All Cocoa programmers should be familiar with the class NSDate. It specifies a point in time and in conjunction with NSCalendar can give year, month, day, hour, minute, second and other values.

Normally, NSDate values are implicitly associated with your locale timezone (current timezone). However, an NSDate can be within any timezone — so an NSDate only has truly universal meaning if you further specify the timezone in which it applies.

A timezone in Cocoa is represented by an instance of NSTimeZone. Broadly speaking, NSTimeZone objects encapsulate an offset from Greenwich Mean Time (GMT) and hold information about potential daylight savings zones.

The truly valuable information, the data which makes world time conversion possible, comes from two class methods on NSTimeZone — the knownTimeZoneNames method and the timeZoneWithName: method. Between the two methods, you can obtain access to a nearly complete list of world time zones.

NSArray *timezoneNames = [NSTimeZone knownTimeZoneNames];
timezones = [[NSMutableArray arrayWithCapacity:[timezoneNames count]] retain];
for (NSString *name in
    [timezoneNames sortedArrayUsingSelector:@selector(compare:)])
{
    [(NSMutableArray *)timezones addObject:[NSTimeZone timeZoneWithName:name]];
}

With this array of timezones available, you can easily select a source and destination timezone. A conversion of an NSDate from the source to the destination then looks like this:

NSInteger sourceSeconds =
    [sourceTimeZone secondsFromGMTForDate:date];
NSInteger destinationSeconds =
    [destinationTimeZone secondsFromGMTForDate:date];
NSTimeInterval interval = destinationSeconds - sourceSeconds;
return
    [[[NSDate alloc]
        initWithTimeInterval:interval sinceDate:date]
    autorelease];

where date is the specified time in the source timezone and sourceTimeZone and destinationTimeZone are valid NSTimeZone objects.

This approach has one subtle failing: the offset of both timezones is evaluated at the source date. If the destination changes daylight savings time in the selected time between the source date and the destination's date, then the daylight savings time will be wrong.

NSDatePicker

The user interface for the application is mostly handled by NSDatePicker.

Performing date and time selection became far more enjoyable in Mac OS X 10.4, when Apple introduced the NSDatePicker which handles both the calendar and the clock in the above screenshot.

Adjusting the time is a little fiddly (you have to click exactly on the hands of the clock to drag them around) but a visual control that outputs an NSDate is far nicer than the previous text entry with date formatting.

The clock is disabled by default in Interface Builder. You must enable "hour and minute" or "hour, minute and second" selection to display the clock.

Organising the controller class

Ordinarily, I despise programs where all the control functionality has been unceremoniously dumped into one class. In this case, I couldn't find a good way to decouple the four controls in the window — they all read, write or affect the "date" value which is contained by the DateController.

An interesting point: I have chosen to store the selection from the two tables as an NSIndexSet, rather than the NSTimeZone that is shown in the table. This is because the NSArrayControllers report their selection in this manner. Instead of overriding the getter and setter for the selection binding to convert from the NSIndexSet to the NSTimeZone, it is simplest to just store the value NSArrayController hands over and defer conversion from index to content object.

So instead of overrides for the getter and setter of both sourceTimezoneIndex and destinationTimezoneIndex, all we need are the following lines when the selected timezones are finally required in the destinationDate method:

    NSTimeZone *sourceTimeZone =
        [timezones objectAtIndex:[sourceTimezoneIndex firstIndex]];
    NSTimeZone *destinationTimeZone =
        [timezones objectAtIndex:[destinationTimezoneIndex firstIndex]];

Conclusion

Writing a world time conversion program is simple. Really simple, in fact; I thought it would be harder than this. All of the data and controls are provided in Cocoa by default — it's really just a matter of connecting it together.

My apologies to skilled programmers for whom this post was too obvious. I promise that next week will be an obscure, convoluted mess.

Read more...

Sliding UITextFields around to avoid the keyboard

It's an iPhone post because I finally can. Here's a good way to slide your view around when editing UITextFields so that they never get trapped under the onscreen keyboard.

I'll be giving a talk at the Brisbane Cocoaheads meeting this Monday evening (Oct 6). Come along and heckle.

Hidden text fields

The iPhone's onscreen keyboard occupies the bottom 216 pixels on screen (162 in landscape mode). That's around half the screen, so if you ever have a text field you want to edit in the bottom half of the screen, it needs to move or it will get covered.

You can just animate the whole window upwards by the height of the keyboard when editing a text field in the bottom half but this doesn't work well for text fields in the middle (they can get moved too far up).

Instead, I'm going to show you a method which divides the window as follows:

slidingsections.png

Everything in the top section will stay still when edited. Everything in the middle section will animate upwards by a fraction of the keyboard's height (proportional to the field's height within the middle section). Everything in the bottom section will animate upwards by the keyboard's full height.

Implementing the delegate methods

All of the methods shown here should go into a view controller probably the "main" view controller for the current screen (i.e. the visibleViewController of the current UINavigationController, your RootViewController or other top-level view controller).

You will need to make this view controller the delegate (in Interface Builder) for every UITextField you want to animate.

Your view controller will need the following instance variable:

CGFloat animatedDistance;

The following constants should also be declared somewhere (likely the top of the view controller's implementation file):

static const CGFloat KEYBOARD_ANIMATION_DURATION = 0.3;
static const CGFloat MINIMUM_SCROLL_FRACTION = 0.2;
static const CGFloat MAXIMUM_SCROLL_FRACTION = 0.8;
static const CGFloat PORTRAIT_KEYBOARD_HEIGHT = 216;
static const CGFloat LANDSCAPE_KEYBOARD_HEIGHT = 162;
Animate upwards when the text field is selected

Get the rects of the text field being edited and the view that we're going to scroll. We convert everything to window coordinates, since they're not necessarily in the same coordinate space.

- (void)textFieldDidBeginEditing:(UITextField *)textField
{
    CGRect textFieldRect =
        [self.view.window convertRect:textField.bounds fromView:textField];
    CGRect viewRect =
        [self.view.window convertRect:self.view.bounds fromView:self.view];

So now we have the bounds, we need to calculate the fraction between the top and bottom of the middle section for the text field's midline:

    CGFloat midline = textFieldRect.origin.y + 0.5 * textFieldRect.size.height;
    CGFloat numerator =
        midline - viewRect.origin.y
            - MINIMUM_SCROLL_FRACTION * viewRect.size.height;
    CGFloat denominator =
        (MAXIMUM_SCROLL_FRACTION - MINIMUM_SCROLL_FRACTION)
            * viewRect.size.height;
    CGFloat heightFraction = numerator / denominator;

Clamp this fraction so that the top section is all "0.0" and the bottom section is all "1.0".

    if (heightFraction < 0.0)
    {
        heightFraction = 0.0;
    }
    else if (heightFraction > 1.0)
    {
        heightFraction = 1.0;
    }

Now take this fraction and convert it into an amount to scroll by multiplying by the keyboard height for the current screen orientation. Notice the calls to floor so that we only scroll by whole pixel amounts.

    UIInterfaceOrientation orientation =
        [[UIApplication sharedApplication] statusBarOrientation];
    if (orientation == UIInterfaceOrientationPortrait ||
        orientation == UIInterfaceOrientationPortraitUpsideDown)
    {
        animatedDistance = floor(PORTRAIT_KEYBOARD_HEIGHT * heightFraction);
    }
    else
    {
        animatedDistance = floor(LANDSCAPE_KEYBOARD_HEIGHT * heightFraction);
    }

Finally, apply the animation. Note the use of setAnimationBeginsFromCurrentState: — this will allow a smooth transition to new text field if the user taps on another.

    CGRect viewFrame = self.view.frame;
    viewFrame.origin.y -= animatedDistance;
    
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [UIView setAnimationDuration:KEYBOARD_ANIMATION_DURATION];
    
    [self.view setFrame:viewFrame];
    
    [UIView commitAnimations];
}
Animate back again

The return animation is far simpler since we've saved the amount to animate.

- (void)textFieldDidEndEditing:(UITextField *)textField
{
    CGRect viewFrame = self.view.frame;
    viewFrame.origin.y += animatedDistance;
    
    [UIView beginAnimations:nil context:NULL];
    [UIView setAnimationBeginsFromCurrentState:YES];
    [UIView setAnimationDuration:KEYBOARD_ANIMATION_DURATION];
    
    [self.view setFrame:viewFrame];
    
    [UIView commitAnimations];
}
Since we're writing the delegate methods...

This next method has nothing to do with animation but since we're writing the delegate methods for a UITextField, this is essential. It dismisses the keyboard when the return/done button is pressed.

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    [textField resignFirstResponder];
    return YES;
}

Result

For a window that looks like this:

textfieldwindow.png

Editing text fields in the top, middle and bottom sections will look like the following:

slidingpositions.png

Notice in particular how the middle section remains in the middle of the visible area after the keyboard appears. This is the primary benefit of the presented approach.

Read more...