Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSSplitView divider on INAppStoreWindow title bar

I'm trying to create a Reeder/Sparrow-like UI to handle the content of my app. Currently I use an NSSplitView with two NSViews inside (the one on the left is the list of content and the other on the right is the "inspector").

What I would like to know is how to create the divider on the title bar which also acts as a divider of the split view. I'm already using the INAppStoreWindow subclass.

Any ideas? Thanx in advance

like image 747
once Avatar asked Feb 26 '13 15:02

once


1 Answers

The way I've done this is to add an NSSplitView subclass as a subview of the INAppStoreWindow's tileBarView:

// This code comes from the INAppStoreWindow readme
INAppStoreWindow *appStoreWindow = (INAppStoreWindow *)[self window];

// self.titleView is a an IBOutlet to an NSView that has been configured in IB with everything you want in the title bar
self.windowTitleBarView.frame = appStoreWindow.titleBarView.bounds;
self.windowTitleBarView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
[appStoreWindow.titleBarView addSubview:self.windowTitleBarView];

The two tricky parts are getting this split view to behave like a title bar (i.e. to still allow you to drag the window around), and synchronizing the split view in the title bar with the main split view in the window, so they seem like the same thing to the user.

To solve the first, you have to do more than just return YES from -mouseDownCanMovewWindow in your title bar NSSplitView subclass. If you do just that, none of the subviews of the the tile bar will respond to mouse events. Instead, do this:

@implementation MyTitleBarSplitView

- (BOOL)mouseDownCanMoveWindow
{
    return NO;
}

// Code below adapted from http://www.cocoabuilder.com/archive/cocoa/219261-conditional-mousedowncanmovewindow-for-nsview.html
- (void)mouseDown:(NSEvent*)theEvent 
{
    NSWindow *window = [self window];
    NSPoint mouseLocation = [theEvent locationInWindow];
    NSRect dividerRect = NSMakeRect(NSMaxX([[[self subviews] objectAtIndex:0] frame]), 
                                    NSMinY([self bounds]), 
                                    [self dividerThickness], 
                                    NSHeight([self bounds]));
    dividerRect = NSInsetRect(dividerRect, -2, 0);
    NSPoint mouseLocationInMyCoords = [self convertPoint:mouseLocation fromView:nil];
    if (![self mouse:mouseLocationInMyCoords inRect:dividerRect]) 
    {
        mouseLocation = [window convertBaseToScreen:mouseLocation];
        NSPoint origin = [window frame].origin;
        // Now we loop handling mouse events until we get a mouse up event.
        while ((theEvent = [NSApp nextEventMatchingMask:NSLeftMouseDownMask|NSLeftMouseDraggedMask|NSLeftMouseUpMask untilDate:[NSDate distantFuture] inMode:NSEventTrackingRunLoopMode dequeue:YES])&&([theEvent type]!=NSLeftMouseUp)) 
        {
            @autoreleasepool 
            {
                NSPoint currentLocation = [window convertBaseToScreen:[theEvent locationInWindow]];
                origin.x += currentLocation.x-mouseLocation.x;
                origin.y += currentLocation.y-mouseLocation.y;
                // Move the window by the mouse displacement since the last event.
                [window setFrameOrigin:origin];
                mouseLocation = currentLocation;
            }
        }
        [self mouseUp:theEvent];
        return;
    }

    [super mouseDown:theEvent];
}

@end

The second order of business is to synchronize the two split views. Make your controller class (probably the window controller, but whatever makes sense in your code) the delegate of both your main content split view and the title bar split view. Then, implement the two NSSplitView delegate methods below:

@implementation MyController
{
    BOOL updatingLinkedSplitview;
}

- (CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex
{
    // If already updating a split view, return early to avoid infinite loop and stack overflow
    if (updatingLinkedSplitview) return proposedPosition;

    if (splitView == self.mainSplitView)
    {
        // Main splitview is being resized, so manually resize the title bar split view
        updatingLinkedSplitview = YES;
        [self.titleBarSplitView setPosition:proposedPosition ofDividerAtIndex:dividerIndex];
        updatingLinkedSplitview = NO;
    }
    else if (splitView == self.titleBarSplitView)
    {
        // Title bar splitview is being resized, so manually resize the main split view
        updatingLinkedSplitview = YES;
        [self.mainSplitView setPosition:proposedPosition ofDividerAtIndex:dividerIndex];
        updatingLinkedSplitview = NO;
    }

    return proposedPosition;
}

- (void)splitView:(NSSplitView *)splitView resizeSubviewsWithOldSize:(NSSize)oldSize
{
    // This is to synchronize the splitter positions when the window is first loaded
    if (splitView == self.titleBarSplitView)
    {
        NSRect leftFrame = NSMakeRect(NSMinX([self.leftTitleBarView frame]),
                                      NSMinY([self.leftTitleBarView frame]),
                                      NSWidth([self.leftMainSplitView frame]),
                                      NSHeight([self.leftTitleBarView frame]));
        NSRect rightFrame = NSMakeRect(NSMaxX(leftFrame) + [splitView dividerThickness],
                                      NSMinY([self.rightTitleBarView frame]),
                                      NSWidth([self.rightMainSplitView frame]),
                                      NSHeight([self.rightTitleBarView frame]));

        [self.leftTitleBarView setFrame:leftFrame];
        [self.rightTitleBarView setFrame:rightFrame];
    }
}
like image 185
Andrew Madsen Avatar answered Sep 24 '22 04:09

Andrew Madsen