Most efficient way for partially drawing an UIView in drawRect: - iphone

I'm using setNeedsDisplayInRect: as much as possible in my UIView subclass, but I don't know what to do in drawRect:. What is the best way to detect which parts of the UIView have to be drawn? Right now I've divided my UIView into several CGRect's. For each CGRect I'm calling CGRectContainsRect() to check whether that CGRect needs to be drawn or not. Is that the correct way, or is there a better way?

You'll want to draw any rect that is even partially in the invalidated rect, so you'll want to use CGRectIntersectsRect(). Having said that, I've seen few cases where it makes sense to partially draw UIViews -- unless you have a very large single view inside of a scroll view, you may as well draw the whole thing.

You're looking at a classic spatial partitioning task. Depending on your view complexity, you can use different strategies to find out what needs to be drawn. If your scene is very simple, then drawing everything or partitioning the scene into discrete areas and doing your bounding rectangle check is fine. For very complex scenes, there are several good data structures (such as octrees) for representing your scene as a tree, and performing spacial queries for objects within a given region.
Check out the topic of Spacial Partitioning on Wikipedia: http://en.wikipedia.org/wiki/Space_partitioning

Related

How to detect if the touch point is contained by draw content of CALayer?

There are three layers added to UIView. One layer draws a rectangle. One draws a circle. One draws a polygon. The layer's opacity is no. When I touched the polygon, I want to get the correct layer which draws the polygon. And the three layers are full filled to the view. I have implemented this. But I don't know if we have better solution to solve it .My way is like this:
1.Drawing the content using -drawLayer:inContext. store the CGPath that you used.
2.In the UIView's -touchedEnded:withEvent method. using CGPathContainsPoint() to detect if the touch point is contained by the CGPath.
Maybe this is the stupid way to solve this. Anyone who can tell me how to solve it better?
If you need an accurate hit test for path's I'm afraid you have to check/iterate the layer hierarchy yourself if the point is inside your path using CGPathContainsPoint as you suggested.
While iterating you could optimize it by skipping layers where the point is outside their frame.
For less fine grained control you can get the touched layer by using CALayers
- (CALayer *)hitTest:(CGPoint)thePoint
method.
If you have a layer hierarchy with a nesting level < 1000 (which is almost always true) I would not worry too much.

Improving drawing performance on custom UIView

I have a custom UIView which is composed of many images, their positions are changing in response to the user touch.
The view must track the user touch and i'm experiencing a performance bottleneck in the drawing of such view, preventing me to follow the input in realtime.
At the beginning i was drawing everything in the [UIView drawRect:] method and of course it was way too slow because everything was redrawn even if not necessary.
Then, i used more CALayers to update only the layer that was changing and this gave me much better responsiveness.
But still, when i have to draw the same image many times on a layer it takes up to 500ms.
Since the images are placed at fixed positions it there a way to pre-draw them? Should i consider putting them in many CALayers and just hide/show them?
Also, i don't understand why a [CALayer setNeedsDisplayInRect:] exists but the delegate has (apparently) no way to know what the invalid rect is to optimize the drawing.
Solution
Following the advice in the answer I finally created many CALayers for the images and set the contents property the first time the layer was being shown. This is a lazy-loading compromise: in a first attempt i set the contents of every layer at the creation time but this caused to pre-draw any possible image on the program launch, freezing the application for seconds.
From the documentation for -[CALayer drawInContext:]:
Default implementation does nothing. The context may be clipped to protect valid layer content. Subclasses that wish to find the actual region to draw can call CGContextGetClipBoundingBox. Called by the display method when the contents property is being updated.
The default implementation of display calls drawInContext: on an automatically-created context; presumably setting the bounding box as well (which is presumably passed to drawRect:).
If you're drawing several static images, I'd just stick each one in its own UIView; I don't think the overhead is that big (if it is, the CALayer overhead should be smaller). If they all animate, I'd definitely use UIView/CALayer. If some of them don't animate (much) and you notice significant slowness, you can pre-render those. It's a trade-off between rendering in drawRect: (or similar) and layer compositing on the GPU, but in general I'd assume that the latter is much faster.

The best way to implement drawing features like Keynote

