Efficient drawing in NSView drawRect - macos

I have an application I'm writing in which I'm drawing into an NSView. In mouseDown I am saving the location in my data model and then drawing a graphic at that location within the drawRect method of the view. It all works fine.
At the end of my mouseDown I was calling [self setNeedsDisplay:YES]; to force a redraw. The only thing is that the dirtyRect is always the full size of the view. I wanted to optimize this as much as possible so that I'm not redrawing the entire window for just a few changed pixels.
So now I'm calling [self drawRect:...] instead and specifying the rectangle.
Now in my drawRect I am comparing every graphic I have to see if it falls in the dirtyRect. It seems like I've traded work of drawing for work of bounds checking everything. I'm not sure I've made it any more or less efficient.
So what is the standard practice? Is it common to just redraw everything in the view and ignore the dirtyRect? Is there a nice function I can use as a test to see whether my object is in the dirtyRect?

You should never call -drawRect: yourself if you're trying to draw to the screen. Let AppKit call it for you. What you should do is call -setNeedsDisplayInRect: at the end of your -mouseDown:.
Then, in -drawRect:, only draw stuff contained in the dirtyRect. You can test to see if a point is inside the dirtyRect with NSPointInRect(). There are lots of other useful functions for working with NSRect. See the documentation for the point functions and the rectangle functions.

Related

Layer-backed NSView performance with rendering directly in CALayer.drawInContext:

