Sunday, February 26, 2012

Portability between iOS and OS X

Last week Apple released the developer preview of OS X Mountain Lion. I don't have it and I don't run it, but from reviews I gather that the ‘iOSification’ of OS X is continuing. OS X gets more and more features from iOS and eventually, the two will supposedly merge. Applications from the desktop cross over to mobile devices and vice versa. This may seem perfectly logical, but from a programmer's perspective this is remarkable, because iOS and OS X are very different.

iOS is a phone OS, OS X is for desktop computers and its portable brother, the laptop computer. The tablet is like an oversized phone, it's not a laptop without keyboard. This is the major difference between todays tablets and yesterdays tablet PCs. The tablet PC failed because it had a desktop interface on a tablet device. Why does this make such a big difference? The answer is that the tablet has no mouse. The mouse is a perfect pointing device. It has much greater precision than your fingertips. With a mouse, you can point at the exact pixel that you want to point at. Using a mouse you can also right click, click and drag, and click while pressing a modifier key. Try that on a tablet. Sure, you can compensate somewhat by using gestures but the lack of precision when using your fingers still gets in the way. This becomes painfully obvious when using Photoshop with a trackpad. It just isn't convenient.
So, we might think up a new kind of interface for Photoshop so that it will play nicely with a touch interface. Fair enough. The supposed merge of iOS and OS X still is remarkable in another way.

Under the hood, iOS and OS X both run the Darwin kernel. Darwin for mobile devices probably has a number of tweaks (like virtual memory management, or lack thereof) but it apparently is largely the same software. Apps for both platforms are programmed in Objective-C. You can use Xcode and Interface Builder to develop for both platforms. But while the platforms appear similar in these respects, they simply are not the same. For the user interface OS X has Cocoa and AppKit, but iOS has Cocoa Touch and UIKit. AppKit has NSView, NSButton and NSTextField, while UIKit has UIView, UIButton and UITextField. Here it clearly shows that iOS is very different from OS X. Porting an app means changing everything in the user interface. Applications should be designed around the model-view-controller paradigm so that you might reuse a lot of code, but it's not going to be easy — there's certainly more to it than just changing ‘NS’ to ‘UI’.

Okay, so let's not use the Cocoa GUI elements and stick to OpenGL. OS X has full blown OpenGL support and iOS supports OpenGL ES. For portability reasons you might stick to using plain OpenGL in an ‘ES’ish way, using only a common subset of functions. But there is another problem. OS X has the Cocoa NSOpenGLView class, while iOS uses a CAEAGLLayer. The first is a view and the second is a layer. In OS X it should be possible to use a Core Animation Layer with OpenGL too. It's however common practice to use NSOpenGLView because it integrates with the desktop windowing system. A consequence is that the code will be very different, mostly in respect to keyboard and mouse input.

Coming back to the mouse; the trackpad on OS X is handled in a very different way than the touch screens in iOS. The trackpad is actually a mouse (much like the surface of a magic mouse) and it is not the same as a touch screen. When you point your finger at a touch screen, the software gets the coordinates of the touch event. When you point at a trackpad, the software sees the mouse pointer position, which is something entirely different. When you swipe a touch screen, you generate a swipe event. When you swipe a trackpad, it actually produces a mouse scroll wheel event. This makes sense because the trackpad is a replacement for the mouse for laptop computers in the first place.

Even when you overcome this issues, you will find that the coordinate systems between the two platforms are different: OS X puts the origin of a view in the bottom left corner, while on iOS it's in the top left corner. And there is more. These are just a few differences between the two; the complete list is much longer.

It's quite an undertaking to port apps between iOS and OS X. iOS and OS X were never meant to be merged into one. They are two separate platforms for two separate ways of computing. But then the tablet came and things changed. People expect it to be a modern reinvention of the laptop computer, and in a way, it is. Trying to put a desktop experience on a mobile device didn't work; having a mobile device's interface on the desktop isn't pleasing either. Porting involves major application interface design changes. Apple acknowledges this and has some good documentation on the subject; see their pages on Migrating from Cocoa Touch if you want to know more.

Saturday, February 11, 2012

The TreeView in Cocoa : an NSOutlineView tutorial

The MacOS X Finder has a so called ‘List’ viewing mode in which folders have a square triangle to their left side. The triangle can be clicked to expand or collapse the folder, allowing the user to show or hide the contents of the folder. Such an hierarchical tree of items is usually called a TreeView widget. In Cocoa, it is called NSOutlineView.

