drawRect passes in a rectangle which represents the area of your view to re-draw. The Apple Docs say:
Your implementation of this method should redraw the specified area of
the view as quickly as possible
What is not clear to me is how you should actually proceed to use this rectangle that is passed in. Suppose your view is made up of a single Bezier Curve and drawRect asks you draw only part of that curve. Well, even this simple example is non-trivial since you must break up the curve to find the part that lies only within the rectangle.
In nearly all the example code I see for drawRect the actual argument to the method is entirely ignored. So perhaps the typical case is to just ignore this argument? What happens if you draw based on the entire view's bounds in mind, but you instead only receives a small portion of that view to this method?
Related
I'm on Yosemite 10.10.5 and Xcode 7, using Swift to make a game targeting iOS 8 and above.
EDIT: More details that might be useful: This is a 2D puzzle/arcade game where the player moves stones around to match them up. There is no 3D rendering at all. Drawing is already too slow and I haven't even gotten to explosions with debris yet. There is also a level fade-in, very concerning. But this is all on the simulator so far. I don't yet have an actual iPhone to test with yet and I'm betting the actual device will be at least a little faster.
I have my own Draw2D class, which is a type of UIView, set up as in this tutorial. I have a single NSTimer which initiates the following chain of calls in Draw2D:
[setNeedsDisplay]; // which calls drawRect, which is the master draw function of Draw2D
drawRect(rect: CGRect)
{
scr_step(); // the master update function, which loops thru all objects and calls their individual update functions. I put it here so that updating and drawing are always in sync
CNT = UIGraphicsGetCurrentContext(); // get the curret drawing context
switch (Realm) // based on what realm im in, call the draw function for that realm
{
case rlm.intro: scr_draw_intro();
case rlm.mm: scr_draw_mm();
case rlm.level: scr_draw_level(); // this in particular loops thru all objects and calls their individual draw functions
default: return;
}
var i = AARR.count - 1; // loop thru my own animation objects and draw them too, note it's iterating backwards because sometimes they destroy themselves
while (i >= 0)
{
let A = AARR[i];
A.scr_draw();
i -= 1;
}
}
And all the drawing works fine, but slow.
The problem is now I want to optimize drawing. I want to draw only in the dirty rectangles that need drawing, not the whole screen, which is what setNeedsDisplay is doing.
I could not find any tutorials or good example code for this. The closest I found was apple's documentation here, but it does not explain, among other things, how to get a list of all dirty rectangles so far. It does not also explicitly state if the list of dirty rectangles is automatically cleared at the end of each call to drawRect?
It also does not explain if I have to manually clip all drawing based on the rectangles. I found conflicting info about that around the web, apparently different iOS versions do it differently. In particular, if I'm gonna hafta manually clip things then I don't see the point of apple's core function in the first place. I could just maintain my own list of rectangles and manually compare each drawing destination rectangle to the dirty rectangle to see if I should draw anything. That would be a huge pain, however, because I have a background picture in each level and I would hafta draw a piece of it behind every moving object. What I'm really hoping for is the proper way to use setNeedsDisplayInRect to let the core framework do automatic clipping for everything that gets drawn on the next draw cycle, so that it automatically draws only that piece of the background plus the moving object on top.
So I tried some experiments: First in my array of stones:
func scr_draw_stone()
{
// the following 3 lines are new, I added them to try to draw in only dirty rectangles
if (xvp != xv || yvp != yv) // if the stone's coordinates have changed from its previous coordinates
{
MyD.setNeedsDisplayInRect(CGRectMake(x, y, MyD.swc, MyD.shc)); // MyD.swc is Draw2D's current square width in points, maintained to softcode things for different screen sizes.
}
MyD.img_stone?.drawInRect(CGRectMake(x, y, MyD.swc, MyD.shc)); // draw the plain stone
img?.drawInRect(CGRectMake(x, y, MyD.swc, MyD.shc)); // draw the stone's icon
}
This did not seem to change anything. Things were drawing just as slow as before. So then I put it in brackets:
[MyD.setNeedsDisplayInRect(CGRectMake(x, y, MyD.swc, MyD.shc))];
I have no idea what the brackets do, but my original setNeedsDisplay was in brackets just like they said to do in the tutorial. So I tried it in my stone object, but it had no effect either.
So what do I need to do to make setNeedsDisplayInRect work properly?
Right now, I suspect there's some conditional check I need in my master draw function, something like:
if (ListOfDirtyRectangles.count == 0)
{
[setNeedsDisplay]; // just redraw the whole view
}
else
{
[setNeedsDisplayInRect(ListOfDirtyRecangles)];
}
However I don't know the name of the built-in list of dirty rectangles. I found this saying the method name is getRectsBeingDrawn, but that is for Mac OSX. It doesn't exist in iOS.
Can anyone help me out? Am I on the right track with this? I'm still fairly new to Macs and iOS.
You should really avoid overriding drawRect if at all possible. Existing view/technologies take advantage of any hardware capabilities to make things a lot faster than manually drawing in a graphics context could, including buffering the contents of views, using the GPU, etc. This is repeated many times in the "View Programming Guide for iOS".
If you have a background and other objects on top of that, you should probably use separate views or layers for those rather than redraw them.
You may also consider technologies such as SpriteKit, SceneKit, OpenGL ES, etc.
Beyond that, I'm not quite sure I understand your question. When you call setNeedsDisplayInRect, it will add that rect to those that need to be redrawn (possibly merging with rectangles that are already in the list). drawRect: will then be called a bit later to draw those rectangles one at a time.
The whole point of the setNeedsDisplayInRect / drawRect: separation is to make sure multiple requests to redraw a given part of the view are merged together, and drawing only happens once per redraw cycle.
You should not call your scr_step method in drawRect:, as it may be called multiple times in a cycle redraw cycle. This is clearly stated in the "View Programming Guide for iOS" (emphasis mine):
The implementation of your drawRect: method should do exactly one
thing: draw your content. This method is not the place to be updating
your application’s data structures or performing any tasks not related
to drawing. It should configure the drawing environment, draw your
content, and exit as quickly as possible. And if your drawRect: method
might be called frequently, you should do everything you can to
optimize your drawing code and draw as little as possible each time
the method is called.
Regarding clipping, the documentation of drawRect states that:
You should limit any drawing to the rectangle specified in the rect
parameter. In addition, if the opaque property of your view is set to
YES, your drawRect: method must totally fill the specified rectangle
with opaque content.
Not having any idea what your view shows, what the various method you call do, what actually takes time, it's difficult to provide much more insight into what you could do. Provide more details into your actual needs, and we may be able to help.
My app already has the drawing enabled for the user on the screen. How can I define certain points in the screen and detect when the user draws through that point? Read about a few swift methods but can't quite grasp if they are applicable for what I need, also I can't find any "collision" methods.
You can use the .containspoint method. However I would recommend you to use a rectangle and not a point. It`s very difficult to draw over one certain point. So you could use CGRect for a rectangle and then again .containspoint(from the user touched point).
That is, if
[self setNeedsDisplayInRect:rect];
is called, and rect is very carefully calculated for the region that needs to be redrawn, but if our drawRect code doesn't care about rect and draw everything anyway, can the iOS system still somehow improve the drawing speed? (or possibly improve very little?) This question probably requires somebody who is very familiar with UIKit/CoreGraphics.
There are a few ways the answer could be yes:
You can clip to the rectangle, in which case anything outside it won't be painted, even if you draw in it. Drawing outside the rectangle won't be free, but it will be cheaper. iOS can't do this for you because you might deliberately ignore the rect, or use the rect but also draw something else elsewhere in your bounds unconditionally. (Though that other thing should probably be another view.)
Even if your current drawRect: doesn't use the rectangle, you might go back to that code later to optimize it. As you're probably aware, one very good way to do that—if it's at all possible—is to use the rectangle to decide what you draw. Even if you're not doing that now, you may do it in the future, and specifying changed rects now means that many fewer things to change then.
A corollary to #3 is that even if what you're drawing now can't be so optimized, you may decide in a future major version to completely change what the view draws to something that can. Again, specifying changed rects now means that many fewer things to do in the future.
Subviews. If your view doesn't actually draw some of the things that the user sees in it, but rather delegates (not in the Cocoa/Cocoa Touch sense) those things to subviews, then you might override setNeedsDisplayInRect: to send setNeedsDisplay: messages to subviews—and only the subviews whose frames intersect the rect—before calling super. (And UIView's implementation might already do this. You should test it.)
If your drawRect: implementation ignores the rect passed in and draws the entire view, any optimization of the rect passed to setNeedsDisplayInRect: is for nought.
The "needs display" rect is passed straight through; the only thing it's there for is for the drawRect: implementation to use for ignoring unnecessary drawing. Once your drawRect: implementation is entered, the system can't tell whether your drawing outside the passed rect is intentional, so all the drawing really happens (performance implications included).
Depending on what you're drawing and how, it's not too difficult to restrict your drawRect: implementation to at least make some use of the rect passed in. Everything you want to draw has a bounding rect, whether it's a chunk of text or a bezier path or an image or just a rect you're filling with some color. Surround each bit to drawing with a CGRectIntersectsRect() test -- you won't completely restrict your drawing to the passed in rect that way, but you'll at least eliminate anything that doesn't touch that rect from needing to be drawn.
I'm wondering whether I need to check if something is within the bounds of the CGRect passed to drawRect:, or if drawRect: automatically handles that for me.
For example, assume that I have 10 UIBezierPaths on the screen. Each curve is in an NSMutableArray named curves. Every time drawRect: is called, it loops through this array and draws the curves it finds there. If the use moves one curve, I find its containing CGRect and call [self setNeedsDisplayInRect:containingRect]. In my drawRect: implementation, do I need to personally check whether each of the UIBezierPaths falls within the CGRect passed to drawRect: (using CGRectIntersectsRect), or is that handled automatically?
This falls into a class of optimizations you'll have to make yourself if you think it's necessary after profiling.
UIKit isn't that smart unfortunately. Though it would probably be too slow if it was!
Being an absolutely beginner on programming the Ipad I have gotten to the point where i can place various controls on a view and interact with them.
What I like to do is to have a defined area on to which I can do some vector drawing. (horisontal/vertical lines etc and tiny graphic objects).
The "area" should be rectangular like a component with a border etc with a size and position.
I somewhat assume I need a separate view for this and have seen som examples of a view with a drawRect where functions like CGContextAddlineToPoint are called.
The problem I have is where/when and how to load this view ?
For example I may want to press a button on the screen to ask a webservice for some data which on return should produce som lines on this graphic area.
I have already some some response functions for buttonclick in my viewcontroller.
If somebody could help me to move on I would be grateful.
regards
martin
Yes, you need a custom view class for that and override the drawRect: method for your drawing.
You can use CoreGraphics (the C functions that have the CG prefix, like the CGContextAddLineToPoint function). To get the CoreGraphics context, use UIGraphicsGetCurrentContext.
But I recommend to read about UIBezierPath (also search for UIBezierPath here on StackOverflow and on Google to find lots of examples). There's also a guide from Apple.