"Use standard control" or how we stole a calendar from Apple

In this article, I would like to introduce readers to the fairly common task of creating a calendar, which was set by our team as part of one project.

The desire to share our experience in overcoming the difficulties that we have to face while implementing this kind of application arose mainly because we did not find a ready-made solution that suits us in terms of performance.

For this reason, we had to spend some time researching and comparing technologies, and we are ready to share our experience. In particular, in the article we would like to share a solution to a set of tasks related to quick rendering of cells and smooth animation, asynchronous loading of events for the calendar from the database.

I ask all interested under cat.

The first thing we decided on is the appearance of the calendar. It seemed to us that it would be very cool to make the calendar as similar as possible to Apple’s solution. As a result, a standard calendar application was unanimously chosen as a prototype, which became available with the release of iOS 7.0. The standard calendar in iOS 7.0 consists of three views: year, month, and week.

The representation of the year in the standard calendar can be seen below.

For the weekly view, we did not see any problems in terms of performance, since it presents a table with events that are loaded depending on the selected date.

For the monthly and annual submissions, which are scrolling lists, everything did not look so optimistic. The main problem was that the application needed to support working with recurring events without an end date. Moreover, the minimum repetition interval was one day, or, more simply, an event could be scheduled for each day. This fact already at the stage of the analysis of the task meant a long processing of the data that should have been pulled from the database. In addition to this, I also wanted to implement an endless list, without displaying the indicator with loading, and this would mean dynamic data loading during scrolling.

Further, the article will describe the process of creating exactly the annual presentation as the most demanding system resources.

All work on optimizing the calendar started with the search for a quick way to draw a table cell. We decided to use a table or UITableView both for representing the month and for representing the year. To represent the year, this solution was chosen so that it would be convenient to pull up the data immediately for the whole year, and not for individual months, as would be the case with the UICollectionView.

At first, we decided to try to create an interface using simple and popular tools, such as the components of the UIKit library. It was decided to make an array from UILabel, satisfying all the positions of the dates in the calendar, of which for one month there are 7 x 6 = 42.

Then, as it is already obvious, the text with the day number was substituted into the desired UILabel without changing the position of the label itself. As a result, it turned out that the calendar terribly slowed down and stuck. It became interesting why, and with the help of Time Profiler it was possible to establish the reason. The trouble was in the setText function, and in more detail, in the mechanism for converting an array of char-s into glyphs for a certain date and further rendering.

Therefore, the next step was to create a UILabel array in the amount of 31 pieces for the maximum number of days in a month with a predetermined number text. With this method, we manipulate the coordinates of a particular label.

Scrolling was already possible, however, even using some tricks in the form of layer rasterization, we managed to achieve only an average of 41 FPS with a maximum of sixty. However, some twitching at the beginning of scrolling and its deceleration were still noticeable.

Our further research was directed towards the Quartz library. We used the same system with predefined numbers for the 31st date, as in the method with labels. Also, depending on the month, the coordinates of the layers were set without changing the string content. As a result, the average FPS was increased to 44.

The last option was to use CoreText. With the help of it, it was possible to achieve the required speed of the annual calendar submission and 53 FPS, but then we were expected to receive the next challenge.

The standard iOS 7.0 calendar works very smoothly, but it does not have the function of displaying events for a specific day, which can be seen to represent the month as a dot under the date.

We wanted this feature to be available for the annual calendar. Here again, performance problems began to arise, since when setting up an event that repeats daily, FPS began to sag. The problem was already not related to rendering text, but to drawing a background behind the date. Therefore, we decided to draw all the events in a separate stream, and then convert the graphic context into a UIImage object. UIImage is thread-safe and therefore rendering events would look like substituting a new image for a specific month.

There are many solutions available for working with multithreading in iOS. In our case, the optimal solution was to use the inheritance classes NSOperation and NSOperationQueue. Otherwise, when using, for example, GCD (Grand Central Dispatch), there would be a problem with canceling the loading of data into the calendar, because of which it would be necessary to write an additional wrapper.

At this stage, we decided to immediately consider the problem of loading data from the database. The project used CoreData with all the resulting problems on asynchronous extraction, since the instances of NSManagedObject and NSManagedObjectContext are not thread-safe. To overcome this feature of the framework, a private NSManagedObjectContext is created when the main function of the NSOperation instance is executed, which is executed in a separate thread. This allowed us to combine two actions in a separate thread, namely:
  1. retrieving calendar events from the database,
  2. drawing and creating a picture in a separate stream.

The work of the calendar to display one year can also be illustrated by the following diagram:

As a result, we were able to create a calendar that works almost as smoothly as in the standard application from Apple. We have expanded the functionality to represent the year where the mapping of events was introduced.

I hope that this solution will help developers in creating not only calendars with a complex and resource-intensive interface, but also in the implementation of any lists where the time for rendering the table content is critical for quick work.

The screens of the application with the calendar, as it ended up with us, can be viewed below.

PS The source code of the example project is here .

PPS I also really look forward to feedback on other possible solutions to a similar problem.

Also popular now: