5
votes

I'm experiencing a redraw problem on a CATiledLayer when the parent UIScrollView is zoomed in.

I'm rendering a PDF page in a CATiledLayer backed UIView. It has another UIImageView behind it, which contains a low-res image of the page that the CATiledLayer will draw. When I zoom in, it works as expected. The CATiledLayer will render a higher resolution image according to the zoom level.

The problem occurs after zooming. If I zoom in then just leave the iPad alone, the displayed image blurs then resharpens. It looks like the CATiledLayer is being removed, since I see the blurry low resolution image in the backing view, then the CATiledLayer gets redrawn, i.e. I see the tiling effect and the resharpening of the image. This happens if I just leave the app alone and wait about 30 to 40 seconds. I've only observed it on the iPad 3rd gen (New iPad, iPad3, whatever). I'm also testing on iPad2s and I have yet to encounter the issue.

Has anyone else encountered this problem? Any known cause and, possibly, solutions?

Edit:

My UIScrollViewDelegate methods are as follows:

// currentPage, previousPage, and nextPage are the pdf page views
// that are having the refresh problem 

- (void)positionBufferedPages { 
  // performs math {code omitted}

  // then sets the center of the views
  [previousPage.view setCenter:...];        
  [nextPage.view setCenter:...];
}

- (void)hideBufferedPages {
  if ([previousPage.view isDescendantOfView:scrollView]) {
    [previousPage.view removeFromSuperview];
  }

  if ([nextPage.view isDescendantOfView:scrollView]) {
    [nextPage.view removeFromSuperview];
  }          
}

- (void)showBufferedPages {
  if (![previousPage.view isDescendantOfView:scrollView]) {
    [scrollView addSubview:previousPage.view];
  }

  if (![nextPage.view isDescendantOfView:scrollView]) {
    [scrollView addSubview:nextPage.view];
  }

  if (![currentPage.view isDescendantOfView:scrollView]) {
    [scrollView addSubview:currentPage.view];
  }
} 

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
  return currentPage.view;
}

- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
  [self hideBufferedPages];
}

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollViewParam withView:(UIView *)view atScale:(float)scale {
  [self positionBufferedPages];
  [self showBufferedPages];    
}

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
  // nothing relating to the pdf page view
  // but does set the center of some other subviews on top of the pdf page views
}

Not sure how helpful this will be though, as the scrollview is not receiving input while the problem happens. As mentioned above, the pdf page is loaded to the CATiledLayer, then I leave the iPad alone (no input is received by the device), and the CATiledLayer will redraw by itself.

I've also tried catching calls to setNeedsDisplay, setNeedsDisplayInRect:, setNeedsLayout, and setNeedsDisplayOnBoundsChange: on both the view and the tiled layer, but the redraw happens without any of those functions getting called. drawLayer:inContext: gets called, of course, but the trace only shows some Quartz calls being started in a background thread (as expected, as tiled layers prepare the content in the background), so it is of no help either.

Thanks in advance!

4
Could it be because of the hi-res retina display of the ipad 3? Are you setting the CATiledLayer contentsScale correctly? Also in your custom PDF creation rendering code you should be using UIGraphicsBeginImageContextWithOptions and set the scale accordingly - Lefteris
I'm not using UIGraphicsBeginImageContextWithOptions, but I use CGContextScaleCTM to set the scale. Is there a difference? It looks like I'm rendering the pdf properly. My problem is that the CATiledLayer somehow redraws itself for no reason after a while. - Altealice
You should set the CATiledLayer contentsScale to the [[UIScreen mainScreen] scale] - Lefteris
Can you please post some code from the UIScrollViewDelegate methods, so we can get a better handle on your process? - Sam
I've added the delegate methods. Any insight is greatly appreciated. Thanks! - Altealice

4 Answers

2
votes

How is your app's memory usage looking? CATiledLayer will discard its cache and redraw if a memory warning occurs. I've seen it do this even without memory warnings being sent to the app (just a higher than usual memory load). Use Instruments to see memory usage. You may need to use the OpenGL ES Driver instrument to see what's going on with graphics memory.

2
votes

I spoke with an Apple engineer about this and the short answer is that iOS only has X amount of memory available for caching a CATiledLayer and on the Retina display of the iPad, there are just too many pixels to use more than one layer.

I had been using two CATileLayers to display a map view and a drawing view on top. I removed the second CATiledLayer and the problem went away.

1
votes

I've had the exact same problem. In my case it was caused by using the UIGraphicsBeginImageContext() function; this function does not take scale into account, which gives problems on a retina display. The solution was to replace the call with UIGraphicsBeginImageContextWithOptions(), with the scale (=third) parameter set to 0.0.

If you based your code on the Zooming PDF Viewer sample code from Apple, like I did, chances are this will solve your problem too.

1
votes

Longshot, any chance you are calling any methods on the views from the non-main thread? All sorts of unexpected funky stuff can happen if you do.