My Cocoa application collects events (instances of NSManagedObject) that need to be displayed in a timeline. My initial approach was to use an existing Javascript based widget (I tried using Simile Timeline and Timeglider) and display the timeline using a WebView control. This works in principle, however unfortunately both these widgets do not handle BC dates very well, which is an important requirement for my app.
The events in my app have date ranges from 500.000BC up to recent dates. Event dates are only expressed with a year. Their day, month and time attributes are irrelevant.
After discarding the Javascript approach, I remain with the option to display the timeline using a custom Cocoa control. As I found none suitable, I will have to develop that myself.
This would be my first custom Cocoa control and after thinking about this for a while I've come up with the following rough design:
I need a custom control to render the actual time line. This control is probably based on an NSView. This control should calculate its size based on the number of tick marks on the time line multiplied by the width (pixels) between each mark. For example the timeline is made up of centuries, each century 100 pixels wide. A time line of events between 10.000BC and 5.000BC would then be 5000 pixels wide (10000 - 5000 = 5000 years, equals 50 centuries).
I need a ScrollView to wrap the timeline to allow it to support scrolling behaviour. There's only need for scrolling horizontally.
I need something to represent an actual event. I'm thinking of using existing controls for this, probably the standard round button and a label wrapped together as a single control.
I need a custom control to render a tick mark on the time line.
Taking this as the basic design for my time line component in Cocoa, would that work or am I completely missing the point?
The basic approach sounds fine.
Apple has a good example of creating a custom NSView called "TreeView". It's a good sample to understand.
“TreeView” presents an example of a creating an entirely new custom view from scratch (by directly subclassing NSView). Its implementation illustrates many of the considerations involved in creating a custom view, including issues of content layout, drawing, handling user interaction, and providing Accessibility support.
Another thing you may want to consider is zoom in and out. If you have a long timeline, I imagine you may want to zoom out and then zoom in on a cluster of activity. If you have one event in 10k BC and then a cluster of events much later, the user could scroll through tons of empty space trying to find events. Another approach would be to have a mini timeline above that's fit to/static size which is sort of like an index with lines showing activity points - then clicking on that would auto scroll to that point. That may be a nice to have depending on your data.
Some thoughts:
For something this custom drawn, you'll want to override drawRect to draw your lines and layout your subControls.
If you're drawing your background or any part of the views, ensure you enable layer backed views:
[self setWantsLayer:YES];
If you can, as you noted, try leverage existing controls that you add and layout. In my custom controls, I maintained data structures independent of the views/controls that represented the state of all the objects. The in drawRect, I detected the view changing and I called my layoutSubviews function. My layoutSubViews function would do the math from my data structures and create or move the frame of existing controls. That worked well for resize and zooming. If you zoom, your labels ad markers will need to react well to being zoomed really small - perhaps text drops out at some point etc...
if ([self dataSource] &&
!NSEqualRects(_prevRect, [self bounds]))
{
// layoutViews is my custom function that worked over the data structures
// and moved the frame
[self layoutViews];
}
_prevRect = [self bounds];
Hope that helps.