I have many layer-backed views contained in a NSScrollview and am predominantly concerned with scrolling performance. The documentView and higher are layer-backed hence their subviews are also layer-backed.
The following are the three natural places that display/rendering code could go:
override NSView.wantsUpdateLayer to return false (or don't do anything because this is the default) & do drawing in NSView's drawRect method
override NSView.wantsUpdateLayer to return true & do drawing in NSView's updateLayer method
do NO drawing in NSView at all & perform all drawing in CALayer's drawInContext method
From the WWDC 2013 Session 215 talk it was stated that (2) is more performant than (1) because the view then doesn't need to create a temporary store for the drawRect output. Firstly, I don't have 100% clarity on when "backing stores" are made and when not and Secondly how do (2) and (3) compare and when might you use one over the other?
In particular, I have to draw text into my view. How would I go about doing that in the updateLayer call? The only examples of drawing text seem to need to get hold of a context - which isn't naturally available in updateLayer.

Click on NSBezierPath

I'm drawing a NSBezierPath line on my NSImageView. I'm creating NSBezierPath object, setting moveToPoint, setting lineToPoint, setting setLineWidth: and after that in drawRect of my NSImageView subclass I'm calling [myNSBezierPath stroke]. It all works just like I want, but I can't seem to use containsPoint: method... I tried implementing
if([myNSBezierPath containsPoint:[theEvent locationInWindow]]{
//do something
}
in -(void)mouseUp:(NSEvent*)theEvent of my NSImageView subclass but it's never reacting and I'm sure I'm hitting that line... Am I doing something wrong? I just need to detect if NSBezierPath is being clicked.
Cheers.
Make sure to transform the mouse click location into the coordinate system of your image view subclass, not into the one of the window (unless they are the same). Your bezier path doesn't know about the offset it's drawn into, so you have to take that into account when performing a hit test.
Also, from the containsPoint: documentation:
This method checks the point against the path itself and the area it
encloses. When determining hits in the enclosed area, this method uses
the non-zero winding rule (NSNonZeroWindingRule). It does not take
into account the line width used to stroke the path.
Emphasis mine.

Resize custom NSView inside NSScrollView by setFrameSize triggers drawRect

So I have this simple custom NSView-inside-NSScrollView setup, constructed in awakeFromNib programmatically, exactly like in Apple's ScrollView Programming guide.
The custom view (documentView of NSScrollView) initially has a certain frame-size and the scroll-position remains at the origin of the custom-view. No user-initiated and no programmatic scrolling is going on.
Now, from a different thread, by issueing a performSelectorOnMainThread, I change the framesize of the custom-view by invoking setFrameSize(newSize) on the custom view, only making it bigger in height. Origin and width stay the same.
The question now is, why am I getting the drawRect: invoked, presumably by cocoa, on the custom view with a dirty rect of (0, 0, viewwidth, viewheight) ?
I think this is strange, at least it is not optimal because there is a redraw-request in an area where nothing has changed. The height was extended from 10000 to 10300 for example, which is not relevant to the rect being shown at that moment in the visible rect (0, 0, viewwidth, viewheight).
From the class-reference of NSView, I was assuming that setFrameSize: does not cause drawRect: to be triggered, as long as you don't issue a setNeedsDrawInRect: or similar.
So, does anyone know why, in the case of being contained in a NSScrollView, this drawRect: call to the contained view is made by Cocoa ?
And which object (NSScrollView, NSClipView, NSNotificationCenter...) is issueing this call ?
Any hint greatly appreciated,
Joerg

How do I prevent a CALayer from redrawing as its bound change?

i have a CALayer with a custom draw method I've added to my view's base layer. I set needsDisplayOnBoundsChange to NO. However, when I resize the parent view's frame, the layer's drawInContext: is getting called continuously. I'd like the contents to scale while the resize is occurring. Any clues?
Interesting, I have a case where I have a CALayer that correctly scales its contents until I call setNeedsDisplay on it to redraw its contents. One thing that may be different is that in my case the layer is being drawn by its delegate and not by a subclass of CALayer. Another thing that may be different is that this is on iOS and not OSX (I don't know which you are using in this case). It is possible that there could be behavior differences between subclasses and delegate drawn layers and/or iOS and OSX.
Another thing to note is that needsDisplayOnBoundsChange is documented to be NO by default, so one should not need to set it. I am not specifically setting needsDisplayOnBoundsChange on my layer.
You could try using a delegate to do the drawing to see if that makes a difference. Note that a UIView cannot be a delegate to a CALayer. In my case I made a simple delegate object that forwards the draw call to my view.

Core animation code structure/conventions

In learning Core Animation, I learned very quickly that if you don't do it right, you get really weird undefined behavior. To that end, I have a few questions that will help me conceptually understand it better.
My NSView subclass declares the following in it's init. This view is a subview of normal layer backed view.
[self setLayer:[CALayer layer]];
[self setWantsLayer:NO];
After this, when and in what situations should I refer to self as opposed to [self layer]? I have been ONLY manipulating the layer with explicit and implicit animations, staying away from [self setFrame:] etc. and using [[self layer] setPosition] etc.
The problem with this approach is that the actual frame of the view stays in one spot throughout any and all animations applied. What if my view is supposed to recieve mouse events? For example, I have a view that uses core animation and it is dragged around by the mouse. Is there a way I can somehow keep the view frame synced with the current state of the presentation layer so I can receive proper mouse events?
About the presentation layer, apparently it's only available when an actual animation is in progress. Is there any sort of property of the layer that can tell me where it's ACTUALLY visually at even when an animation's not in progress?
I think you need to re-phrase your question a little. It seems there is some underlying misunderstanding, but you're not really expressing it very clearly. You're question title suggests you're looking to understand something more theoretical, but your actual question suggests you're looking for something more concrete. Let me see if I can clarify a few things.
The presentationLayer provides information about the layer's current state while "in-flight".
When there is no animation occurring, the presentationLayer and the layer information will be identical. Query the layer's bounds, frame, or position to find out where it is currently in its parents coordinate space.
NSViews must have layer backing enabled to be able to perform animations.
Make sure you're not just animating with an explicit animation and not actually setting the layer value that you're animating. Animations don't automatically change the properties of the layers they're animating. You have to change the property to the ending value yourself or it will just "snap back" to the starting value.
If you want to animate the view, as opposed to a layer, you can use the animator proxy, like [[view animator] setFrame:newFrame];
Wrap calls to the animator in a CATrasaction to alter things like animation duration.
Let me know if you need some clarification by updating your question. Providing some pertinent code would really help identify the problems you're having trouble solving.
Firstly, you want to use [self setWantsLayer: YES]. Also, it's only important to call -setLayer: before -setWantsLayer: if you want to provide a specific CALayer subclass (such as a CAScrollLayer); if you just want a regular CALayer you just call -setWantsLayer: and it'll be created for you. Even better, just check the 'wants layer' option in Interface Builder.
Secondly, the entire point of using a layer-backed view is that you can continue to use the regular NSView methods and get the free CoreAnimation 'tweening' effects. If you want to use CoreAnimation as your only means of moving items around, then the correct way to do so is to create a layer backed view which contains your pure-CALayer presentation hierarchy.
I've not looked at any freely-available CoreAnimation tutorials, but I can definitely recommend the Pragmatic Programmers' book on the subject. They also have a screencast available by the book's author.

Resources