Using CALayer as a background for other NSViews - cocoa

I have an application with an NSTableView in a window. I want to use a CALayer as the background for the entire window, and the table view. In all my my experiments so far, the CALayer always draws over the NSTableView, which is not the effect I'm looking for. Is there a way to make this work, or am I simply out of luck due to the nature of layer-hosting views vs NSViews?
My test setup is a window with the usual NSScrollView/NSTableView combo, and a sibling NSView behind it in the view order. The NSView is set to be layer-hosting with my custom layer within it (just a layer with a backgroundColor set). I've experimented with setting the window's content view to be layer-backed, as well as the table view itself, as well as wrapping the NSScrollView in a layer-backed NSView. The result is always the same.
Thanks for any insight you might be able to provide.

It is simple. all overlapping views or layers should be layer backing or layer hosting for correct ordering.
you can set [tableview setWantsLayer:YES]
or simply check it in the layers tab when editing the interface.

Related

SetNeedsDisplay on NSView triggers the redrawing of the whole views hierarchy

I'm working on an app made by a NSWindow which own a lot of custom subviews, that could be opaque or not.
Whenever I call SetNeedsDisplay: or SetNeedsDisplayInRect: on a subview, the system calls the drawRect of each single subview starting from the content view of the parent NSWindows.
How can it be avoided? How can I redraw just the dirty subview (it should be the default behaviour)? Is there something that I'm missing maybe in subclassing the NSView? Or in setting the properties or the syle of the parent NSWindow?
Thanks
The that could be opaque or not is the trouble-some bit. Any non-opaque view triggers a redraw of the entire view hierarchy, because the window must restore that views background to a pristine state. Only views set to opaque may not require anything else below them to be redrawn. They might still trigger redraws "above" though, if the opaque view itself is partially covered by other views.
Ok, I think I’ve figured it out. It seems that turning all the subviews into layer-backed views did the trick.
And this is reasonable giving the way the layers are managed by the gpu and how the layer compositing is performed.
But I still don't understand why, using the "classic" NSViews, no matter if they are opaque or not, siblings or children, overlapped or not , I cannot invalidate a single view without the system calls the re-drawing of the entire view hierarchy of the window

Cocoa: How to properly hide other views below NSImageView?

I have a view with several subviews (NSButton, NSTextField, NSPopUpButton) and a NSImageView with a spinner icon which should be displayed on top of the other views while data is retrieved from the web.
To display the NSImageView on top I have set
imageViewSpinner.wantsLayer = true
imageViewSpinner.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor
The problem is, that the focus borders and PopUpButtons are still accessible/shining through the NSImageView (see attached video).
To solve this, I could iterate over all the other subviews and set them to "isHidden" or "disabled" but I wonder, if there is a cleaner solution to this problem, for example defining the NSImageView as topmost layer without things getting through?
You could put all of the other views inside of one container view and hide that.
You can also use a tab-less tab view to programmatically switch between view sub-hierarchies. (That basically achieves the same thing. It's better when there are more than 2 views to manage.)

NSVisualEffectView disappears when using a layer-backed view

My goal
I would like to add a vibrancy effect to a collection view.
The setup
I have the following view hierarchy:
When I use this layout, the background has the vibrancy effect as expected, but the performance is not quite there.
NSCollectionView is optimized to work with layer-backed views, so I enable the CALayer on the collection view's enclosing scroll view.
The problem
If I do this, the visual effect view is no longer visible, and the collection view has a white background.
My question
Is there any way to make a layer-backed view work together with NSVisualEffectView?
So if anyone's wondering, here's the trick:
adding the NSVisualEffectView: correct
making the enclosing scroll view layer-backed: correct
enabling the scroll view to draw a background: wrong
Make sure to either set drawsBackground to false, or disable it in Interface Builder, and your collection view will have the vibrant background, and fast scrolling as well 👍

Drawing an NSTableView's background outside of its bounds

I'm having a problem since Lion introduced elastic scrolling (pictured below). When you scroll my table view (cell-based, with alternating row colors) beyond its bounds, the background doesn't draw. I've tried doing my own drawing in -[drawBackgroundInClipRect:], but it seems like you can't exceed the bounds of the table view. How can I extend the background into elastic scrolling territory?
In Answer to Your Question
A view drawing outside its bounds is generally a no-no. When using alternating background colors, the NSTableView draws its background directly. But in a view-based table view, NSTableRowView is used and if it has its own background color, this is poses even more challenge.
The Bad News
The assemblage of NSScrollView (and its various parts), NSTableView, and NSTableHeaderView is complicated on its own. Once you throw view-based functionality into the mix (where each row has a view and each cell has their own view, and each are reused, animated around, etc.), overriding this behavior "is no way to go through life, son!" ;-)
The Good News
The issue of alternating background colors not extending in an elastically-stretched scrolled table view has been resolved (at least on 10.10, that I can tell), so this is no longer an issue unless you have row/cell views with custom backgrounds or just background colors.
A General Solution For Custom Document (Scrolled) Views
For all other scrolled views with ruled backgrounds you wish to extend for elastic scrolling, you'll need a custom NSScrollView subclass that expects a document view (your custom scrolled view) to conform to a protocol you define (like -(NSImage *)backgroundImageForClipViewBounds:). The scroll view would observe its content view (NSClipView) for bounds change notifications and flag itself for display. When the scroll view's -drawRect: is called, it would then ask the scrolled view for its -backgroundImageForClipViewBounds: (via your protocol) and draw it into itself, thereby making the scrolled view's "infinite background" its own.
I haven't tested this theory but I believe it would work. I hope this helps.

