NSScrollview and transparent, overlay NSScroller subclasses - cocoa

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.

Related

Shrink NSScroller of NSScrollView in layer-backed view

I'm experiencing a weird problem (Bug?): Say I have a WebView, which will scroll vertically.
I now want to shrink the mainFrame's vertical scroller a little bit, so that its height is smaller than the NSScrollView itself.
The reason for this is that I want to pin two views (on top and on bottom edge) above the webview.
I did that easily in the frameLoad delegate method by altering the verticalScroller's frame (altering origin and height).
It works, but:
However once I set the webView and it's parent NSView to be layer-backed, it stops working, the scroller resets itself to the default position and height.
Now I don't know if this is a bug or not.
Is there any other way I could try to 'inset' the scroller?
Have you tried subclassing NSScroller, setting it as the vertical scroller for your NSScrollView and overriding
-(void) setFrameOrigin:(NSPoint)origin;
-(void) setFrameSize:(NSSize)size;
I assume making the NSView layer-backed causes frameSize to be set again(NSScrollView seems to take quite a bit of control on this because when you create a custom scroller, there is no "clean" way to set an NSScroller as horizontal or vertical other than calling -initWithFrame: with
height > width to automatically set it as vertical
width > height to automatically set it as horizontal
My other suggestion would be, if you want the NSScroller to not fully reflect the size of its parent NSScrollView would you be better off having an NSView as the content view of the webview. And the NSView have 3 subviews. Two views at the top and bottom pinned, and one NSScrollView in the middle. No custom NSScroller necessary in this case.

Transparent NSImageView falls behind NSView

I have a transparent .png image that lays over an NSView and it should look like this:
However, sometimes, with no rhyme or reason or indication that it is going to do this, the NSImageView falls behind the NSView:
I am not sure how to keep the NSImageView above the NSView, or why it even falls behind the NSView.
Here is what it looks like in Interface Builder:
Update:
I have tried the following with same results:
[header removeFromSuperview];
[mainView addSubview:header positioned:NSWindowAbove relativeTo:nil];
What is a better way to achieve this effect?
Cocoa UI elements that overlap have an undefined drawing order, and Apple states that you shouldn't overlap them. You could create some custom drawing code to make your current design work. However, honestly I would change the design so it matches with Mac OS X's human interface guidelines. You can read about these at the link below.
http://developer.apple.com/library/mac/#documentation/UserExperience/Conceptual/AppleHIGuidelines/XHIGIntro/XHIGIntro.html%23//apple_ref/doc/uid/TP30000894-TP6
When you have two overlapping NSView on the same level it is undefined what the drawing order will be. You can sort the drawing of the subviews using SubviewsUsingFunction:context:. You'll have to provide a comparison function that compares based on some value (e.g. tag):
NSComparisonResult viewCompareByTag(NSView *firstView, NSView *secondView, void *context) {
return ([firstView tag] > [secondView tag]) ? NSOrderedAscending : NSOrderedDescending;
}
[mySuperView sortSubviewsUsingFunction:&viewCompareByTag context:nil];
Turns out that I had to re-arrange some of the views in Interface Builder,
then in code:
[[header layer] setZPosition:1.0f];

Gray border when using NSBorderlessWindowMask

Whenever I try to create a custom window using NSBorderlessWindowMask and set an NSView (for example an NSImageView) as its contentView, I get a 1px gray border around the NSView and I don't seem to be able to get rid of it.
I have followed several approaches including Apple's RoundTransparentWindow sample code as well as several suggestions on StackOverflow.
I suspect the gray border is either coming from the window itself or the NSView.
Have any of you experienced this problem or do you have a possible solution?
The code is fairly straightforward. This is the init method of the custom window:
- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag {
self = [super initWithContentRect:contentRect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
if (self != nil) {
[self setAlphaValue:1.0];
[self setBackgroundColor:[NSColor clearColor]];
[self setOpaque:NO];
}
return self;
}
To test this, in IB I place an NSImageView in that custom window WITHOUT border and yet the image in the NSImageView has a border. The same goes for other NSView subclasses, such as NSTextField, NSTableView.
In addition, I also noticed that the same is happening with the sample application (RoundTransparentWindow) of Apple. Is it even possible to draw an NSView in a custom window without a 1px border?
Thanks
Are you sure this happens when you use a regular NSView with no drawing? I bet not. Other controls (like NSImageView)have borders. Maybe you should double check to make sure they're turned off whe possible.
Update - How do you get your view into your window? You don't include that code. I created a basic test project (download it here) with an image well and it works just fine. See for yourself.

setFrame created an NSView that is 15 pixels smaller in both dimensions than expected

I have a puzzling problem. I have a pointer to a vanilla NSView that was initialized in a nib. I messaged the NSView with:
[myNSView setFrame:NSMakeRect(0,0,816,1056)];
but when I step through the debugger, myNSView has a frame of {{0,0}, {801, 1041}}. The dimensions of the rectangle are 15 less than I've specified! This happens consistently. If I specify two [setFrames] in a row, everything works, but of course that's not the answer.
Why does setFrame fail?
15px is exactly the size of an NSScroller at NSRegularControlSize.
My guess is that you have your NSScrollView configured to automatically hide scrollers.
Try turning off the horizontal and vertical scrollers of your scrollView in the NIB, if that solves the problem, you'll know where to look from there.  It is something related to the clipView of the scrollView autoresizing the documentView. The clipView itself is being autoresized when the scrollers appear; directly after you set the documentView to a frameSize (the 100% setting I'd guess) that requires scrollers.

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