The NSOutlineView is one of the more difficult Cocoa classes to use. Apple's documentation about it isn't very clear, especially not to beginners, and secondly, it requires a relatively lot of work on your part to get it going. What's also confusing is that there's a NSTreeController that is hard to grasp. In fact, we are not going to bother with NSTreeController. As you will see, it is possible to implement a perfectly good tree view without having to use NSTreeController at all.

The NSOutlineView is a very generic class that handles any kind of hierarchically organized collection of items. So even if you just want to add a simple directory browser to your program, you will have to do the lower level stuff of getting the directory contents and handing them on a plate to the NSOutlineView so that it can display them. On the other hand, if you have a large collection like a zoological family tree of animals, you can also use NSOutlineView to display it and let the end user access this data in a convenient way.

The idea behind NSOutlineView is that it gets its data from a DataSource. Rather than pure data, the DataSource is a class that implements a protocol for providing data items. As said, the DataSource hands the items on a plate to the NSOutlineView.
The data items themselves should be instances of a generic class that is derived from NSObject. So for a directory browser, the data item must have a property for representing the filename. It must also have a pointer to its parent directory, and if it is a directory itself, it must have an array with pointers to data items representing the contents of this directory. As you may suspect by now, implementing the class for the data items is actually harder than implementing the DataSource.

Before starting off, make a new class derived from NSViewController named DirViewController (or so) that has only one property: a pointer to an NSOutlineView. You can bind an NSOutlineView control in IB to this property using Ctrl-dragging. And you can instantiate your new view controller class in IB by dragging an NSViewController to the object area and changing its class name to DirViewController.
We will also bind a DataSource to the NSOutlineView. For this purpose we will create a custom class named DirTreeDataSource. It implements the NSOutlineViewDataSource protocol:
@interface DirTreeDataSource : NSObject <NSOutlineViewDataSource>

From this protocol we need to implement actually only four tiny methods:
- (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item;
- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item;
- (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(id)item;
- (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item;
Note that you won't call these methods. They will be called when the user clicks in the NSOutlineView control. These methods then tell the NSOutlineView what it can do with this item. The parameter item is of type id, and you can safely typecast it to a pointer of your custom item class. The parameter item may be nil, in which case you should treat it as the root item of the tree.

numberOfChildrenOfItem: must return the number of subdirectories, or zero if item is a file rather than a directory.
isItemExpandable: must return YES if item is a subdirectory, or NO if it is a file.
child:ofItem: must return the item that is at the given index. So for a directory item that holds a number of files in an array, return the array item at the index. Which comes down to return [[item array] objectAtIndex:index];
objectValueForTableColumn:byItem: must return the value of the item. In other words, it returns the filename. I suppose it must return something that the outlineview's cell can draw.

Our custom item class will be called DirTreeItem. Its superclass should be NSObject. As properties, it needs to have a name, a pointer to its parent item, and a mutable array of subitems.
@interface DirTreeItem : NSObject

@property (retain) DirTreeItem *parent;
@property (retain) NSString *name;
@property (retain) NSMutableArray *subdirs;

Of course, DirTreeItem needs some methods. The hardest part is to get the directory contents loaded into the subdirs array. You can do this by using [NSFileManager contentsOfDirectoryAtURL:]. This requires a file:// URL that you can create with [NSURL fileURLAtPath:]. This requires the full path of the current item, which you can get by traversing the item's parent pointers all the way up to the root. Mind not to put filenames into the subdirs array; it must hold DirTreeItems (!) so allocate a new DirTreeItem for each directory entry and add it to subdirs.
Loading the directory contents should be triggered by the data source's numberOfChildrenOfItem: and child:OfItem: methods.

After writing the code for DirTreeItem and DirTreeDataSource, head back to IB. Instantiate the data source by dragging an NSObject to the object area and changing its class name to DirTreeDataSource. Now select the NSOutlineView control and bind its data source to our instantiated DirTreeDataSource object by Ctrl-dragging. We now have a tree view control that is fed by a data source. Our data source will load data items dynamically (as it gets the directory contents) when the user browses the tree.

I have to say, it takes quite some work to get an NSOutlineView going. If you want to add a small image to the items in the outline view, you are going to have to subclass NSCell and do your own drawRect: method. The NSOutlineViewDataSource protocol also supports editing items, drag and drop, and sorting.
It is also possible to bind the outlineview's data source to an NSTreeController. If you go this way, the data items must be key-value-coding compliant. From what I gather, NSTreeController is convenient if you use a Core Data model. Otherwise, you are better off using a data source implementation as shown above.

Although I told you how it works, I did not spell out all the code. If you need more help with this, please study Apple's example.