NSTextField over NSOpenGLView

I have made a window with an NSOpenGLView that I am rendering openGL content into.
I want to add some buttons and text fields to the view: I can add NSTextFields and NSButtons using interface builder (or code) but they do not appear.
NSOpenGLView is documented as not being able to have sub views, so I then made my own CustomGLView by deriving directly from NSView and implementing the code to create and use a NSOpenGLContext in it. But the subviews are still not appearing :- the OpenGL context paints over them.
On Windows this problem does not exist:- Windows used to host OpenGL MUST have the WS_CLIPCHILDREN and WS_CHIPSIBLINGS styles set ensuring that any peer, or sub children (views) will not be obscured by the OpenGL surface.
How do I get subviews to display over a NSView thats drawing using OpenGL ?
You have 2 choices:
Create a window just for the text field. Add as a child window of the one hosting the OpenGL view. Major downside is you have to manage positioning it correctly if the Open GL view is moved.
Set up your view hierarchy like so:
Layer-backed view
Layer-hosting view whose layer contains an OpenGL layer
Text field
Simply call -setWantsLayer:YES on the subviews of the NSOpenGLView.
NSOpenGLView cannot have subviews according to the documentation. Even if you subclass the NSOpenGLView, that will change nothing.
What you can do is to create a NSView that will hold both the NSOpenGLView and the NSTextField. You then overlap them in the right order to make one draw atop the other.
I'm not heavily into OpenGL yet, but it's my understanding that you can accomplish the visual effect of subviews with Quartz Extreme using layer-backed views; however, those may be problematic. Since subviews are not supported directly, any solution is liable to be a hack.
Indeed, the solution in that link actually hacks a second window to appear over your OpenGL display, the second window displaying the Cocoa views you desire.
The following code (from the above link) is something I've not tested (again not being an OpenGL guy by nature -- yet), but appears like a fairly clever approach:
// This is the GL Window and view you already have
glWindow = [[GLWindow alloc] initWithContentRect:windowRect];
glView = [[[GLView alloc] initWithFrame:NSMakeRect(0, 0, windowRect.size.width, windowRect.size.height)] autorelease];
[glView translateOriginToPoint:NSMakePoint(glView.bounds.size.width/2, glView.bounds.size.height/2)];
[glWindow setContentView:glView];
// And here's your transparent UI window
uiWindow = [[TransparentWindow alloc] initWithContentRect:windowRect];
uiView = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, windowRect.size.width, windowRect.size.height)] autorelease];
[uiView translateOriginToPoint:NSMakePoint(uiView.bounds.size.width/2, uiView.bounds.size.height/2)];
uiView.wantsLayer = YES;
[uiWindow setContentView:uiView];
[glWindow addChildWindow:uiWindow ordered:NSWindowAbove];
Again, I've not tested this, but it looks like it will get you the visual effect you desire.
The text can be rendered into a texture -- I just used this for a project, did a lot of looking for sample code, and ultimately found Apple's GLString demo code, which was an absolute trove of how-to:
http://developer.apple.com/library/mac/#samplecode/CocoaGL/Listings/GLString_m.html
I haven't tried adding buttons, but you can, of course, draw your own and comparing the positions of click events with those of your buttons...
This was my solution:
1) Create a parent NSView (let's call it parentView).
2) Add an NSOpenGLView Child to parentView.
3) Add an additional NSView Child to parentView (make sure this is after the OpenGLView within the hierarchy). You can add additional TextFields, etc. to this view.
4) In the ViewController for the parent make sure you call [parentView setWantsLayer: TRUE]; I did this within -(void) viewWillAppear
1) The NSOpenGLView can have a subview. It can have plenty even.
2) The reason some views, controls and other elements are being bullied by NSOpenGLView is due to the loading process when the Application launches. I.e If you add a slider or textfield above and into the content view of the window where the NSOpenGLView also resides, upon Application-Launch that textfield will most likely wind up beneath the NSOpenGLView.
This is an Apple Bug. And they know about it.
You can solve it quite easily even without adding a subview to NSOpenGLView...
In Interface Builder drag i.e. a CustomView into the canvas (Not the view). And set it the way you want it with sliders, text and what not. Then create an outlet (Call it i.e topView) in your view controller. Then somewhere in your code... Perhaps (applicationDidFinishLaunching) add this line...
[_window.contentView addSubview:_topView];
(Do your positioning & layout)
This will do the exact same thing as if you had dragged it into the contentView yourself inside IB. Only it will draw the darn thing in the correct Z position.
You loose IB's constraints this way and have to it manually
One could also just subclass and CAOpenGLLayer and use that as a backing layer inside of a regular NSView. There too it is drawn correctly...
Here is Apple's way of wanting to do that. CALayers are a Godsend ;)
Enter following String ** NSOpenGLLayer** in search and hit enter to get to where it is...
NSOpenGLLayer
Hope this helps....

Resources