Creating a custom title bar on a standard NSWindow - objective-c

I've been trying to build a specific look for my menubar app.
I've been using a NSWindow with a NSBorderlessWindowMask style mask and setting [window setOpaque:NO] and [window setBackgroundColor:[NSColor clearColor]]. That gives me a blank canvas which works great for the title bar.
Now I'm having problems with the view-based NSTableView I'm using for the listing. How can I clip the NSTableCellViews to the window's rounded corners?
I started out just having a custom view wrapping the NSTableView, drawing the background with rounded corners. Using [view addClip:path] doesn't clip child views though.
I've also tried using a [view setWantsLayer:YES] with a mask. That worked great, but the table view cells would sporadically glitch out. It seems that having a NSScrollView be a child of a layer is a known problem:
My current view structure looks something like:
NSWindow
- MyTitleBarView
- MyBackgroundView
- NSScrollView
- NSTableView

I found one way to do it:
The trick is to keep the window style as the default and not set NSBorderlessWindowMask. Then you can add your custom title bar view to the window's theme frame like so:
NSView *themeFrame = [[window contentView] superview];
NSView *firstSubview = [[themeFrame subviews] objectAtIndex:0];
[titleBarView setAutoresizingMask:(NSViewMinYMargin | NSViewWidthSizable)];
[themeFrame addSubview:titleBarView positioned:NSWindowBelow relativeTo:firstSubview];
This basically just puts your custom title bar view on top of the standard title bar. You'll probably have to do some rejiggering to the view frames and window buttons. See INAppStoreWindow for some code examples of this.
The INAppStoreWindow project says that this method doesn't use any private APIs, and thus is able to be used on the App Store.
If you require the window to be transparent, you can just set the following on the window:
[window setOpaque:NO];
[window setBackgroundColor:[NSColor colorWithCalibratedWhite:1.0 alpha:0.5]];

Related

Resizing NSWindow to fit child NSView

I have one main NSWindow which is empty, and 5 NSViews. The NSViews have different buttons and labels etc, and the window is empty. The first view displayed is a menu, linking to the other views and back. This works fine and the views switch well.
However if the NSWindow is a certain size, and the NSView is bigger, then it spills out of the NSWindow and gets cut off.
Is there any way such that when I do:
[_window setContentView: theNewView];
to also have _window resize to fit the new view? If this is possible, can this be done with an animation?
-[NSWindow setContentSize:] does this (without animation). Give it the desired size of the content view and it will resize both content view and the window appropriately, e.g.
[_window setContentSize:theNewView.frame.size];
[_window setContentView:theNewView];
For animation, you need to compute window size manually using frameRectForContentRect: and then change window's frame with animate:YES:
[_window setContentView:theNewView];
NSRect viewScreenFrame = /*translate theNewView.frame to screen coordinates*/;
NSRect wndFrame = [_window frameRectForContentRect:viewScreenFrame];
[_window setFrame:wndFrame display:YES animate:YES];

Padding around NSTableView

