How to custom draw window title bar in Objective-C? - objective-c

I'd like to customize the way I draw the window title bar on OS X. Specifically, I'd like to do something like the Twitterrific app where there is a custom close button, no min/max buttons, and the window title text is right-justified. Unlike Twitterrific, I'm not looking to custom draw the entire window (though I'm not completely opposed to that either).
I've already seen the RoundWindow sample on Cocoa With Love as well as the RoundTransparentWindow example Apple provides, but neither seems appropriate.

If you don't want to use a borderless window class then you can do a couple of things.
First, you can customize the close/min/max buttons buy using -[NSWindow standardWindowButton:]. Once you get the button you can position it/remove it/etc...
You can customize the title by setting the title to #"". Then you can add a NSTextField to draw your own title by doing the following [[[NSWindow contentView] superview] addSubview:textField].
This is probably the easiest way to do things.
Another way to do this is to customize the view that draws all the window title bar, etc...
NSWindow's content view's is inside a "theme view". You can subclass the theme view and do your own drawing. The only problem is that the theme view is a private class so you'll have to be careful.

cocoadev provides some more detail on how best to implement your own NSWindow subclass, complete with a description of most of the common pitfalls.
The gist of it is to create a subclass of NSWindow, and set its styleMask to NSBorderlessWindowMask in the init method:
- (id) initWithContentRect: (NSRect) contentRect
styleMask: (unsigned int) aStyle
backing: (NSBackingStoreType) bufferingType
defer: (BOOL) flag
{
if ((self = [super initWithContentRect: contentRect
styleMask: NSBorderlessWindowMask
backing: bufferingType
defer: flag]) == nil) { return nil; }
[super setMovableByWindowBackground:YES];
[super setLevel:NSNormalWindowLevel];
[super setHasShadow:YES];
// etc.
return self;
}
Note that you should probably return YES for canbecomeKeyWindow in order to make your window behave like a normal window.
- (BOOL) canBecomeKeyWindow
{
return YES;
}
You can then create a custom NSView subclass, fill the entire window with an instance of said class, and then perform all of the appropriate window drawing from within that custom view.
The whole thing can get a bit painful. You will have to re-implement most of the normal window behaviours such as resizing by dragging the bottom right corner.

There's an example of a custom window implementation in the CoreData Stickies sample project.

Related

keyDown: Not Called on NSClipView Subclass

My app is not document based, and its sole window is managed by a custom, xib-based NSWindowController subclass that I instantiate within the app delegate code:
- (void) applicationDidFinishLaunching:(NSNotification*) aNotification
{
_mainWindowController = [MainWindowController new];
// (stored in ivar just to prevent deallocation)
//[_mainWindowController showWindow:self];
// ↕︎ Not sure about the difference between these two... both seem to work.
[[_mainWindowController window] makeKeyAndOrderFront:self];
}
I have subclassed NSClipView to "center content inside a scroll view" (instead of having it pegged to the lower left corner) when it is zoomed to a size smaller than the clip view, and also implement custom functionality on mouse drag etc.
My window does have a title bar.
My window isn't borderless (I think), so I am not subclassing NSWindow.
I have overriden -acceptsFirstResponder, -canBecomeKeyView and -becomeFirstResponder in my NSClipview subclass (all return YES).
The drag events do trigger -mouseDown: etc., and if I set a breakpoint there, the first responder at that point is the same as the window hosting my clip view: [self.window firstResponder] and [self window] give the same memory address.
What am I missing?
Update
I put together a minimal project reproducing my setup.
I discovered that if my custom view is the window's main view, -keyDown: is called without problems. But if I place a scroll view and replace its clip view by my custom view (to do that, I need to change the base class from NSView to NSClipView, of course!), -keyDown: is no longer triggered.
I assume it has something to do with how NSScrollView manages events (however, as I said before, -mouseDown:, -mouseDragged: etc. seem to be unaffected).
I also discovered that I can override -keyDown: in my window controller, and that seems to work, so I have decided to do just that (still open to an answer, though). Also, since I'm trying to detect the shift key alone (not as a modifier of another key), I'd rather use:
- (void) flagsChanged:(NSEvent *) event
{
if ([event modifierFlags] & NSShiftKeyMask) {
// Shift key is DOWN
}
else{
// Shift key is UP
}
}
...instead of -keyDown: / -keyUp: (taken from this answer).

Unexpected behavior of NSTextView (because of titlebar-less NSWindow?)

I have a NSWindow (my main window) and a child window (positioned NSWindowBelow the main window) that has a NSTextView. The child window doesn't have a title bar, nor shadow and its transparent.
Here's the code I use to set up my child window to make it transparent:
- (id) initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag{
if (![super initWithContentRect: contentRect styleMask:NSBorderlessWindowMask backing: bufferingType defer:NO]) return nil;
[self setBackgroundColor: [NSColor clearColor]];
[self setOpaque:NO];
return self;
}
But when I try to select the text in it, here's what happens (the black stuff above the child window is the main window):
It looks like the NSTextView is not focused, because the selection is not blue. I've tried calling: [[_childWindow textView] becomeFirstResponder]; but the outcome is the same. Another thing, is that when I scroll it, sometimes it's very laggy and "breaky".
Do you guys have any ideas on whats causing this and how to fix it? I suspect it's because the window doesn't have a title bar, but I'm not sure.
Thanks!
From the NSWindow docs:
canBecomeKeyWindow
Indicates whether the window can become the key window.
- (BOOL)canBecomeKeyWindow
Return Value
YES if the window can become the key window, otherwise, NO.
Discussion
Attempts to make the window the key window are abandoned if this method returns
NO. The NSWindow implementation returns YES if the window has a title bar or a
resize bar, or NO otherwise.
Try overriding -canBecomeKeyWindow and returning YES.

