PDA

View Full Version : Custom NSView with subview of NSSplitView




ArtOfWarfare
Aug 18, 2013, 05:41 PM
I want a subclass NSView that actually wraps around an NSSplitView containing a few other views. The problem is, the split view is ignoring any directions to set the position of its dividers (marked in red):

- (id)initWithFrame:(NSRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
[self setup];
}
return self;
}

- (void) setup
{
self.verticalSplitView = [NSSplitView new];
self.verticalSplitView.dividerStyle = NSSplitViewDividerStyleThin;
[self.verticalSplitView setVertical:YES];
NSTextView *textView1 = [NSTextView new];
NSTextView *textView2 = [NSTextView new];
NSTextView *textView3 = [NSTextView new];
textView1.backgroundColor = [NSColor redColor];
textView2.backgroundColor = [NSColor blueColor];
textView3.backgroundColor = [NSColor magentaColor];
[self.verticalSplitView addSubview:textView1];
[self.verticalSplitView addSubview:textView2];
[self.verticalSplitView addSubview:textView3];
[self addSubview:self.verticalSplitView];
self.verticalSplitView.translatesAutoresizingMaskIntoConstraints = NO;
[self addConstraints:[NSLayoutConstraint constraintsFillingChild:self.verticalSplitView]];
[self.verticalSplitView setPosition:40 ofDividerAtIndex:1];
[self.verticalSplitView setPosition:20 ofDividerAtIndex:0];
}

- (void)drawRect:(NSRect)dirtyRect
{
[[NSColor redColor] setFill];
NSRectFill(dirtyRect);
}

I also wrote this category method on NSLayoutConstraint:

+ (NSArray *)constraintsFillingChild:(id)view
{
NSDictionary *views = NSDictionaryOfVariableBindings(view);
NSArray *v = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view]|"
options:0
metrics:nil
views:views];
NSArray *h = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]|"
options:0
metrics:nil
views:views];
NSMutableArray *constraints = [[NSMutableArray alloc] initWithCapacity:4];
[constraints addObjectsFromArray:v];
[constraints addObjectsFromArray:h];
return constraints;
}

I tested this by dragging an NSView into a window in MainMenu.xib and assigning it to my subclass.

I've set a variety of values for the methods I highlighted in red and no matter what they seem to be ignored. I know they're being executed because everything else in that method works.



heyadrian
Aug 19, 2013, 12:05 AM
What you're looking for is:

setPositionOfDividerAtIndex:

i.e.

http://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSSplitView_Class/Reference/Reference.html#//apple_ref/occ/instm/NSSplitView/setPosition:ofDividerAtIndex:

Another way of dealing with this is by having an NSSplitViewDelegate :)

If you do set the position and are using constants, I'd suggest using a delegate as you need to handle the screen resizing to resize the subviews accordingly so they don't break constraints.

Here's an example of one of my split view delegates which I use in one of my apps. It's on a 2 paned split view, but it still applies!

SplitDelegate.h file:



#import <Foundation/Foundation.h>

@interface SplitDelegate : NSObject <NSSplitViewDelegate>

@end


SplitDelegate.m file


#import "SplitDelegate.m"

@implementation SplitDelegate

-(CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex{
return 200.0; //controls the MAX position of a split (use an if else or switch block to deal with the dividerIndex)
}

-(CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex{
return 200.0;//controls the MIN position of a split (use an if else or switch block to deal with the dividerIndex)

}

-(CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex{
return 200.0; //This deals with a FIXED splits (so for multiple use an if else or switch block to handle divider Index)
}


//The following handles resizing of the window and how the subviews and splits and panes are affected. This is specifically for a 2 pane splitView
-(void)splitView:(NSSplitView *)sender resizeSubviewsWithOldSize:
(NSSize)oldSize
{
CGFloat dividerThickness = [sender dividerThickness];
NSRect leftRect = [[[sender subviews] objectAtIndex:0] frame];
NSRect rightRect = [[[sender subviews] objectAtIndex:1] frame];
NSRect newFrame = [sender frame];

leftRect.size.height = newFrame.size.height;
leftRect.origin = NSMakePoint(0, 0);
rightRect.size.width = newFrame.size.width - leftRect.size.width
- dividerThickness;
rightRect.size.height = newFrame.size.height;
rightRect.origin.x = leftRect.size.width + dividerThickness;

[[[sender subviews] objectAtIndex:0] setFrame:leftRect];
[[[sender subviews] objectAtIndex:1] setFrame:rightRect];
}


//The following deals with a custom divider and the effective Rect that the divider lives in i.e. where you click with the mouse to move it left or right
- (NSRect)splitView:(NSSplitView *)splitView effectiveRect:(NSRect)proposedEffectiveRect forDrawnRect:(NSRect)drawnRect ofDividerAtIndex:(NSInteger)dividerIndex{
return NSMakeRect(drawnRect.origin.x, drawnRect.origin.y, 1, drawnRect.size.height);
}

@end



Hope this helps :)

Cheers,

A

----------

Just to add, is there any reason why you're just not making a nib with a delegate? That's a hell of a lot easier than messing about with constants :D Even if you have a custom nib, it can still be a subclass of NSSplitView :) The reason why I say go that method is because it sets up most of the constructors for you before hand rather than having manually hand-punch it all in.

ArtOfWarfare
Aug 19, 2013, 05:13 AM
The reason I'm doing it by hand is because I need to be able to generate several of these views, whereas generally I only use nibs for static parts of my interface, but now that you've suggested it, I guess I could use a nib... Maybe... I'll think about it.

ArtOfWarfare
Aug 20, 2013, 05:16 AM
Okay - I've solved the problem, and I believe I know what accounts for this behavior within Apple's own code.

When you call setPosition: ofDividerAtIndex:, it calls the delegate of the split view to check if the value you've passed in is valid. Specifically, it'll call splitView:constrainMinCoordinate: ofSubviewAt:.

I believe their exact implementation resembles something like:
CGFloat minimum = [delegate splitView:self constrainMinCoordinate:(...) ofSubviewAt:(...)];

But if delegate isn't set, this returns 0. Similarly, constrainMaxCoordinate: returns 0. Thus if there's no delegate for a split view, it will never accept widths other than 0.