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.
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).
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With