Custom NSView with subview of NSSplitView

Discussion in 'Mac Programming' started by ArtOfWarfare, Aug 18, 2013.

  1. macrumors 603

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #1
    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):

    Code:
    - (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]];
        [color=RED][self.verticalSplitView setPosition:40 ofDividerAtIndex:1];
        [self.verticalSplitView setPosition:20 ofDividerAtIndex:0];[/COLOR]
    }
    
    - (void)drawRect:(NSRect)dirtyRect
    {
        [[NSColor redColor] setFill];
        NSRectFill(dirtyRect);
    }
    I also wrote this category method on NSLayoutConstraint:

    Code:
    + (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.
     
  2. macrumors member

    Joined:
    Aug 14, 2011
    #2
    What you're looking for is:

    setPositionOfDividerAtIndex:

    i.e.

    http://developer.apple.com/library/...stm/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:


    Code:
    #import <Foundation/Foundation.h>
    
    @interface SplitDelegate : NSObject <NSSplitViewDelegate>
    
    @end
    
    SplitDelegate.m file

    Code:
    #import "SplitDelegate.m"
    
    @implementation SplitDelegate
    
    -(CGFloat)splitView:(NSSplitView *)splitView constrainMaxCoordinate:(CGFloat)proposedMaximumPosition ofSubviewAt:(NSInteger)dividerIndex{
        return 200.0; [B]//controls the MAX position of a split (use an if else or switch block to deal with the dividerIndex)[/B]
    }
    
    -(CGFloat)splitView:(NSSplitView *)splitView constrainMinCoordinate:(CGFloat)proposedMinimumPosition ofSubviewAt:(NSInteger)dividerIndex{
        return 200.0;[B]//controls the MIN position of a split (use an if else or switch block to deal with the dividerIndex)[/B]
        
    }
    
    -(CGFloat)splitView:(NSSplitView *)splitView constrainSplitPosition:(CGFloat)proposedPosition ofSubviewAt:(NSInteger)dividerIndex{
        return 200.0;  [B]//This deals with a FIXED splits (so for multiple use an if else or switch block to handle divider Index)[/B]
    }
    
    
    [B]//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[/B]
    -(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];
    }
    
    
    [B]//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[/B]
    - (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.
     
  3. thread starter macrumors 603

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #3
    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.
     
  4. thread starter macrumors 603

    ArtOfWarfare

    Joined:
    Nov 26, 2007
    #4
    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:
    Code:
    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.
     

Share This Page