Hello! This time I want to tell you how I implemented the iBooks alternative. In my previous post I wrote about
the soft hyphenation algorithm in the text. It was just useful when creating your reading room, you can appreciate its work clearly in the
application . But besides this, when implementing the project I had to face many other interesting things, such as parsing and rendering HTML with CSS, implementing controls with custom design, etc. Our
rashapasta designer loves to give me tasks with a non-standard interface, which needs to be implemented with pens, but first things first.
UI (or dancing with a tambourine)
In terms of UI in the project, it was not the easiest task to make a grid table with horizontal paging. As usual, in search of ready-made solutions, I climbed onto
stackoverflow.com , but alas, everything that went over was more or less unsuitable.
There were high hopes for
AQGridView , but as it turned out, there are only empty stubs from horizontal filling and paging. It was decided to give her a second chance and apply a familiar trick to turning the table 90 degrees. At first this option even seemed to be working and less acceptable, but even here there were some stones.
The
bugs in the
AQGridView itself and in the standard
UIScrollView beat me
away from the desire to use this component. In some situations, the grid was constantly breaking down: some cells dropped out and order was constantly flying. In order to dispel doubts about my curvature, I tried to reproduce the problem on the demo from the kit - the bug was confirmed.
As for the
UIScrollView and its derivatives - here I also first sinned on
AQGridView , but when I began to use
UITableView , the problem repeated. The essence of the bug is that with the
UIScrollView rotated through the transformation, the bounce effect fell off, which was very ugly and unnatural for iOS.
It was experienced that it turned out that the resize and the movement of the
UIScrollView were guilty when the device was turning, which was done by hand through the
layoutSubviews handler. Having taken my shaman tambourine, I found out that the positioning of the rotated
UIScrollView through the
center property breaks everything. Probably there were some other conditions, but so many options were tried that it was impossible to remember.
')
This whole long story ended up having to be perverted with the good old
UITableView . I fixed the bounce, and the problem with the turn was solved. A table cell is made the size of a page and consists of several sub-cells, each of which is implemented as an instance of a separate class. It turned out like this:

Working with HTML and Liang-Knuth algorithm.
With parsing popular e-book formats and rendering is another story. HTML is not difficult in principle,
libxml did a great job. The HTML file is processed recursively, divided into blocks of text, each block is assigned the appropriate attributes. It remains to drive all this into the framesetter from
CoreText and is ready. But it was not there! It is necessary to make transfers and alignment in width. I had to go down a level and use not the framesetter, but the typesetter. With it, you can conveniently cut the text into lines, for example, by function
CFIndex CTTypesetterSuggestClusterBreak( CTTypesetterRef typesetter, CFIndex startIndex, double width);
In the process of splitting into lines need to determine the place of the gap. If a gap occurs in the middle of a word, then you need to correctly put the transfer. This is where the implementation of the above-mentioned
Liang-Knuth algorithm comes to the rescue.
Render (or don't make the user wait!)
All that remains is to cut the resulting mountain of lines of text into pages and render it. Empirically it turned out that this whole bunch of word processing operations before the render takes a whole lot of time. From the profiler, I realized that the distribution of hyphenation was to blame. I drove the calculation of the book into the background and in a separate thread - it began to work faster.
The only drawback is that while the render is going, you cannot use the rewind slider. If it is necessary to move to a chapter that has not yet been processed, we put it first in the processing queue in order to display it on the screen as quickly as possible.
The result was not bad, and on the iPad, books are processed fairly quickly (considering that this is a render on the fly).
Here is how the rendered pages look in different screen orientations:

AFNetworking was normally zayuzan to work on HTTP, I highly recommend it. The truth was one “but”, when analyzing an application for memory leaks, there was a problem with the display of file download progress associated with circular references. In the
setDownloadProgressBlock method,
there was a block like this:
if ([self.progressDelegate respondsToSelector:@selector(fileDownloadRequest:progressBytes:withTotalBytes:)]) { [self.progressDelegate fileDownloadRequest:self progressBytes:alreadyDownloadedBytes+totalBytesRead withTotalBytes:alreadyDownloadedBytes+totalBytesExpectedToRead]; }
The presence of self in the code block and caused a cyclical dependence. This is solved by creating a separate local variable into which the pointer to the delegate is copied, and this variable is already used in the block. It became like this:
id<FileDownloadProgressDelegate> progress = self.progressDelegate; [self.request setDownloadProgressBlock:^(NSInteger bytesRead, NSInteger totalBytesRead, NSInteger totalBytesExpectedToRead) { if ([progress respondsToSelector:@selector(fileDownloadRequest:progressBytes:withTotalBytes:)]) { [progress fileDownloadRequest:self progressBytes:alreadyDownloadedBytes+totalBytesRead withTotalBytes:alreadyDownloadedBytes+totalBytesExpectedToRead]; } }];
In the future, as soon as I have free time, I will continue to describe my experience in developing for iOS, but for now I invite you to discuss the result of my work in the comments.