📜 ⬆️ ⬇️

“Use standard control” or how we stole the calendar from Apple

In this article, I would like to acquaint readers with a fairly common task of creating a calendar, which was set by our team in one project.

The desire to share experience in overcoming the difficulties encountered in the implementation of such applications, 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 the solution of a complex of tasks related to the fast drawing 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 was the appearance of the calendar. As it seemed to us, it would be very cool to make the calendar as similar as possible to the Apple solution. As a result, a standard calendar application was unanimously chosen as the 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 presentation of the year in the standard calendar can be seen below.



For the weekly presentation, we did not see any performance problems, as it represents a table with events that are loaded depending on the selected date.



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

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

All work on the optimization of the calendar began with finding a quick way to draw a table cell. We decided to use a table or UITableView for both the month view and the year view. For the presentation of the year, this solution was chosen so that it would be convenient to pull the data for the whole year at once, and not for individual months, as would be the case with the UICollectionView.

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



Then, as is already obvious, the text with the number of the day was substituted into the desired UILabel without changing the position of the label itself. The result was that the calendar was terribly slow and sticking. It became interesting why, and with the help of Time Profiler it was possible to establish the reason. The trouble was in the function setText, and if in more detail, in the mechanism of converting an array of chars into glyphs for a specific date and further drawing.

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



Scrolling was already possible, however, even using some tricks in the form of rasterization of a layer, only an average value of 41 FPS was achieved with a maximum of sixty. However, some twitching at the beginning of scrolling and slowing it down was still noticeable.

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

The last option was to use CoreText. With it, we managed to achieve the required speed of the annual calendar view and 53 FPS, but then we were waited for by another chelenzh.

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



We wanted this feature to be available for the annual calendar. This is where performance problems began to arise again, since when setting an event that repeats daily, the FPS started to subside. The problem was already connected not with drawing the text, but with drawing the 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 drawing events would look like the substitution of a new image for a particular month.

There are many solutions available for working with multithreading in iOS. In our case, the optimal solution was to use the heirs 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, which would have caused us 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 attendant problems on asynchronous extraction, since the NSManagedObject and NSManagedObjectContext instances are not thread-safe. To overcome this framework feature, 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 the two actions in a separate thread, namely:
  1. retrieving calendar events from the database
  2. drawing and creating images in a separate stream.

The work of the calendar for displaying one year can also be illustrated by the following diagram:



As a result, we managed to create a calendar that works almost as smoothly as in the standard Apple application. We have expanded the functionality to represent the year where the event display was entered.

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 rendering time of table content is critical for quick work.

The application screens with the calendar, as it eventually turned out with us, can be viewed below.



PS The source code of the sample project is here .

PPS I also count on feedback on other possible solutions for a similar task.

Source: https://habr.com/ru/post/239651/


All Articles