I've a following problem. There is a subclassed NSScrollView with a view based NSTableView in it. I've added a custom background to the scrollview in the -drawRect: method of subclass, and now I would like to add some "padding" around the inner tableview like this:
example http://img.skitch.com/20120117-ktd9g5wy8u9cm37jeebjjxx61u.png
How can I implement this?
If you're targeting Mac OS 10.10 or later, you could use
[scrollView setAutomaticallyAdjustsContentInsets:NO];
[scrollView setContentInsets:NSEdgeInsetsMake(top, right, bottom, left)];
Finally, I've solved the problem. I've created an NSView (let's call it documentContentView), added my NSTableView as a subview of this documentContentView, then I've added the documentContentView to the scrollview's documentView:
NSTableView *docView = (NSTableView *)self.scrollView.documentView;
id newClipView = [[CustomClipView alloc] initWithFrame:[self.scrollView.contentView frame]];
[self.scrollView setContentView:(NSClipView *)newClipView];
[newClipView setDrawsBackground:NO];
NSView *documentContentView = [[NSView alloc] initWithFrame:docView.bounds];
docView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
[documentContentView addSubview:docView];
[self.scrollView setDocumentView:documentContentView];
[self.scrollView setDrawsBackground:NO];
I've created my custom NSClipView called CustomClipView (based on this article http://www.cocoadev.com/index.pl?CenteringInsideNSScrollView) and this subclass sets the origin of the documentContentView when the window resized. I've subclassed my tableview as well, and in -reloadData method I can resize the documentContentView when the tableview change it's contents.
The left and right padding can be done inside of the row/cell itself. For the top and bottom padding I suggest to add additional rows with no content and not selectable. This is not sexy, but worked for me.
First of all, don't add backgrounds in drawRect:. Add it in your initWithFrame: if you're subclassing, or change it from the invoker.
Adding the padding is easy: Change the frame of the NSTableView so that it is smaller, and has an origin that isn't at 0,0.

add UIImageView that covers navigation bar

I think I'm missing something simple here. I need to create a semi-transparent UIImageView that covers the entire screen including the navigation toolbar. How do I do this?
This is what I'm trying to do ...
This is a possible solution:
UIImage *image=[UIImage imageNamed:#"whatever.png"];
UIImageView *overlay=[[UIImageView alloc] initWithImage:image];
overlay.alpha=0.5;
[[[[UIApplication sharedApplication] delegate] window] addSubview:overlay];
EDIT:
It is likely that you would not be setting the alpha value for the overlay, but rather use a transparent PNG with embedded transparence levels. Still, it's a possibility.
When not using ARC, you should [overlay release].
To focus the above answer a little bit, you just need to be clear on how views clip to what parts of the device's screen that they "own".
The key point is that in a navigation view, the Navigation bar itself is not part of your [myController view] - your view controller's view is everything below the bar and anything you do in that view clips to the rectangle below that bar.
The bar is, however, part of your [myAppDelegate window]. The window is essentially the entire screen of your device, while the views are sub portions responsible for managing their specific bounds. So calling [[myAppDelegate window] addSubView:] will display above the bar where [[myViewController view] addSubView:] will not.

Drawing a Window at Coordinates or Drawing outside of NSStatus Item

I am trying to display something off the side of a NSStatusItem. I think I could do this in two ways:
Display a transparent window with the image I need at the coordinates of the mouse cursor.
OR
Use a custom NSStatusItem and move the controls/images in the view to the left so they are actually off the status item
The problem is, setting the NSRect frame negative (-200,0,100,50) doesn't seem to actually work. So, how can I render things outside of the bounds of the status item (think the CSS overflow property) or render a transparent window at specific coordinates?
The system will prevent you from drawing outside the status item, but using a transparent window will work.
NSRect rect; //The location of the window
NSWindow *win = [[NSWindow alloc] initWithContentRect:rect styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:YES];
[win setOpaque:NO];
[win setBackgroundColor:[NSColor clearColor]];
//or
[win setContentView:myView];
Here, myView is a custom view which will be the background of the window. In order for the window to be transparent, you either have to set the background color to clear or use a custom content view which only draws where it is not transparent. You will probably want to use a floating window so that it stays on top. Be careful not to cover up something important because your window could intercept events intended for something underneath it.

Custom NSView Fill Paints Over Bottom Bar

I have a window which has a custom NSView and has a bottom bar with controls on it, one of which is an NSColorWheel.
For simplicity sake the Window is 332px high, with the custom NSView being 300px high and the bottom bar being 32px high.
The bottom bar is created as part of my awakeFromNib when the app loads the window using the following code:
[[self window] setAutorecalculatesContentBorderThickness:YES forEdge:NSMinYEdge];
[[self window] setContentBorderThickness: 32.0 forEdge: NSMinYEdge];
In my custom NSView class I fill the rectangle with color. Everything works fine when the app loads using the following in my NSView class:
- (void)drawRect:(NSRect)dirtyRect
{
dirtyRect = [self bounds];
NSColor * mNewColor = [NSColor blackColor];
[mNewColor set];
[NSBezierPath fillRect:dirtyRect];
}
However, if I subsequently call a method that changes the color of the custom NSView when a color wheel in the bottom bar is changed, the bottom bar gets overwritten with the color. The following code illustrates this method (this code is in the custom NSView class:
- (void)changeBackgroundColor:(NSNotification *)notification
{
NSLog(#"Changed background color");
NSRect mRect = [self bounds];
NSColor * mNewColor = [theColorWell color];
[mNewColor set];
[NSBezierPath fillRect:mRect];
[self setNeedsDisplay:YES];
}
Resizing the window instantly corrects the problem, but obviously I don't want the user to have to resize the window for an obvious bug!
What I don't understand is why my bounds appear to be mapping to the parent window and not the custom NSView when I call setNeedsDisplay and yet the bound correctly adjust when I resize the window using the mouse (even if just by 1 pixel).
Do I somehow need to account for the bottom bar on the redraw?
Any and all help much appreciated.
You should do all your drawing in the drawRect: method of your custom NSView. Cocoa automatically sets up the graphics context for you when it calls this method - things may not draw correctly if you perform drawing operations in other methods.
Your code in drawRect: could set the colour to the the current background colour as specified by your NSColorWell and fill the dirtyRect rectangle with this.
Then in the other method just call [self setNeedsDisplay:YES]; and then drawRect: will automatically be called to redraw the view.
See here for more information: http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/CocoaViewsGuide/SubclassingNSView/SubclassingNSView.html#//apple_ref/doc/uid/TP40002978-CH7-SW4 (in particular the Drawing View Content section)