Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is UIScrollView is resetting its zoomScale property automatically?

I have an extended UIScrollView that is taken directly from the Apple documentation example project "ZoomingPDFViewer"

If you get this project you'll note that it's UIScrollView extension class "PDFScrollView" has a glaring bug when you run it (on sim AND device)

If you pinch zoom far enough, you'll eventually hit the zoom scale limits set by the StoryBoard... but then, you can just re-pinch and go right on past the limits.

I've been trying to implement this class in my own project, and this bug has come with it.

For some reason, after the zoom ends, the zoomScale property of the UIScrollView is being arbitrarily set back to 1.0, so you can effectively pinch zoom to infinity... keep in mind the zoomScale being reset does NOT impact the content... so you can just keep zooming out until the PDF in the demo is just a pixel on the screen.

I've never seen this before and I've done tons of custom UIScrollViews before.

I've tried a few steps to solve it but nothing seems to be working. Does anyone know what would cause a UIScrollView to reset its zoomScale in that manner?

I thought it might be because the subviews are being removed and added during zooms, but I put a dummy view in the scroll view to act as an anchor, and that did not work either.

Like I said.. ZoomingPDFViewer is the exact code I am working with, using iOS 6.0

Any ideas would be appreciated. Sample code below

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
NSLog(@"Ending Zoom %0.2f %0.2f", [self zoomScale], scale);

// Set the new scale factor for the TiledPDFView.
_PDFScale *= scale;

// Calculate the new frame for the new TiledPDFView.
CGRect pageRect = CGPDFPageGetBoxRect(_PDFPage, kCGPDFMediaBox);
pageRect.size = CGSizeMake(pageRect.size.width*_PDFScale, pageRect.size.height*_PDFScale);


// Create a new TiledPDFView based on new frame and scaling.
TiledPDFView *tiledPDFView = [[TiledPDFView alloc] initWithFrame:pageRect scale:_PDFScale];
[tiledPDFView setPage:_PDFPage];

// Add the new TiledPDFView to the PDFScrollView.
[self addSubview:tiledPDFView];
self.tiledPDFView = tiledPDFView;

NSLog(@"End of end zoom %0.2f", [self zoomScale]);

}

This is the end scroll delegate... both of this logs produce a zoomScale that is exactly what you would expect... so... any number other than 1.0, assuming you actually zoomed.

Then layoutSubviews is called..

// Use layoutSubviews to center the PDF page in the view.
- (void)layoutSubviews 
{
[super layoutSubviews];

// Center the image as it becomes smaller than the size of the screen.

CGSize boundsSize = self.bounds.size;
CGRect frameToCenter = self.tiledPDFView.frame;

// a bunch of code that sets frameToCenter

self.tiledPDFView.frame = frameToCenter;
self.backgroundImageView.frame = frameToCenter;

/*
 To handle the interaction between CATiledLayer and high resolution screens, set the tiling view's contentScaleFactor to 1.0.
 If this step were omitted, the content scale factor would be 2.0 on high resolution screens, which would cause the CATiledLayer to ask for tiles of the wrong scale.
 */
self.tiledPDFView.contentScaleFactor = 1.0;

NSLog(@"Subviews Layed Out %0.2f", [self zoomScale]);
}

At this point, zoomScale reports back as 1.0 no matter what... this traces 0.003 seconds after the End Zoom delegate method reports a proper zoom scale.

like image 922
M. Ryan Avatar asked Sep 28 '12 15:09

M. Ryan


1 Answers

Yeah, this happened sometime around iOS 3. zoomScale is always relative to the current state of the scrollView, not a global measurement. After each zoom, you need to recalculate the minimum and maximum zoom scales relative to the current overall zoom scale.

This is some code I've used in the past. It should give you the gist of the process:

Early on, grab the values you need:

- (void) viewDidLoad
{
    [super viewDidLoad];
    ...
    // Squirrel away min and max zoom values from nib
    sMinZoomScale = self.scrollView.minimumZoomScale; // the very minimum
    sMaxZoomScale = self.scrollView.maximumZoomScale; // the very maximum
    sGlobalZoomScale = 1.0f;  // we're currently at 100% size
    ...
}

and then the rest (self.navigationView is the scrollView's content view):

- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView
                        withView:(UIView *)view
                         atScale:(CGFloat)scale
{
    // after a zoom, change the resolution of our map
    sGlobalZoomScale *= scale;
    // Peg the scale to minZoom and maxZoom, in case of rounding errors
    sGlobalZoomScale = MIN(MAX(sMinZoomScale, sGlobalZoomScale), sMaxZoomScale);
    self.navigationView.globalZoomScale = sGlobalZoomScale;
    self.navigationView.globalScaleTransform = self.globalScaleTransform;
    [self updateResolution];

    // throw out all tiles so they'll reload at the new resolution
    [self.navigationView setNeedsDisplay];
}

// By the time we get here, the scrollview's applied a fake scale and translate transform, and
//   altered the scrollview's zoom scale. BUT, the navigationview is as it ever was.
- (void) updateResolution
{
    CGFloat zoomScale = [self.scrollView zoomScale];

    CGSize oldContentViewSize = self.navigationView.frame.size;    
    // zooming properly resets contentsize as it happens.
    CGSize newContentSize = self.scrollView.contentSize;

    CGPoint newContentOffset = self.scrollView.contentOffset;
    CGFloat xMult = newContentSize.width / oldContentViewSize.width;
    CGFloat yMult = newContentSize.height / oldContentViewSize.height;

    newContentOffset.x *= xMult;
    newContentOffset.y *= yMult;

    CGFloat currentMinZoom = self.scrollView.minimumZoomScale;
    CGFloat currentMaxZoom = self.scrollView.maximumZoomScale;

    CGFloat newMinZoom = currentMinZoom / zoomScale;
    CGFloat newMaxZoom = currentMaxZoom / zoomScale;

        // Reset zoom scales temporarily so we can patch up the revised content view
    // don't call our own set..zoomScale, 'cause they eventually call this method.  Infinite recursion is uncool.  
    [self.scrollView setMinimumZoomScale:1.0f];
    [self.scrollView setMaximumZoomScale:1.0f];
    [self.scrollView setZoomScale:1.0f animated:NO];

    [self.navigationView setFrame:CGRectMake(0.0f, 0.0f, newContentSize.width, newContentSize.height)];
    [self.scrollView setContentSize:newContentSize];
    [self.scrollView setContentOffset:newContentOffset animated:NO];

    [self.scrollView setMinimumZoomScale:newMinZoom];
    [self.scrollView setMaximumZoomScale:newMaxZoom];
}

Hope this helps.

like image 139
rsswtmr Avatar answered Nov 15 '22 05:11

rsswtmr