开发者

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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