How can you create a zoomable timeline in iOS? - ios

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?

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

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.

Related

Horizontally Scrolling UICollectionView with Vertically Scrolling Sections

I'm trying to implement the following:
At the top is a segment control that, based on the user's selection (either tapping or scrolling), moves to the relevant CollectionView section.
I've been playing around for a couple of days with CollectionViews and maybe I've just confused myself with LayoutFlows and CompositionalLayouts or is that over complicating it?
I have a diffable datasource that is made up of [Sections, CellItems] but I'm struggling with how to structure it.
What is the recommended programmatic (rather than using interface builder) to do the above? I was just going to originally do 5 horizontal cells, each with a tableView but based on some other comments I've seen from Apple on the Apple forums and the move away from TableView to CollectionView at this year's WWDC, I thought I should be trying to do this properly.
Any direction would be appreciated. All my research seems to point to using orthogonalScrollingBehavior but that layouts out the sections vertically with each section scrolling horizontally.
So my question is should I be trying to do what I'm doing using a CompositionalLayout?
Well first off your segmented control idea makes this sound like you could just use a page control for the horizontal aspect and remove a lot of the collection view complexity. Something to consider anyway.
As for the data source, I think all you need is an array of arrays. Eg. [[CellItems]]. So CellItems[0] would be the first column. Then CellItems[0][0] would be the first cell. CellItems[1][2]... 2nd column 3rd cell. I do apologize as your current solution may be related to diffable datasources and I haven't had time yet to dig into those.
Start with the horizontal axis first and ignore the vertical axis. Get that working as needed. Once you have that up and running... implement another collection view into the first one's cells.
Sorry if I misunderstood the question if it was more about HOW to implement a collection view.

UICollectionView sticky supplementary view

Assuming I'm on a chat scenario, I want user images to move along with scroll until the other user's messages appear. Some visual example:
I'm using a subclass of UICollectionViewFlowLayout to display the messages and provide that initial left offset for the messages (the sectionInset property is not working on iOS 9 for some reason) and I assumed handling a custom supplementary view to do what's shown above should not be that hard, but apparently it is :)
Some info:
I'm not using standard header/footers for flowLayout since it add a space between sections that I don't want
Found this link which is pretty interesting from performance perspective but the example is somehow incomplete (great post though)
I'm trying to avoid shouldInvalidateLayoutForBoundsChange: to return true and recreate attributes every time since chat cells can be complex in terms of size calculation.
I believe this is not a weird scenario, have anyone accomplish something like this successfully? If so, I would appreciate some pointers.
Thank you in advance.
you can use sections for each cell groups. When using sections they have to be a clear background and be customized. So message cells can be scrolled below sections and circle images on section will be on top of its cells.
As a second way, you can use circle images apart from all collection items. For this, you have to create circle images that how many required. While scrolling up/down, they have to be moved as they will be anchored on top of message cells group.
I hope it helps for you :)

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:
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:
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)
Update
Why does the hierarchy look like this:
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.
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:
I don't think this is so hard to do.
You just need a different perspective, than what you tried.
Make all the CollectionView cells the same size. They represent 1h time, so they should have equal size.
Then have another view in each cell. This is the one that show that there is an event. This view you will give a color, and place it in the cell according to the time when the event start or finish.
So in the cellForRowAtIndexPath, you would check your events and if this cell have any event then you are going to show this event view, color it, and position it. If you make the cells height 60, it gets super easy to position the view. For example if the event start at 1:20, at the cell for hour 1, you will size the event view (0, 20, 320, 40).
As per discussion in comments on the question, using the Custom Collection View Layout library linked in the question is suitable for the mentioned problem which can be easily integrated using CocoaPods.
UICollectionView is highly customizable using custom UICollectionViewLayout. See the official apple documentation for creating custom layouts.
To be frank, Apple's documentation is too overwhelming for most of us. So, you can look up (google) custom UICollectionViewLayout tutorial, go through any one you find the easiest and go on to use the library to your specific needs.

Ultravisual iPhone app like UIView or UITableView scroll

Can someone please give me a hint on how to recreate the scrolling effect used in the UltraVisual iPhone app? Here's a gif to illustrate the effect:
The first "cell" is full height while the other displayed cells are regular sized. While the user scrolls up, the first cell slowly animates to the regular height, while the next one slowly gets bigger. Do they use an UITableView? Or an UIScrollView? I have no idea how it's made...
Ha, you made my day! I actually wrote that view :)
This is actually very straightforward. This view uses UICollectionView with a custom UICollectionViewLayout.
The general principle is this. I make up a 'drag interval' – that is the required distance to drag between each cell. This value is arbitrary but affects how much the user has to drag to switch cells. The total height of the collection view is the 'drag interval' * the number of items in the view. Then I set the layout to automatically paginate to the nearest drag interval (which gives it the snapping behavior). This is very similar to how coverflow works. From this you can calculate the index of the 'top cell' by dividing the contentOffset.y by the height.
With the 'top cell' index you can generate the frames for each cell pretty easily. The top cell's frame is { 0, contentOffset.y, 320, 176 }, and from there you can calculate the next cells frame and so forth.
Then the last trick is calculating the interpolation of the page index. This is basically the decimal part of the current cell index. This will give a number between 0 and 1 that can be used to calculate the interpolation between the top frame and the frame below.
Every 'prepareLayout' calculates the frames of the cells on screen, and then in layoutAttributesForElementsInRect:, generate all the layoutAttributes based on the generated frames.
Using this trick you can create all sorts of complicated layouts. UICollectionView can be a powerful beast, but definitely takes a bit to wrap your head around it.
It's very cool! We made a fairly simple to use control like this that can be found here:
https://github.com/RobotsAndPencils/RPSlidingMenu

IOS Complex view

(please forgive my english)
I'm new at IOS developing but I've a good skill on other programming languages (mainly c#).
I'm trying to develop a test app with a main window.
Inside that window there is a scrollview with fixed size. inside that scrollviewer there is a view containing some stacked couples of one TextField and one Label: I can't know - ad design time - how many "rows" of them I have to put into.
My problems is:
I can put on the view the first couple (a textfield and, next, a label). Label size can be one or several rows high ,so I've to set multiline and sizeToFit
How can I put the second couple (and so on) without overlap the existing labels..? I can't use a table because between rows there are other labels (title of section).
Do I have to calculate the height of each label and programmatically calculate where to draw the next? Is there a more efficient way?
I don't need code, please just some hints or some keyword to googling on.
Many thanks.
I don't see why you can't use a UITableView, you can set up cells with all the controls you need in it. Either way, using a UITableview won't make the job any easier (just more memory efficient if you have many rows).
The only way that I see is, as you said, to calculate where the next 'row' should be placed (depending on the height of your previous rows). You'll also need to calculate the entire height, in order to set it to the contentSize of the UIScrollView.
Instead of UILabel you can also use a non editable UITextView, since it'll be easier to get its size (after you set the text, you can set the size of a UITextView to be equal to its contentSize)

Resources