5

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!

Altealice
  • 3,572
  • 3
  • 21
  • 41
  • 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 May 03 '12 at 13:53
  • 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 May 03 '12 at 14:03
  • You should set the CATiledLayer contentsScale to the [[UIScreen mainScreen] scale] – Lefteris May 03 '12 at 14:13
  • Can you please post some code from the UIScrollViewDelegate methods, so we can get a better handle on your process? – Sam May 07 '12 at 13:59
  • I've added the delegate methods. Any insight is greatly appreciated. Thanks! – Altealice May 08 '12 at 07:22

4 Answers4

2

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.

Josh Knauer
  • 1,744
  • 2
  • 17
  • 29
  • I'm logging memory warnings, but it's not triggering when the redraw happens. I'll try to check with instruments. Thanks for the info! – Altealice May 11 '12 at 13:13
  • So how did you solve this problem? A lot of us are having the same problem and a solution would be great – Brodie May 17 '12 at 03:16
2

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.

Brodie
  • 3,526
  • 8
  • 42
  • 61
1

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.

  • setting to scale to 0.0 is false... it should be set like this: UIGraphicsBeginImageContextWithOptions(frame.size, NO, [[UIScreen mainScreen] scale]); – Lefteris May 03 '12 at 14:05
  • Your way works too of course, but setting scale to 0.0 has the same effect. http://developer.apple.com/library/ios/#documentation/uikit/reference/UIKitFunctionReference/Reference/reference.html –  May 03 '12 at 14:06
  • Interesting. Thanks. I'm not calling UIGraphicsBeginImageContext() though, since I'm drawing using `drawlayer:inContext:`. Are you using a different method? – Altealice May 03 '12 at 14:08
  • I use that as well, but I used UIGraphicsBeginImageContext to create the blurry image that is used while zooming. –  May 03 '12 at 14:10
  • So, UIGraphicsBeginImageContext is only in the blurry backing image, but there is no UIGraphicsBeginImageContext in drawLayer:inContext:, right? – Altealice May 03 '12 at 14:12
  • I'm afraid not, sorry. Maybe it would help if you could post some code of your scrollview delegate? –  May 04 '12 at 06:29
1

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.

Danra
  • 9,546
  • 5
  • 59
  • 117
  • Yeah, there are stuff happening in the background, but none of them should be calling the view. If this is the case, then the background stuff might be eating up memory and causing redraws as Josh says. I'll check when I get back to work tomorrow. – Altealice May 13 '12 at 19:28