Getting the visible rect of an UIScrollView's content

Or you could simply do

CGRect visibleRect = [scrollView convertRect:scrollView.bounds toView:zoomedSubview];

Swift

let visibleRect = scrollView.convert(scrollView.bounds, to: zoomedSubview)

Answering my own question, mostly thanks to Jim Dovey's answer, which didn't quite do the trick, but gave me the base for my answer:

CGRect visibleRect;
visibleRect.origin = scrollView.contentOffset;
visibleRect.size = scrollView.bounds.size;

float theScale = 1.0 / scale;
visibleRect.origin.x *= theScale;
visibleRect.origin.y *= theScale;
visibleRect.size.width *= theScale;
visibleRect.size.height *= theScale;

The main difference is that the size of the visibleRect ought to be scrollView.bounds.size, rather than scrollView.contentSize which is the size of the content view. Also simplified the math a bit, and didn't quite see the use for the isless() which would break the code whenever it's greater.


You have to compute it using UIScrollView's contentOffset and contentSize properties, like so:

CGRect visibleRect;
visibleRect.origin = scrollView.contentOffset;
visibleRect.size = scrollView.contentSize;

You can then log it for sanity-testing:

NSLog( @"Visible rect: %@", NSStringFromCGRect(visibleRect) );

To account for zooming (if this isn't already done by the contentSize property) you would need to divide each coordinate by the zoomScale, or for better performance you would multiply by 1.0 / zoomScale:

CGFloat scale = (CGFloat) 1.0 / scrollView.zoomScale;
if ( isless(scale, 1.0) )      // you need to #include <math.h> for isless()
{
    visibleRect.origin.x *= scale;
    visibleRect.origin.y *= scale;
    visibleRect.size.width *= scale;
    visibleRect.size.height *= scale;
}

Aside: I use isless(), isgreater(), isequal() etc. from math.h because these will (presumably) do the right thing regarding 'unordered' floating-point comparison results and other weird & wonderful architecture-specific FP cases.


Edit: You need to use bounds.size instead of contentSize when calculating visibleRect.size.