Adding thumbnails with PDFKit

Today I’m continuing with some ideas from my recent post about using PDFKit. In that post I was using a custom PDF view for an app that would work something like a basic slide presentation app.

Part of that was adding a thumbnail view with PDFThumbnailView, which ended up looking like this.

PDFThumbnailView works with a PDFView and generates thumbnails for pages in the current document. You can tell it how big the thumbnails should be and whether to arrange the thumbnails horiontally or vertically, but that’s about it as far as layout goes.

As you can see above, if there are more slides than fit in the view, the view shows thumbnails of pages at regular intervals in the document. My test document is 50 pages, so I end up with pages 1, 11, 21, 30, 40, and 50 always visible in the thumbnail view. If you tap and hold you can drag to any page, so all pages are available, just not always visible.

By the way, I’m using a 50 page test PDF I made that just shows the current page number, which is super handy for testing. Feel free to get a copy if you can use it.

But I want to see every page

That works OK but if I’m looking for a specific page in a presentation, I’d prefer to see thumbnails of every slide in a scrolling list, like in actual presentation apps. So let’s make it bigger. And since it’ll be too big to fit on the screen, let’s put it in a scroll view.

First I’ll change the constraints on the thumbnail view to make it wide enough to accomodate all the slides. Back in my previous PDFKit post I configured the thumbnail view. Now I’ll change to use these constraints.

    pdfThumbnailView.heightAnchor.constraint(equalToConstant: CGFloat(thumbnailSize)),
    pdfThumbnailView.widthAnchor.constraint(equalToConstant: CGFloat(pdfDocument.pageCount*thumbnailSize))

The height is the same, but the width is the thumbnail size multiplied by the number of pages– which should be enough to contain every page in the document. I’ll deal with position in a bit.

Next I’ll create the scroll view and add the thumbnail view as its only subview. I’m leaving out things like background color to keep it simple here.

var pdfThumbnailScrollView = UIScrollView()
pdfThumbnailScrollView.translatesAutoresizingMaskIntoConstraints = false

Finally I’ll add some constraints so the scroll view and the thumbnail view know how to lay themselves out. The scroll view only has one subview, so I constrain it to fit. I’m not using my addSubviewAndConstrain function from the previous post here because I need the width to be the value I configured earlier.

    pdfThumbnailView.leadingAnchor.constraint(equalTo: pdfThumbnailScrollView.leadingAnchor),
    pdfThumbnailView.trailingAnchor.constraint(equalTo: pdfThumbnailScrollView.trailingAnchor),
    pdfThumbnailView.topAnchor.constraint(equalTo: pdfThumbnailScrollView.topAnchor),
    pdfThumbnailView.bottomAnchor.constraint(equalTo: pdfThumbnailScrollView.bottomAnchor)

Add the scroll view to the view hierarchy, and that’s it!

Well… almost. It looks good at first, but as you go through the document, something weird happens. Near the beginning of the document it’s exactly right.

But as you go through the document, the larger thumbnail for the current page gets skewed off center. Near the end of the document it’s off by nearly a full thumbnail. You end up with the same page number appearing twice with different sizes.

Not good. And not buggy because of any documented requirements I’m not meeting.

I experimented with a few random-seeming changes hoping for a fix (a bit of shotgun debugging) and eventually found that if I made the thumbnail view wider, the problem disappeared. Why though? It should be big enough. I guessed that this might be due to spacing between thumbnail images. I can choose what size thumbnail I want, but I can’t tell the thumbnail view anything else about its layout. So maybe there’s some padding between thumbnails I should account for?

I took a couple of screenshots and examined them with xScope and found that this was the case. The horizontal distance from one thumbnail to the next was 2 pixels more than the thumbnail width. My thumbnail view was not quite wide enough to hold all the thumbnails. But I still couldn’t be sure that this was what caused the off-center thumbnails.

Let’s try it out. I’ll add a variable to hold the extra padding, and then modify the thumbnail view’s width constraint to use it.

let pdfThumbnailPerPagePadding = 2
// ...later...
    pdfThumbnailView.widthAnchor.constraint(equalToConstant: CGFloat(pdfDocument.pageCount*(thumbnailSize + pdfThumbnailPerPagePadding)))

The width is now the number of pages multiplied by the thumbnail width plus a fudge factor for every page. I gave it a try, and…

Yes! That was it. A little extra width was all the layout needed. All thumbnails are visible in a scrolling view, and the current page’s thumbnail is exactly where it should be.

One detail I don’t completely understand is that I needed to add the extra padding once per page. If it’s necessary because of space between thumbnails, it seems like the padding should appear pdfDocument.pageCount - 1 times. Doing it that way still has layout issues though– it has to be at least pdfDocument.pageCount times. I speculate that there’s a bit of padding before the first thumbnail and after the last one, but I can’t be sure.

Caution, bugs may exist

Of course this is all based on my observations of how PDFThumbnailView currently works. Sicne this isn’t documented, it could change in the next version of iOS and break my layout. It’s a risk and I’ll have to test on beta iOS releases.

I’m also not certain that I’m using PDFThumbnailView as intended here, so maybe this isn’t even supposed to work and I’ve just stumbled across something that makes it do what I want. Regardless, I filed FB7379442 with Apple in the hope that these details will someday become clear.