Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 have 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.

like image 229
Friend Avatar asked Aug 26 '11 12:08

Friend


People also ask

What is the large canvas feature in Adobe Illustrator?

Adobe Illustrator lets you create your large-scale artwork on a 100x canvas, which provides more working space (2270 x 2270 inches) and ability to scale. You can use the large canvas to create your large-scale artwork without losing the document fidelity. The large canvas feature is available in the latest version of Illustrator (24.2).

Can I export the artwork created on a large canvas?

You can export the PDF containing the artwork created on a large canvas through the Asset Export panel. However, the artwork will appear ten times smaller when you open it in a Acrobat version 1.5 or below. To view it in actual size, use a PDF preset with Acrobat 1.6 or later.

How do I create a document on a large canvas?

Your new document is automatically created on a large canvas if one of the following settings is applied in the New Document dialog: Artboard size larger than 227 inches (default canvas size) or large units specified in Feet, Meters, Yards, or Feet and Inches. Large number of artboards that cannot be accommodated in the default canvas size.

How do I use an image as a background in UIView?

Image-based backgrounds - For views that display relatively static content, consider using a UIImageView object with gesture recognizers instead of subclassing and drawing the image yourself. Alternatively, you can also use a generic UIView object and assign your image as the content of the view’s CALayer object.


2 Answers

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.

like image 99
Rob Napier Avatar answered Nov 15 '22 07:11

Rob Napier


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.

like image 20
simeon Avatar answered Nov 15 '22 08:11

simeon