Avoiding UIView to create very large drawing canvas
I derived a class from UIView, only to realize that there are real limitations on its size due to memory. I ha开发者_开发百科ve this UIView inside UIScrollView.
Is there a way for me to put something inside a scroll view that is not a UIView-derived class but into which I can still draw, and which can be very very large?
I don't mind having to respond to expose-rectangle events, like one does when using conventional windowing systems.
Thanks.
The things inside of a UIScrollView
must be UIViews
, which are size-restricted for memory reasons. UIView maintains a bitmapped backing store for performance reasons, so it has to allocate memory proportional to its size.
The usual way that you handle this is to generate several UIViews
and swap them out as the user scrolls around. The other version of that is to use CATiledLayer
. Neither of those give you the "giant canvas" drawing model, though. It's up to you to break things up and draw them as needed. This is the usual approach, though.
If you really want a giant canvas, my recommendation would be a CGPDFContext
. There is rich existing support for these, particularly using UIWebView
(remember, you can open data:
URIs to avoid reading files from disk). And you can draw parts of them directly by applying affine transforms and then CGContextDrawPDFPage
. CGBitmapContext
is another approach, but it could require a lot more memory for a small amount of drawing.
So you have a UIView
inside a UIScrollView
, but you want your UIView
to have very large bounds (i.e., so it matches the size of your UIScrollView
's contentSize
). But you don't want to draw the entire UIView
every time it needs displaying, nor can you fit its entire contents in memory at once.
Make your UIView
uses a CAScrollLayer
backing, as follows:
// MyCustomUIView.m
+ (Class) layerClass
{
return [CAScrollLayer class];
}
Add a method to update the scroll position when the user scrolls the UIScrollView
containing your UIView
:
// MyCustomUIView.m
- (void) setScrollOffset:(CGPoint)scrollOffset
{
CAScrollLayer *scrollLayer = (CAScrollLayer*)self.layer;
[scrollLayer scrollToPoint:scrollOffset];
}
Ensure that when you draw your UIView
, you only draw the portions contained in the CGRect
provided to you:
- (void)drawRect:(CGRect)rect
{
// Only draw stuff that lies inside 'rect'
// CGRectIntersection might be handy here!
}
Now, in your UIScrollViewDelegate
, you'll need to notify your CAScrollLayer
backed view when the parent UIScrollView
updates:
// SomeUIScrollViewDelegate.m
- (void) scrollViewDidScroll:(UIScrollView *)scrollView
{
// Offset myCustomView within the scrollview so that it is always visible
myCustomView.frame = CGRectMake(scrollView.contentOffset.x,
scrollView.contentOffset.y,
scrollView.bounds.size.width,
scrollView.bounds.size.height);
// "Scroll" myCustomView so that the correct portion is rendered
[myCustomView setScrollOffset:self.contentOffset];
// Tell it to update its display
[myCustomView setNeedsDisplay];
}
You can also use CATiledLayer
, which is easier because you do not have to track the scroll position — instead your drawRect
method will be called with each tile as-needed. However this will cause your view to fade in slowly. It might be desirable if you intend to cache parts of your view and don't mind the slow updates.
精彩评论