Performance UIImageView vs UIView with QuartzCore - ios

So I just discovered QuartzCore, and I am now considering to replace a UIImageView containing a bitmap with a UIView subclass doing things like
CGContextFillEllipseInRect(contextRef, rect);
They would look exactly the same: the bitmap is just a little filled circle.
The very view I'm replacing is playing an important role in my app: it's being dragged around a lot.
Question: performance wise: should I bother? I can imagine that the vector circle is being recalculated all of the time, while the bitmap is just buffered. Or that the vector is easier to digest than a bitmap.
Can anyone advise?
thanks ahead

All UIView's on iOS are layer backed. So drawRect will only be called once and you will draw to the CALayer backing the view. You can have it draw again by calling setNeedsDisplay. When you are dragging the view around and drawing it, the view will render from the layer backing. Using a UIImageView is also layer backed and so the end result should be two layer backed views. The one place where you may see a difference is in low memory situations when the view is not visible (though I am not sure).

Related

How does a UIView/CALayer render itself without drawRect:?

In Apple's Core Animation documentation, it says there are two rendering paths involved. From what I know, CALayer caches bitmap data of a UIView content. There are two ways of providing content of a CALayer. One is implementing drawRect: or other CALayer drawing methods, the other is setting a bitmap to the property contents of CALayer.
Here I'm wondering, what happens behind the scene if neither of the above two things is done? I believe there is a private drawing path UIView uses in this situation. What is this private drawing path consists of? How does it work?
The crux of CALayer is that it's GPU-backed. In modern graphics and animation, you want to minimize the number of times your bitmap data crosses between the CPU and the GPU. These operations are costly.
CALayer always uses a private drawing path, whether you use setContents: or drawRect:. In fact, the underlying plumbing of CALayer handles both of these in essentially the same way. When you setContents: , CALayer takes the image you gave it, and uploads it to the GPU via OpenGL (probably nowadays Metal) calls. When you drawRect: the CALayer gives you a context into which you can draw, and then it does the same thing with the bitmap data.
If you don't set contents or implement drawRect, you can still do things like set the layer's background color, border, corner radius, etc. This is being rendered by CALayer's GPU-based private drawing path.
CALayer does not draw any of its content using drawRect:. Only view based drawing techniques like Core Graphics use drawRect: but the only problem with this ways of things is that it is done on the CPU and the main thread making it an expensive process. So instead, Core Animation manipulates a cached bitmap of your apps content in the graphics hardware directly which is far much more optimised. You update or provide the initial contents for a Core Animation layer through one of its delegates (displayLayer: or drawLayer:inContext) or using the contents property as you have mentioned. All layer objects in Core Animation are derived from CALayer.
CALayer is simply a layer object belonging to Core Animation which in itself is simply a support system for UIView or subclasses of UIView. Core Animation is not a drawing technology in the sense it cannot create primitive shapes like Quartz, Open GL ES and Metal can. Instead, Core Animation allows you to manipulate an already existing view and it does this by caching the bitmap data of a UIView and sending this off to the graphics hardware to be manipulated. We say Core Animation is a support system and all its work relies on using layer objects of which CALayer is the main type. It can only do this of course if a view has a layer and a view does not actually need a layer to exist. However, in iOS all views come with a layer attached by default. We say views in iOS are "layer backed". In MacOS, you need to actually add Core Animation support for views.
The actual drawing of the contents of a CALayer happens in a few ways. The first is changing the contents property of the CALayer as you have mentioned and you do this by giving it a CGImageRef. The second is by implementing or overriding in a subclass the CALayer delegate displayLayer: which creates a bitmap and sets it to the content property of a layer. The third is by implementing or overriding in a subclass the CALayer delegate drawLayer:inContext which creates a bitmap, creates a graphics context to draw into that bitmap, and then calls your delegate method to fill the bitmap.
In iOS we do not usually worry about how the content of our view's layers are rendered. Since all views are layer backed, iOS manages how to render these views in the most efficient way possible using the methods I've just described. This is an optimisation to save you time and it makes layers very easy to use. You'll usually worry about overriding these delegates or subclassing them if you are developing for MacOS where views are not always layer backed. You might also worry about this if you decide not to use the default CALayer in iOS, for example you might change a view's layer from CALayer to CAMetalLayer. Or perhaps if you are looking for a performance optimisation and even this in a small number of cases.
There are three ways to provide content to a layer.
- Assign an image object directly to the layer object’s contents property.
- Assign a delegate object to the layer and let the delegate draw the layer’s content.
- Define a layer subclass and override one of its drawing methods to provide the layer contents yourself.
If we don't implement drawRect: or setting the contents property or subclass the layer, it will use the default way which is the second one to provide content to a Layer-backed views's layer. The layer will capture the content of the view and render it.

