Cocoa: [self.view setWantsLayer:YES] brings that view above other subviews? - macos

Simply, I have an NSView with a bunch of subviews. In the awakeFromNib method (inside the view's controller), I decided to add the following:
[_backgroundImageView setWantsLayer:YES];
[[_backgroundImageView layer] setShadowOpacity:1.0f];
[[_backgroundImageView layer] setShadowOffset:NSMakeSize(-3, -3)];
The backgroundImageView is at the back of all the subviews. But, when I added the previous code, it draws the shadow correctly, but also draws the backgroundImageView above all other layers. Why? How I can Fix that?

You need setWantsLayer:YES in code for the superview.
Turn's out I should enable the layer for the superview (self.view), too. Not only that, but I should tighten up the imageView's frame, or else it will be scaled "axis independently" even though it is set to "Proportional up or down".

Related

NSImageView disappears when the parent NSView wants layer (setWantsLayer:YES)

I have a simple custom NSView that has a label and an image. Currently the view works quite well (all the thumbs are instances of my custom view):
However, I want to set the opacity of these thumbnails. Setting viewInstance.layer.opacity doesn't work as the layer is nil. When I use [viewInstance setWantsLayer:YES] first somewhere, and I set the opacity, the opacity of the text changes (it is displayed correctly), but the image view disappears altogether:
I've tried creating a layer first, assigning it, and then calling setWantsLayer: after reading this question: How to add a CALayer to an NSView on Mac OS X but nothing changed:
CALayer *selfLayer = [[CALayer alloc] init];
[selfLayer setFrame:CGRectMake(0, 0, 100, 100)];
[self setLayer:selfLayer];
[self setWantsLayer:YES];
I've tried all combinations of creating a layer and displaying it for the image view too (just calling setWantsLayer:, creating layer and then assigning it, calling display method of it), but still nothing changes:
[self addSubview:self.imageView];
CALayer *imageLayer = [[CALayer alloc] init];
[imageLayer setFrame:CGRectMake(0, 0, 100, 100)];
[self.imageView setLayer:imageLayer];
[self.imageView setWantsLayer:YES];
I've tried adding the image view's layer as a sublayer of the main view and then displaying it:
[self.layer addSublayer:self.imageView.layer];
[self.imageView.layer display];
[self.layer display];
But still, the images won't display, whereas the text always displays correctly. When I comment out the code about assigning and wanting layers, images display again, but obviously, I can't use the layer opacity. I am on OS X Mavericks with OS X SDK 10.8. What am I doing wrong?
UPDATE: I ended up getting rid of NSImageView completely and drawing the NSImage directly into the layer by setting the layer's contents property. However, this is just another approach which solved my problem for this project, it still doesn't answer the original question.
I ended up getting rid of NSImageView completely and drawing the NSImage directly into the layer by setting the layer's contents property. However, this is just another approach which solved my problem for this project, it still doesn't answer the original question.

NSWindow, strange corner when using Core Animation

In my project there is a view with a custom drawRect: method which draws a dark background. When I call [view setWantsLayer:YES] the corner of the window (where view is placed) appears less smooth (image 2) than usual (image 1).
How can I solve this?
You need to set cornerRadius: and enable maskToBounds: for layer like this:
[view setWantsLayer:YES];
[[view layer] setMasksToBounds:YES];
[[view layer] setCornerRadius:10.0];
Result:

NSScrollview and transparent, overlay NSScroller subclasses

I have made a slick NSScroller subclass, but can't figure out how to make it overlay on top of the NSScrollView instead of pushing the documentView aside.
Here you can see the background of a NSCollectionView that I wish to make 100% wide, and have the scroller sit along top. Currently, I have to set a white background to the scroller because drawing with a clearColor is not showing as transparent, but as black.
Am I going about this the wrong way? Am I missing something obvious here? How can I achieve the behavior of a transparent-tracked NSScroller that sits atop a NSScrollView's contents?
I was able to get the positioning by implementing tile in the subclass OF NSSCROLLVIEW
- (void)tile {
[super tile];
[[self contentView] setFrame:[self bounds]];
}
And it turns out that trying to draw clear color was the problem to begin with. In the scroller subclass, I just omitted any drawing that had to do with the slider track completely BY OVERRIDING DRAWRECT: OF THE NSSCROLLER SUBCLASS, LIKE SO:
- (void)drawRect:(NSRect)dirtyRect
{
[self drawKnob];
}
Note that for this to work properly, you MUST enable layer-backing for the scrollView!
That is, call:
[scrollViewInstance setWantsLayer:YES];
or set it in Interface Builder.
If you don't do this, the scrollView's contentView will draw ON TOP OF the scrollers. Also: you should be aware that what you're doing is essentially overlapping two NSViews (NSScroller on top of NSScrollView --- both inherit from NSView.) Unlike UIViews on iOS, overlapping NSViews on OS X is not officially supported by any current version of the OS (10.6 down). Turning on CALayers seems to make it work, but it's still something to bear in mind. Of course, turning on layers can seriously kill drawing performance.
See this SO question for more detail: Is there a proper way to handle overlapping NSView siblings?
What's about this color: [NSColor colorWithCalibratedRed:0.0 green:0.0 blue:0.0 alpha:0.0]
I think you have to modify the sizes with the interface builder.

NSTextView not refreshed properly on scrolling

I have a NSTextView with a sizeable quantity of text. Whenever I scroll however, the view isn't updated properly. There are some artifacts that remain at the top or the bottom of the view. It appears that the view doesn't refresh itself often enough. If I scroll very slowly the view updates correctly though. If I add a border to the view everything works perfectly, borderless view is the one that has a problem. Here's a link to a screenshot:
Thanks
Have you set the setDrawsBackground and copiesOnScroll propertes for either the NSScrollView or the NSClipView?
The first thing I would suggest is turning off the "draws background" property of the NSScrollView:
[myScrollView setDrawsBackground:NO];
Note that this should be set on the NSScrollView, and not on the embedded NSClipView.
The following excerpt from the documentation may be relevant:
If your NSScrollView encloses an NSClipView sending a setDrawsBackground: message with a parameter of NO to the NSScrollView has the added effect of sending the NSClipView a setCopiesOnScroll: message with a parameter of NO. The side effect of sending the setDrawsBackground: message directly to the NSClipView instead would be the appearance of “trails” (vestiges of previous drawing) in the document view as it is scrolled.
Looks like the text field isn't even in the scrolling-area... Are you sure something isnt overlapping it?
I had a similar trouble - artifacts develop when the NSTextView is embedded in another scrollview (ie. a NSTableView).
I actually turned on the setdrawsbackground, and then added a nice color to make it disappear again.
-(void)awakeFromNib{
NSScrollView *scroll = [self enclosingScrollView];
[scroll setBorderType:NSNoBorder];
[scroll setDrawsBackground:YES];
[scroll setBackgroundColor:[NSColor windowBackgroundColor]];
}
This in combination with a scrollWheel event let me use the NSTextView in a NSTableView.
-(void)scrollWheel:(NSEvent *)theEvent{
NSScrollView *scroll = [self enclosingScrollView];
[[scroll superview] scrollWheel:theEvent];
}
I had the same trouble some time ago. I don't remember how I solved it.
Try to place the NSTextView to another view if the superview is a custom view. Just to see what will happen.

How to make a transparent NSView subclass handle mouse events?

The problem
I have a transparent NSView on a transparent NSWindow. The view's drawRect: method draws some content (NSImages, NSBezierPaths and NSStrings) on the view but leaves parts of it transparent.
Clicking on the regions of the view that have been drawn on invokes the usual mouse event handling methods (mouseDown: and mouseUp:).
Clicking on the transparent areas gives focus to whatever window is behind my transparent window.
I would like to make parts of the transparent region clickable so that accidentally clicking between the elements drawn on my view does not cause the window to lose focus.
Solutions already attempted
Overriding the NSView's hitTest: method. Found that hitTest: was only called when clicking on a non-transparent area of the view.
Overriding the NSView's opaqueAncestor method. Found that this was not called when clicking on any part of the view.
Filling portions of the transparent area with [NSColor clearColor] in the drawRect: method, and with an almost-but-not-quite-transparent colour. This had no effect.
Experimented with the NSTrackingArea class. This appears to only add support for mouseEntered:, mouseExited:, mouseMoved:, and cursorUpdate: methods, not mouseUp: and mouseDown:.
I had the same problem. It looks like [window setIgnoresMouseEvents:NO] will do it.
(On Lion, at least. See http://www.cocoabuilder.com/archive/cocoa/306910-lion-breaks-the-ability-to-click-through-transparent-window-areas-when-the-window-is-resizable.html)
As far as I know, click events to transparent portions of windows aren't delivered to your application at all, so none of the normal event-chain overrides (i.e -hitTest:, -sendEvent:, etc) will work. The only way I can think of off the top of my head is to use Quartz Event Taps to capture all mouse clicks and then figure out if they're over a transparent area of your window manually. That, frankly, sounds like a huge PITA for not much gain.
George : you mentioned that you tried filling portions with an almost but not quite transparent color. In my testing, it only seems to work if the alpha value is above 0.05, so you might have some luck with something like this:
[[NSColor colorWithCalibratedRed:0.01 green:0.01 blue:0.01 alpha:0.05] set];
It's an ugly kludge, but it might work well enough to avoid using an event tap.
Did you try overriding
- (NSView *)hitTest:(NSPoint)aPoint
in your NSView sublcass?
You can use an event monitor to catch events outside a window/view.
https://developer.apple.com/library/mac/documentation/cocoa/conceptual/eventoverview/MonitoringEvents/MonitoringEvents.html
You can override the hitTest method in your NSView so that it always returns itself.
According to the NSView documentation, the hitTest method will either return the NSView that the user has clicked on, or nil if the point is not inside the NSView. In this case, I would invoke [super hitTest:], and then return the current view only if the result would otherwise be nil (just in case your custom view contains subviews).
- (NSView *)hitTest:(NSPoint)aPoint
{
NSView * clickedView = [super hitTest:aPoint];
if (clickedView == nil)
{
clickedView = self;
}
return clickedView;
}
You can do:
NSView* windowContent = [window contentView];
[windowContent setWantsLayer:YES]
Making sure that the background is transparent:
[[windowContent layer] setBackgroundColor:[[NSColor clearColor] CGColor]];
Another option would be to add a transparent background image that fills the contentView.

Resources