I have custom NSView class which I am porting from OpenGL to Metal for GPU rendering. A lot of places in the code, earlier we used to call setNeedsDisplayInRect with an invalidated rect on the NSView, which I queried during the call to drawRect for rendering final content to the screen.
For moving to Metal, I have added a CAMetalLayer to my custom NSView class. With the addition of this CAMetalLayer, getRectsBeingDrawn returns NULL.
Are we saying that getRectsBeingDrawn only works for non-layer/non-metal layer backed NSViews?
Moreover, I also see a setNeedsDisplayInRect in CALayer, but I don't see a getter like getRectsBeingDrawn in CALayer.
Should I move to setNeedsDisplayInRect in CALayer for dirty rects in case of layer backed views? But, then again how do I retrieve those dirty rects?
Related
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.
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.
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.
I’m trying to identify custom CALayer subclasses via hittesting.
That works fine with one exception:
My custom CALayers represent text windows and all the data (content, background color, position, ...) are taken from a NSManagedObjects (from CoreData). My CALayer subclass saves it’s related managedObject class in an instance variable.
The Problem is: I can't access this ivar:
When I check like so:
CustomCALayer *customLayer =[self.layer.presentationLayer hitTest:mouseDownPosition];
...the customLayer contains all the attributes, that CALayer has, (like backgroundColor or name) but not my managedObject ivar.
I suspect that this happens because hitTest returns CALayer and not my subclass.
But how can I get around that?
(I also tried to hittest a „hitTestLayer“ sublayer of my layer and then get the layer via hitTestLayer.superlayer. But, well, superlayer also returns CALayer...)
Did you override the custom CALayer's - hitTest method to return CustomCALayer instead of CALayer?
I have a layer-hosting view set up like this in a custom NSView subclass:
[self setLayer:rootLayer];
[self setWantsLayer:YES];
I add all the sublayers to the layer tree after I called setNeedsDisplay on each sublayer. Each layer's content is provided by a drawLayer:inContext method of my layer's delegate.
Here is my problem:
After initializing my view the view gets draw correctly. However, when the model has changed and I call [myCustomView setNeedsDisplay:YES]; from my view controller the drawLayer:inContext is not called.
I am confused now how to update the view:
Do I have to call the setNeedsDisplay method on each CALayer in the layer tree?
Should not the call of setNeedsDisplay:YES on the layer-hosting view itself trigger the redraw of the whole layer tree?
Thanks for your help.
Edit
I have found something in the NSView Class reference
A layer-backed view is a view that is backed by a Core Animation layer. Any drawing done by the view is the cached in the backing layer. You configured a layer-backed view by simply invoking setWantsLayer: with a value of YES. The view class will automatically create the a backing layer for you, and you use the view class’s drawing mechanisms. When using layer-backed views you should never interact directly with the layer.
A layer-hosting view is a view that contains a Core Animation layer that you intend to manipulate directly. You create a layer-hosting view by instantiating an instance of a Core Animation layer class and setting that layer using the view’s setLayer: method. After doing so, you then invoke setWantsLayer: with a value of YES. When using a layer-hosting view you should not rely on the view for drawing, nor should you add subviews to the layer-hosting view.
link to documentation
In my case I have a layer-hosting view. So does that indeed mean that I have to trigger the redraw manually? Should I implement a pseudo drawRect method in the custom NSView to call the appropriate setNeedsDisplay on the CALayers that changed?
After further research in Apple's sample code of a kiosk-style menu I found out that if you are using a layer-hosting view, you have to take care of the screen updates which are neccessary due to model changes yourself. Calling setNeedsDisplay:YES on the NSView will not do anything.
So what one has to do if one has to update a view one should write a method like reloadData and in it one should call setNeedsDisplayon each CALayer that needs a refresh. I am still not sure if a call to this method on the root layer will propagate through all the children layers but I do not think so.
I solved the problem now by calling setNeedsDisplay on the individual CALayers that needed recaching. It works without problems.
There is also an oft-used practice of having an empty "drawrect", a la -(void) drawRect:(NSRect)dirtyRect {} to help coerce things into drawing, i believe via good ole view.needsDisplay = YES;.
and it should be noted.. that what is indeed happening is that - by saying your NSView *view; is layer.delegate = view; causes the layer to be drawn when [layer setNeedsDisplay]; is called.... via - (void) drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx {...}..
along the same vein... when saying layer.layoutManager = view... subsequent demands that [layer setNeedsLayout]; will be fulfilled only when the - (void) layoutSublayersOfLayer:(CALayer *)layer {..} method is implemented..
These vital concepts are glossed over / strewn about in Apple's docs... and they are really so pivotal to making absolutely anything work at all.
You can automatically delegate the setNeedsDispay: by changing the redraw policy of the view. You have to assign NSViewLayerContentsRedrawOnSetNeedsDisplay to the property layerContentsRedrawPolicy (see https://developer.apple.com/documentation/appkit/nsview/1483514-layercontentsredrawpolicy). This will trigger redrawing of the layer when you send setNeedsDisplay: to the view:
[self setLayer:rootLayer];
[self setWantsLayer:YES];
self.layerContentsRedrawPolicy = NSViewLayerContentsRedrawOnSetNeedsDisplay;
or in Swift:
layer = rootLayer
wantsLayer = true
layerContentsRedrawPolicy = .onSetNeedsDisplay