We recently launched a new update to the Kobo iOS app (version 5.10) which includes support for Japanese content, improved font resizing, and a faster and more responsive reading experience for comics.
We're particularly excited about this new comic experience, which we think readers are really going to like. It provides a fluid reading experience, complete with seamless loading times so that you can read without any interruptions or delays. The approach we took in developing this new viewer was a bit unconventional, and so for the more technically minded here's some background from the Kobo Labs team’s lead iOS developer on what went into building it.
It had been almost a year since we first launched support for fixed-layout EPUBs (including comics, cookbooks, and other illustrated content) in the Kobo iOS app, and while we had made some performance enhancements along the way, we still felt we could do a lot more to improve it.
The biggest bottleneck for us was relying on UIWebViews to render (and cache) each page's content. Considering that almost all of our comic books were just using HTML to display a single image for each page, it seemed a shame to have to deal with the overhead of using WebViews – and this sparked a plan to work on a new image-based fixed-layout renderer.
There were three distinct challenges we had to tackle:
- We had to be able to figure out whether a given book was composed solely of images – anything that wasn't would just use our standard fixed-layout renderer.
- We'd have to determine the ordered list of image files that made up the book.
- We needed to build a renderer to display the images nicely, and really optimize its performance.
…and all of this had to be done in-app, with no dependencies on server-side processing.
The method we use to figure out if a book can be loaded in the image-based renderer begins by checking for some obvious things that would rule out the book (such as SMIL media overlays) and then starts iterating through each page in the book's spine, using a series of regular expressions to check for non-image content. We use an additional group of regular expressions to locate the image's filename, which we then match against the EPUB manifest. If we've gone through the spine and found nothing but images we can then modify each page in the spine to point to the manifest item for its associated image.
This then brings us to the new renderer to display these images. This was by far the most work out of the project, but it was actually started on well before we nailed down the detection and conversion logic.
Since we were also adding support in this release for Open Manga Format EPUB3 content (which basically boils down to an ordered list of images) we had some books we could start testing with right away. Functionally, the new renderer was designed to behave exactly like our existing fixed-layout renderer, but with the added benefit of being able to swipe to 'peek' at the next and previous page spreads.
The renderer was put together primarily using UIScrollViews and UIImageViews. There's a main ScrollView, which has paging enabled and its contentSize's width set to the window's width multiplied by the number of page spreads (so that the standard UIScrollView paging is done between the spreads, rather than individual pages). This main ScrollView contains three other ScrollViews to display the current spread, previous spread, and next spread, and they're shuffled around horizontally to provide the illusion that you're paging through a large canvas that spans the entire length of the book.
Each of these three ScrollViews contains a main subview containing two UIImageViews, with the ImageViews displaying the images making up that spread. The main subview is returned by the UIScrollViewDelegate's viewForZoomingInScrollView method so that the whole spread can be zoomed and panned (rather than just the individual pages). In addition to the default UIScrollView behaviours we've also added some extra UIGestureRecognizers to allow for things like tapping to pan to the adjacent page and double-tapping to snap to a single-page or two-page zoom-level.
To keep the performance nice and snappy we load the next few pages' images (sequentially) on a background queue and store them in an NSCache. If an image isn't already in the cache when we need it, we run a block on a background queue to load it, then dispatch_async back to the main queue to update the appropriate UIImageView.
The end result of all this is an incredibly responsive reading experience for comics and manga, where you can seamlessly swipe from the book's cover to its last page. We hope that you like it as much as we do!