How can I animate a tile in a UIView

I need to create an animated chevron effect using the bitmap tile below. I am hoping that a UIView with a pattern fill will be sufficient, but I need to be able to animate the origin of the pattern over time, and I can't see that this is possible.
I think this is possible with Quartz, or CoreGraphics at the low level. What would be the best way to do this, and can you provide some example code in Swift that demonstrates the solution?
I would add the image as contents of multiple sublayers (CALayer instances) aligned one below the other and animate those sublayers y position.
The main layer would clip its sublayers on the border and sublayers that go offscreen would move to the bottom to be reused, similarly to how UITableView reuses its cells.
You might also want to look at CADisplayLink to have better control of the rendering.

Best way to handle autoresizing of UIView with custom drawing

I'm working on a custom view, that has some specific Core Graphics drawings. I want to handle the view's autoresizing as efficiently as possible.
If I have a vertical line drawn in UIView, and the view's width stretches, the line's width will stretch with it. I want to keep the original width, therefore I redraw each time in -layoutSubviews:
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
// ONLY drawing code ...
}
- (void)layoutSubviews
{
[super layoutSubviews];
[self setNeedsDisplay];
}
This works fine, however I don't think this is a efficient approach - unless CGContext drawing is blazing fast.
So is it really fast? Or is there better way to handle view's autoresizing? (CALayer does not support autoresizing on iOS).
UPDATE :
this is going to be a reusable view. And its task is to draw visual representation of data, supplied by the dataSource. So in practice there could really be a lot of drawing. If it is impossible to get this any more optimized, then there's nothing I can do... but I seriously doubt I'm taking the right approach.
It really depends on what you mean by "fast" but in your case the answer is probably "No, CoreGraphics drawing isn't going to give you fantastic performance."
Whenever you draw in drawRect (even if you use CoreGraphics to do it) you're essentially drawing into a bitmap, which backs your view. The bitmap is eventually sent over to the lower level graphics system, but it's a fundamentally slower process than (say) drawing into an OpenGL context.
When you have a view drawing with drawRect it's usually a good idea to imagine that every call to drawRect "creates" a bitmap, so you should minimize the number of times you need to call drawRect.
If all you want is a single vertical line, I would suggest making a simple view with a width of one point, configured to layout in the center of your root view and to stretch vertically. You can color that view by giving it a background color, and it does not need to implement drawRect.
Using views is usually not recommended, and drawing directly is actually preferred, especially when the scene is complex.
If you see your drawing code is taking a considerable toll, steps to optimize drawing further is to minimize drawing, by either only invalidating portions of the view rather than entirely (setNeedsDisplayInRect:) or using tiling to only draw portions.
For instance, when a view is resized, if you only need to draw in the areas where the view has changed, you can monitor and calculate the difference in size between current and previous layout, and only invalidate regions which have changed. Edit: It seems iOS does not allow partial view drawing, so you may need to move your drawing to a CALayer, and use that as the view's layer.
CATiledLayer can also give a possible solution, where you can cache and preload tiles and draw required tiles asynchronously and concurrently.
But before you take drastic measures, test your code in difficult conditions and see if your code is performant enough. Invalidating only updated regions can assist, but it is not always straightforward to limit drawing to a provided rectangle. Tiling adds even more difficulty, as the tiling mechanism requires learning, and elements are drawn on background threads, so concurrency issues also come in play.
Here is an interesting video on the subject of optimizing 2D drawing from Apple WWDC 2012:
https://developer.apple.com/videos/wwdc/2012/?include=506#506