I'm trying to make a little iPad tool's for drawing simple geometrical objects (rect, rounded rect, ellipse, star, ...).
My goal is to make something very close to Keynote (drawing feature), i.e. let the user add a rect (for instance), resizing it and moving it. I want too the user can select many objects and move them together.
I've thought about at least 3 differents ways to do that :
Extends UIView for each object type, a class for Rect, another for Ellipse, ... With custom drawing method. Then add this view as subview of the global view.
Extends CALayer for each object type, a class for Rect, another for Ellipse, ... With custom drawing method. Then add this layer as sublayer of the global view layer's.
Extends NSObject for each object type, a class for Rect, another for Ellipse, ... With just a drawing method which will get as argument a CGContext and a Rect and draw directly the form in it. Those methods will be called by the drawing method of the global view.
I'm aware that the two first ways come with functions to detect touch on each object, to add easily shadows,... but I'm afraid that they are a little too heavy ? That's why I thought about the last way, which it seems to be straight forward.
Which way will be the more efficient ??? Or maybe I didn't thought another way ?
Any help will be appreciated ;-)
Thanks.
I'd use the UIKit classes to do your drawing, then profile and optimise your code from there.
Apple/iPad info: link text
My first feeling was to make the 3rd way, but to be convinced, just after I've posted my message, I did some tests with just a global view and over 200 geometrical forms (Rect, Rounded Rect and Ellipse) on it and I move only a half with touchMoved event. I did this test with the way 1 (Subclassing UIView) and the way 3 (Subclassing NSObject), the way 2 seems to me too restrictive and not help me at all.
The resuslt is that the way 1 seems to be more efficient... There is no lag when I move 60 objects together ! Moreover using this way would probably help me because using view comes which some interesting functions like touch detection on complex path (see UIBezierPath), object hierarchy handled by the UIView classe...
So I will use that way and come back here to share my results ;-)
Regards
It's better to use CGLayer objects. The benefits are:
It's much faster and more memory efficient. For simple objects, adding a view is much more expensive and complicates the view hierarchy. For complicated objects, the caching done on CGLayers can boost performance.
It's easy to group objects together. you just put everything in a new layer, and voila! There's almost no overhead.
Using CGLayer and other Quartz objects gives you a lot more flexibility.
The only drawback is that you have to directly use Quartz 2D. It's not really difficult, but needs some learning if you haven't used it before.
CAShapeLayer pretty much handles your option 2. It does rect and rounded rect (see cornerRadius) by default, or you can give it a path for any arbitrary shape. For your option 1, you can use a CAShapeLayer with a UIView instead of implementing drawRect and it may be faster.

implementing stretchable dialog borders in iphone sdk

I want to implement dialog borders that scale to the size I require the dialog to be. Perhaps there is a better more conventional name for this sort of thing. If there is, if someone would edit the title, that'd be great.
Anyhow, I'd like to do this so I can have dialogs of any size without the visual artifacts that come with scaling border art to small, large, or wacky unproportional dimentions. I have a few ideas on how this is done, but am not sure which is better for iphone. I have a few questions.
1) Should I make a containing view object that basically overloads its drawRect method and draws the images where they should be at their appropriate scale when the method is called, or should I main a containing view object that simply contains 8 UIImageViews? I suspect the latter approach won't work if I need to actively scale the resulting dialog class like in an animation.
1b) If overloading drawRect is the way to go, does someone have some sample code or a link to an example that demonstrates drawing an image directly from drawRect()?
2) Is it generally better to create
a) a 3 x 3 image where the segments are in their appropriate 1x1 grid of the image? If so, is it simple to draw from a portion of this image onto my target view in drawRect (if the former assumption is correct that I should use drawRect)?
b) The pieces separately in 8 different files?
UPDATE:
To clarify, the idea is to take any customized border art and be able to stretch the 2nd, 4th, 6th, and 8th cell (in a 3x3-cell grid) to form a border of any size with just those assets. Stretching just a plain image would result in distortion of the corners, so I'd like to stretch those even numbered cells as needed and tack on the corners so there is no distortion. I'd seen this done before so thought it might be a standard thing and have a standard naming to it other than what I called it.
Anyhow, I was advised that adding 8 UIImageViews to a container would not be as efficient as drawing the UIImages on the fly in drawRect so took that approach using CGContextDrawImage() after applying the necessary transformations to the context to translate and scale the Y. Because this function draws from the bottom left corner of an image but onto a top-left origined UIView, the image is upside down without the Y axis invert. I noticed the suggestion to use UIImage functions like drawAtPoint works as well and similarly but for the invert since UIImage draws in the same orientation as UIViews. I will continue my implementation with the former and see how it goes, but one other question.
Would someone happen to know which of these approaches is more efficeint, faster, etc?
I'm not sure I follow, but here's my best shot at an answer...
Using drawRect: or adding individual UIImageViews to a parent view is entirely up to you. UIImageView gives you a bit of encapsulated functionality for free, but otherwise they are the same as far as appearances go.
If you do want to go the drawRect route, you just need to use UIImage's drawAtPoint: method. Do the math for where you want it to be, and draw it. You can calculate your points based on the parent view's dimensions.
As far as scaling, it's impossible to resize these images without scaling them, so I'd plan ahead and make your originals as large or larger than you ever expect to display them.
Hope that helps a little?
Cheers
If you want a border on a dialog box, assuming the box is a UIView (or subclass), then set the layer's border properties and let the system draw the border for you.
#import <QuartzCore/QuartzCore.h>
// ...
view.layer.borderWidth = 2;
view.layer.borderColor = [UIColor whiteColor].CGColor;
view.layer.cornerRadius = 0; // 0=square corners, >0 for rounded

