I have been working recently on a Xamarin.iOS application for managing events. One of the application’s interesting and challenging features is the way it displays multiple lists of events with the help of some relatively advanced, but intuitive UI. This is what the homepage of the app looks like:
Screenshots number two and three represent what the user sees after swiping up on the list of events. At first glance this does not look very complicated, but the Devil is in the detail. The whole screen consists of three parts, starting from the top:
- A header displaying information about the next event. The white circle represents an image.
- A menu with buttons used to switch between different content categories (in this case they are labelled “UPCOMING” and “PREVIOUS”).
- An area displaying a list of events belonging to the selected category.
The behaviour of the list of events can be described as follows:
- Swiping up on the event list pushes the header view up until it’s completely invisible, as seen in the third screenshot.
- At that point, if the event list can’t display all its elements, the content scrolls further with the “UPCOMING/PREVIOUS” bar staying at the top of the screen.
- Swiping left or right lets the user switch between the lists of upcoming or previous events (this can be done at all times, no matter if the header view is visible or not).
How do we satisfy all of the above-mentioned requirements? My first thought was to simply use one table view, but that is not going to work. The reason is the need for horizontal scrolling between multiple lists of events. If we were to use a single table view, we would have to put them all in a table view cell, which is not what it was designed for. On top of that we would encounter problems with handling touches, due to the fact that we’re embedding a scroll view inside another scroll view. This is not going to work if both of them support scrolling along the same axis.
A better approach would be to use a scroll view containing multiple table views, which represent tabs for upcoming and previous events. That sounds good, but we need the user to be able to switch from one list to another via a menu. It looks like it would make sense to create a custom component containing both the menu view and the scroll view with multiple table views. Let’s make that component a subclass of UIViewController, and call it SlideMenuController. The following diagram summarizes our solution:
Both the header view and the SlideMenuController’s view are subviews of the HomePageViewController’s view.
The only outstanding problem is making sure that the header view moves up as we swipe up on the event list. This is the part that was tricky to get right. What we need to do is resize SlideMenuController until it fills the whole screen. If there is still more content to be shown, we have to make sure that the currently visible table view gets a chance to scroll its content. The header view should always stay above the menu view and be eventually pushed off the screen. Our solution should have the same smoothness of scrolling as a standard UITableView, even though we transition from resizing to regular scrolling.
You can find my implementation of SlideMenuController here.
Invisible view hierarchy
We can achieve the desired result by placing invisible scroll views on top of the view controller’s view. The view hierarchy of the invisible scroll views should correspond to the hierarchy managed by SlideMenuController. This means that if the user can see the “UPCOMING” and “PREVIOUS” tabs, than we need two child scroll views contained in a parent scroll view. The content size of child scroll view at index i should be equal to content size of the table view at index i plus the size of the header view displaying the next event. Our resulting view hierarchy will look like this:
As you can see in the diagram above, we’re using a view named ProxyView. Its role is two-fold: it serves as a container for the scroll views that mimic the structure of the visible content and it notifies its delegate of the interactions the user is performing, such as swiping and tapping. The interface a delegate object should implement is as follows:
ScrollViewDidScroll informs us that a child scroll view at a given index (for example the one representing upcoming events) was swiped vertically. ParentScrollViewDidScroll tells the delegate that the user swiped horizontally, which is used to switch between child scroll views. The last method is for handling interactions with other UI elements visible on the screen, like buttons and table view cells.
The class implementing IProxyViewDelegate is typically the view controller whose view contains an instance of ProxyView. The view controller makes sure that the interactions performed on the invisible hierarchy of scroll views is reflected on its visible counterpart in the form of SlideMenuController.
If you would like to take a look at how ProxyView is implemented, you can find the code here.
The challenge we were dealing with was to implement a view controller, which will allow us to display multiple lists of items. The user should be able to switch between the lists by swiping horizontally. Each list can also scroll vertically, which causes it to fill all the available space on the screen and push the header view out of sight.
We achieved our goal by implementing a custom subclass of UIViewController, which manages a scroll view containing multiple instances of UITableView. To make sure that the user’s interaction with the new custom controller is smooth, we implemented a proxy view. Its purpose is to translate swipe gestures into resizing of SlideMenuController and scrolling of the currently visible table view.