How to animate size of custom drawn text?

I'm working on a educational app involving complex scripts in which I paint parts of different 'letters' different colours. UILabel is out of the question, so I've drilled down into Core Text and am having a surprisingly successful go of painting glyphs in CALayers.
What I haven't managed to do is animate the size of my custom drawn text. Basically I have text on 'tiles' (CALayers) that move around the screen. The moving around is okay, but now I want to zoom in on the ones that users press.
My idea is to try to cache a 'full resolution' tile and then draw it to scale during an animation of an image bounds. So far I've tried to draw and cache and then redraw such a tile in the following way:
UIGraphicsBeginImageContext(CGSizeMake(50, 50));
CGContextRef context = UIGraphicsGetCurrentContext();
//do some drawing...
myTextImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Then in [CALayer drawInContext:(CGContextRef)context],
I call [myTextImage drawAtPoint:CGPointZero].
When I run the app, the console shows <Error>: CGContextDrawImage: invalid context 0x0. Meanwhile I can perfectly while just continue to draw text in context in the same method even after that error is logged.
So I have two questions: (1) Why isn't this working? Should I be using CGBitmap instead?
And more important: (2) Is there a smarter way of solving the overall problem? Maybe storing my text as paths and then somehow getting CAAnimation to draw it at different scales as the bounds of the enclosing CALayer change?
Okay, this is much easier than I thought. Simply draw the text in the drawInContext: of a CALayer inside of a UIView. Then animate the view using the transform property, and the text will shrink or expand as you like.
Just pay attention to scaling so that the text doesn't get blocky. The easiest way to do that is to make sure the transform scale factors do not go above 1. In other words, make the 'default' 1:1 size of your UIView the largest size you ever want to display it.

iOS : need inputs in developing efficient ( performance wise ) drawing app

I have this app using which one can draw basic shapes like rectangle, eclipse, circle, text etc.
I also allow free form drawing, which is stored as set-of-points, on the canvas.
Also a user can resize and move around these objects by operating on the selection handles that appear when an object is selected.
In addition the user should be able to zoom and pan the canvas.
I need some inputs on how to efficiently implement this drawing functionality.
I have following things in mind -
Use UIView's InvalidateRect and drawRect
Have a UIView for the main canvas and for each inserted object - invalidate the correspoding rect and redraw all the objects which intersects that rect in the drawRect function of the UIView.
Have a UIView and use CALayer ?
every one keep mentioning about the CALayer , I dont have much idea on this, before I venture into this I wanted a quick input on whether this route is worth taking.
like, https://developer.apple.com/library/ios/#qa/qa1708/_index.html
Have a UIImageView as canvas and when drawing each object, we do this
i) Draw the object into offscreen CGContext, basically, create a new CGContext by using UIGraphicsBeginImageContext, draw the shape, extract the image out of this CG context and use that as source of UIImageView's image property, but here how do I invalidate only a part of the UIImageView so that only that area gets refreshed.
Could you please suggest what is the best approach?
Is there any other efficient way to get this done?
Thanks.
Using a UIImage is more efficient for rendering multiple objects. But Using a CALayer is more efficient when moving and modifying a single object because you don't have to modify the other objects. So I think the best approach is to use a UIImage for general drawing and a CALayer for the shape that is being modified. In other words:
use a CALayer to draw the shape being added or modified, but don't draw it on the UIImage
use a UIImage to draw the other shapes
But OpenGL is still the most efficient solution, but don't bother with that if you don't have too many objects to draw.
If you want to draw polygons, you'll have to use Quartz framework, and have your drawing methods based on CALayer. It doesn't really matter which view you'll put your CALayers in, UIImageView or UIView. I'll say UIView since you won't be needing UIImageView's properties or methods for drawing.

Resources