How to Center Content Within UIScrollView
There seems to be some confusion around the net when it comes to the best way to center a view within an UIScrollView.
And it’s actually not that easy to get right. In PSPDFKit version 1, I used the Apple-recommended way (see Apple’s photo example) of subclassing layoutSubviews. This was “good enough” until I added smart zoom in version 2 of my PDF framework. (That is, text blocks are detected and zoomed onto with a double tap, just like you’re used to in the mobile version of Safari). I noticed that the zoomToRect method did not work properly; the final rect was offset, and it was quite obvious why, since the layoutSubview-method moved around the frame of the view that was zoomed in on. And don’t try to compensate this moving – it seems relatively impossible to get right.
The next generation (PSPDFKit 2.2 and upward) used override setContentOffset: to always center the content. This works, but has the drawback that you can’t pan the view around when you’re zooming out more – it’s always fixed centered. This solution also has a very nasty hidden bug, where the UIScrollView can be “locked up” and doesn’t accept any touches anymore until you perform a pinch in/out. I’ve had Apple DTS against this and even the person helping me only came up with useless workarounds (like not using gesture recognizers… right.) It’s hard to reproduce this bug here without open sourcing half of my framework, so please just trust me on that one.
I’ve asked around on Twitter (thanks, everyone!) and most recommendations were about overriding layoutSubviews. Some suggested stuffing the view into another view and doing the centering manually (which I tried) but this has the drawback that you can scroll into the empty space when zoomed in (or you adapt the view and get the same problem as you had in layoutSubviews).
The solution that works best by far is by setting contentInset. It allows you to pan around the view when zoomed out and doesn’t expose the “lock” bug that overriding setContentOffset: had. It also works fine with using zoomToRect. There are a few gotchas when using edgeInsets as well – for example, I have a mechanism that preserves the current view state (page, zoom, contentOffset) and restores that later on. If you set contentOffset to 0,0 this will decenter your image (in contrast to methods one and two). But you can trigger re-centering with following trick:
1 2 3
I’ve made a small GitHub project that shows off all three centering techniques and zooms to a small rect on double tap (to show off the zoomToRect problem). Hopefully this helps someone save a few hours!