NSPanel as HUD Panel - fullscreen makes strange empty space

I have NSPanel window with style Utility panel (gray small title bar). I can fullscreen this window using standard MAC OS X fullscreen feature. But one strange thing happens - the content view of window doesn't use whole screen, there is small empty space, that is high as title bar.
This doesn't happen, when I change my window style to Regular panel (means higher titlebar).
NSPanel parameters designed in Interface builder:
Clipping of fullscreen view of this window. Note empty space - where red arrow points. This doesn't happen when I change Style to Regular panel.
Try using NSWindow instead of NSPanel
- (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)windowStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)deferCreation {
if (self = [super initWithContentRect:contentRect
styleMask:NSBorderlessWindowMask
backing:NSBackingStoreBuffered defer:deferCreation]) {
[self setOpaque:NO];
[self setExcludedFromWindowsMenu:NO];
}
return self;
}
This is a quazi related answer

UIPageViewController Traps All UITapGestureRecognizer Events

It's been a long day at the keyboard so I'm reaching out :-)
I have a UIPageViewController in a typical implementation that basically follows Apple's standard template. I am trying to add an overlay that will allow the user to do things like touch a button to jump to certain pages or dismiss the view controller to go to another part of the app.
My problem is that the UIPageViewController is trapping all events from my overlay subview and I am struggling to find a workable solution.
Here's some code to help the example...
In viewDidLoad
// Page creation, pageViewController creation etc....
self.pageViewController.delegate = self;
[self.pageViewController setViewControllers:pagesArray
direction:UIPageViewControllerNavigationDirectionForward
animated:NO
completion:NULL];
self.pageViewController.dataSource = self;
[self addChildViewController:self.pageViewController];
[self.view addSubview:self.pageViewController.view];
// self.overlay being the overlay view
if (!self.overlay)
{
self.overlay = [[MyOverlayClass alloc] init]; // Gets frame etc from class init
[self.view addSubview:self.overlay];
}
This all works great. The overlay gets created, it gets show over the top of the pages of the UIPageViewController as you would expect. When pages flip, they flip underneath the overlay - again just as you would expect.
However, the UIButtons within the self.overlay view never get the tap events. The UIPageViewController responds to all events.
I have tried overriding -(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch per the suggestions here without success.
UIPageViewController Gesture recognizers
I have tried manually trapping all events and handling them myself - doesn't work (and to be honest even if it did it would seem like a bit of a hack).
Does anyone have a suggestion on how to trap the events or maybe a better approach to using an overlay over the top of the UIPageViewController.
Any and all help very much appreciated!!
Try to iterate through UIPageViewController.GestureRecognizers and assign self as a delegate for those gesture and implement
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
Your code may be like this:
In viewDidLoad
for (UIGestureRecognizer * gesRecog in self.pageViewController.gestureRecognizers)
{
gesRecog.delegate = self;
}
And add the following method:
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
if (touch.view != self.pageViewController.view]
{
return NO;
}
return YES;
}
The documented way to prevent the UIPageViewController from scrolling is to not assign the dataSource property. If you assign the data source it will move into 'gesture-based' navigation mode which is what you're trying to prevent.
Without a data source you manually provide view controllers when you want to with setViewControllers:direction:animated:completion method and it will move between view controllers on demand.
The above can be deduced from Apple's documentation of UIPageViewController (Overview, second paragraph):
To support gesture-based navigation, you must provide your view controllers using a data source object.

Getting NSView to appear programmatically in Objective-C

I have a NSView object that is being returned to me as a result of a function. I know the view is valid because I can see the contents of the view if I do this:
NSRect rect = NSMakeRect(600,600,200,200);
NSWindow *testWindow = [[NSWindow alloc] initWithContentRect:rect styleMask:NSTexturedBackgroundWindowMask backing: NSBackingStoreBuffered defer:NO];
[[testWindow contentView] addSubview:returnedView];
[testWindow makeKeyAndOrderFront:NSApp];
In my application I have a window with a custom view (has some text on it) that has an outlet referenced in my code using IBOutlet. I'm trying to add the view I'm getting returned as a subview of that outlet.
[referencedView addSubView:returnedView]
[referencedView setNeedsDisplay:YES];
The referenced view is visible (I can see the text in it), but the returnedView doesn't appear on top. Am I forgetting something?
This is what my code looks like now:
[returnedView setFrame:NSMakeRect(0,0,200,200)];
[referencedView addSubview:returnedView];
[referencedView setNeedsDisplay:YES];
[referencedView drawRect:[referencedView bounds]];
Views can only be in ONE superview is what I just learned. I had the test code and the code I wanted to work so it was removing my view and putting it in the window instead.
That custom view (with the text in it) has a custom drawRect implementation, in which the text is drawn, right? In this case, my idea is that you'd want to call super's implementation of drawRect to make sure that the subviews get drawn too.