Erik Erik - 8 days ago 9
Objective-C Question

Best approach for a UICollectionView to look like the iOS calendar app

I'm building an app where I really need to display a list like the one seen in the iOS calendar app. I need to create a collectionView where I can have cells that expand enough to cover their respective hours, like so:

enter image description here

I've tried various things, including this project at Github which I didn't understand how to use in another project

As well as this project I quickly made with a UITableViewController while exploring different methods:

enter image description here

But I'm not really getting where I want to. I need to have the design seen in the first picture and was wondering if anyone could point me in the right direction to achieve that?

Thank you so much for help!!

Update:

Shouldn't the actual cell frame represent the start of an hour, 10:00 in this example? (Rather than the custom-made separator)

enter image description here

Update

Why does the hierarchy look like this:

enter image description here

This kind of positioning appears to be "blocking" the touch of the cells and the

didSelectItemAtIndexPath
method doesn't get called. This applies to the cells that appear further back than the other ones.

enter image description here

Answer

You should indeed use a UICollectionView along with a custom layout. Just provide supplementary views to build the underlying daily schedule (one view per hour), and then use the cells to lay down your events.

When subclassing UICollectionViewLayout, you need to implement a few methods:

  • collectionViewContentSize should return a height equal to the number of hours (24) multiplied by the height of a supplementary view representing an hour.
  • prepareLayout does almost everything. In that method, you calculate every layoutAttributes you will need to use. By getting the time of the event and its duration, you are able to compute the frame of every event. The supplementary view frames (every single "hour" block) are pretty straightforward too, since their height is fixed (origin.y = fixed height multiplied by the hour).
  • layoutAttributesForElementsInRect: simply iterates through your previously prepared layoutAttributes and returns all whose frame intersect the provided frame.
  • layoutAttributesForItemAtIndexPath: finds and returns the cell's layoutAttributes that match the provided indexPath.

  • layoutAttributesForSupplementaryViewOfKind:atIndexPath finds and returns the supplementary view's layoutAttributes that math the provided indexPath.

Next, you give an instance of that layout to a collection view:

self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:myCustomLayout]

Then, you only need to provide the required views for the supplementary views (a.k.a the hour blocks) and the cells, via the UICollectionView's delegate & dataSource. Feel free to create a custom delegate in your custom layout if you need the UIViewController to provide more info that you have with the standard delegate/dataSource!

Note: Since the line that indicate the start of an hour is a few pixels below the cell's top border, you need to shift every time of event by the same number of pixels. Say you have 6 pixels above every line and every hour block has a height of 60 pixels, then if an even start at 2AM, you will set its origin.y = 2 * 60 + 6 (2 hours * 60 pixels per hour + 6 pixels padding). You will also need to adjust you last cell block to be 6 pixels taller since it won't have another cell below.

I recommend you to read the official documentation about creating custom layouts.

To help you: I made a quick sample project since making your own layout can sometime be troublesome. Go check the CalendarViewLayout class, I added a few comments to explain how I dealt with the padding. Here is was it looks like:

Sample