How do I use CALayer with the iPhone?

Currently, I have a UIView subclass that "stamps" a single 2px by 2px CGLayerRef across the screen, up to 160 x 240 times.
I currently animate this by moving the UIView "up" the screen 2 pixels (actually, a UIImageView) and then drawing the next "row".
Would using multiple CALayer layers speed up performance of rendering this animation?
Are there tutorials, sample applications or code snippets for use of CALayer with the iPhone SDK?
The reason I ask is that most of the code snippets I find that demonstrate simple examples of CALayer employ method calls that do not work with the iPhone SDK. I appreciate any advice or pointers.
Okay, well, if you want something that has some good examples of CA good that draws things like that and works on the phone, I recommend the GeekGameBoard code that Jens Aflke published (it is an improved version of some Apple demo code).
Based on what you are describing I think you are doing somthing way more complicated than it needs be. My impression is you want basically a static view that you are animating by shifting its position so that it is partially off screen. If you just need to set some static content in your drawRect going through layers is not going to be faster than just calling CGFillRect() with your color. After that you could just use implicit animations and the animator proxy on UIView to move the view. I suspect you could even get rid of the custom drawRect: implementation with a patterned UIColor, but I honestly have not benchmarked the difference between the two.
What CALayer methods are you seeing that don't work on iPhone? Aside from animation features tied to CoreImage I have not noticed much that is missing. The big thing you are likely to notice is that all views are layer backed (so you do not need to do anything special to use layers, you can just grab a UIView's layer through the layer accessors methos), and the coordinate system has a top left origin.
In any event, generally having more things is slower than having fewer things. If you are just repeating the same pattern over and over again you are likely to find the best performance is implementing a custom UIView/CALayer/UIColor that knows how to draw what you want, rather than placing visually identical layers or views next to each other.
Having said that, generally layers are lighter weight than views, so if you have a lot of separate elements that you need to keep logically separated you will find that moving to layers can be a win over using views.
You might want to look at -[UIColor initWithPatternImage:] depending on exactly what you are trying to do. If you are using this two pixel pattern as a background color you could just make a UIColor that draws it and set the background.
What CALayer methods are you seeing that don't work on iPhone?
As one example, I tried implementing the grid demo here, without much luck. It looks like CAConstraintLayoutManager and CAConstraint are not available in QuartzCore.h.
In another attempt, I tried a very simple, small 20x20 CALayer object as a sublayer of my UIView's layer property, but that didn't show up.
Right now, I have a custom UIView of which I override the drawRect method. In drawRect I grab a context and render two types of CGLayerRefs:
At "off" cells I draw the background color across the entire 320x480 canvas.
At "on" cells, I either draw a single CGLayerRef across a grid of 320x480 pixels (initialization) or across a 320x2 row (animation).
During animation, I make a UIImageView clip view from 320x478 pixels, and draw a single row. This "pushes" my bitmap up the screen two pixels at a time.
Basically, I'd like to test whether or not using CALayer will accomplish two things:
Make my rendering faster, if CALayer has less overhead than what I'm doing now
Make my animation smoother, by letting me transition a layer up the screen smoothly
Unfortunately, I can't seem to get a basic CALayer working at the moment, and haven't found a good chunk of sample code to look at and play with.