Calling setNeedsDisplay:YES on layer-hosting view does not redraw the view
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 setNeedsDisplay
on 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
精彩评论