2

I would like to create a zoomable timeline in my iOS application for a kind of a todo-list. Zooming in would display days and hours and zooming out would trigger the folding out of days or zooming out to months. There would be a scrolling function.

As an example, I would want it to work like this: http://almende.github.com/chap-links-library/js/timeline/doc/

What kind of basic view would be an appropriate starting point for this, keeping in mind that the memory needed should be as low as possible? Would a UITableView, UIScrollView, or something else work for this?

laaposto
  • 11,835
  • 15
  • 54
  • 71
Âsgaroth
  • 37
  • 1
  • 4

2 Answers2

8

The UICollectionView / UITableView will not work because the cells are almost always the same width/height. Most importantly the cells always have the same spacing in-between each cell. Because of this it is able to easily calculate what the index range is, and query the dataSource for the cell's it needs based on index.

A timeline view on the other hand is much different than these controls. The spacing between cells is different, with the cells sometimes overlapping with each other. If you had a data source sorted by position, the control would still have to guess where to start looking for the range. So finding the correct index range is going to be more expensive - you just have to find the right algoritm to determine this in a shorter amount of time.

You're going to have to build your own control by subclassing a UIScrollView. You shouldn't have to mess with drawRect at all. An important concept, which is used by UITableView and UICollectionView, is dequeue'ing cells. The iOS 5 version of Apple's PhotoScroller demonstrates this concept with paging (the iOS 6 version replaces the custom paging with UIPageViewController). You'll have to download the old documentation to get the old sample code.

I'm currently building a timeline view, which I will open source at some point. It's somewhat based on the UITableView and works in horizontal or vertical direction. It dequeue's cells just like the UITableView. I'm not focusing on labels or scaling, but the concept of having inconsistent spacing in-between cells. To give you a head start, here are my dataSource methods I settled on:

- (NSInteger)numberOfCellsInTimelineView:(TimelineView *)timelineView;
- (CGRect)timelineView:(TimelineView *)timelineView cellFrameForIndex:(NSInteger)index;
- (TimelineViewCell *)timelineView:(TimelineView *)timelineView cellForIndex:(NSInteger)index;

Two of these calls are identical to what UITableView has, but it has a new call called cellFrameForIndex. What's significant about this call is that the TimelineView can guess an index and lookup the frame of the cell and see where it fits in the visible bounds. If it guesses a cell inside the visible bounds, it can simply iterate forward and backward until it finds the edges.

Currently the algoritm I'm using takes round(count * (CGRectGetMidX(timelineView.bounds) / timelineView.contentSize.width)) (for the horizontal direction). Basically what this does is takes the mid-point of the visible bounds of the UIScrollView and gets the percentage of what has been scrolled. Then it multiplies that by the number of cells. This works fairly well. When testing a random data-set with 100,000 records at random spacing the calls to cellFrameForIndex ranged from 8 to 150. I'm able to pull 52-60 FPS with this. I'm still working on it, and trying to find a better/quicker way to determine the index range. I'm trying to keep it down to visible cell count + 10 (max) iterations.

If you have time to wait, I'll update my answer to include a link to my GitHub project when I'm done. This could be a few days, to a week. I may add scaling. But you'll have to fork it and add labels and anything else you want.

Edit:

Here is my Github project: https://github.com/lukescott/TimelineView

Community
  • 1
  • 1
Luke
  • 13,678
  • 7
  • 45
  • 79
  • 1
    `UICollectionView` makes no assumptions about the size or positioning of its cells. With a custom `UICollectionViewLayout`, you can implement very flexible layouts that don't even have to be a grid at all... – omz Mar 06 '13 at 19:43
  • Spacing still has to be consistent and known without looking at the data source, which is the key difference. – Luke Mar 06 '13 at 19:54
  • The `UICollectionViewDataSource` only uses indexPath. The `UICollectionView` assumes a *sequence* of data without gaps. In a timeline you can have gaps between points (days, months, years, pixels). Even if you could expand the `UICollectionView` to work for a timeline, you still have to ask the data source for the position and figure out what the visible index range is. The concept is the same, regardless if you create something from scratch or expand the `UICollectionView` - you still need more than the count to determine the visible range for a timeline. – Luke Mar 06 '13 at 22:21
  • thanks for the answers. I got to an basic approach as Sulthan has suggestet. I really thank you anyway it was really helpful! – Âsgaroth Mar 08 '13 at 14:42
  • Github project has been updated to include highlight, selection, and drag and drop support. I'm going to make the scroll speed variable next. I may add a legend view later on. Works both vertical and horizontal. – Luke Mar 11 '13 at 23:50
  • 1
    Actually a UICollectionView is possible. What you need is a mapping between the index paths and the coordinate. The custom UICollectionViewLayout can have a custom data source which can ask for this mapping. I'm currently working on an implementation of this. Since I want to animate between different layouts it has to be a UICollectionView for me. I have the basics working and might put it open source once it's in a further stage. – lammert Jul 30 '13 at 18:16
  • @lammert Sure, you could use `layoutAttributesForElementsInRect`. You'd have to add custom delegate calls for discovery, much like TimelineView does. You'd lack drag and drop capability though. I also did DraggableCollectionView, but that's based on index path. – Luke Jul 30 '13 at 18:23
0

UITableView is definitely not suitable. UIScrollView might be better but it is not very well suited for dynamic or very long content.

I believe the easist approach would be to do it everything by yourself - implement it by a UIView subclass with a drawRect. Of course, you should use UIPanRecognizer in the same manner as UIScrollView uses it.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • 1
    I just would like to point out that a UIScrollView is more than suitable for very long content - if you tile and recycle the cells, as the UITableView and UICollectionView does. This means that anything you can't see is removed and recycled. Subclassing `UIView` and overriding `drawRect` is neither easy or trivial, especially if you expect to interact with the elements, or animate movement. – Luke Mar 08 '13 at 17:14
  • @Luke What you advice to do with `UIScrollView` is basically the same as what would you have to do with drawRect. Trust me, there is nothing difficult with the animations since everything what scrollview does is changing horizontal & vertical offset based on data from pan recognizer. Overriding `drawRect` is not complicated, definitely not more complicated then reusing views with a `UIScrollView`. Also, it is more memory-friendly. I just finished doing my own scrollable component and there was no problem with interaction or animations. – Sulthan Mar 08 '13 at 17:25
  • @Sulthan: Sure you can do it, it's just not very reusable as you're drawing something very specific. With a reusable cell you can subclass and put anything inside with minimal effort. Dequeueing cells is also memory efficient as well. You would only benefit from drawing if you had hundreds-thousands of elements on the screen at the same time, but you would still have the query the datasource, which is a bottleneck in itself. Animating a `UIView` is also easier as it's as simple as setting the frame one time. – Luke Mar 08 '13 at 17:44
  • @Luke Well, of course. I know how to use `UITableView` or `UIScrollView`. However, if you have a problem like this, when not only "cells" have different width (not height - we are scrolling horizontally) but they even overlap each other, even talking about "cells" is wrong. Animating `UIView` is not less complicated than animating everything else. When I last used this, I actually subclasses a layer and the animation was done automatically by changing property `offset`. Specific problems need specific solutions, you can't make everything generic and you shouldn't if you don't plan to reuse it. – Sulthan Mar 09 '13 at 12:44
  • I Finally got back to use UIScrollview and it now works nearly perfect. There is a example by apple http://developer.apple.com/library/ios/#samplecode/StreetScroller/Listings/StreetScroller_InfiniteScrollView_m.html with some modification its possible to create a really suitable control for a timelineapplication – Âsgaroth Mar 13 '13 at 09:15