How to use autolayout with view-based NSTableView when view are provided by an NSViewController?
Asked Answered
N

2

14

I have made the following example app to illustrate my question.

  • The left view is a place holder view (added in Interface Builder). When the App loads I add a subview managed by a NSViewController. The NSViewController draws the different coloured rectangles, each of which is a NSView, and the layout of these coloured views managed by constraint created programmatically and added to the -loadView method of the controller.

  • The right view is an NSTableView (added in Interface Builder). When the App loads I use the same NSViewController class to provide views to for the table view (only one row is added).

When I add the subview the the place holder view I also add two additional constraints,

[_placeholderView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[CTRL_VIEW]|" options:0 metrics:nil views:views]];
[_placeholderView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[CTRL_VIEW]|" options:0 metrics:nil views:views]];

These constraints set the frame of the subview to be equal to the bounds of the superview. All is good.

However, when I provide the view for the NSTableView using the delegate method -tableView:viewForTableColumn:row:, the view has not yet been added to the table. As such it doesn't have a superview, so constraints cannot (yet) be added to the view. This is why the view in the table view does not have the same bounds as the table view cell.

So my question is how can I add constraints to the view I supply to the table view? Can I access the view again after the table view has added it? This seems a bit hack-ish.

Two views using autolayout.

The source code for the AppDelegate.h,

#import <Cocoa/Cocoa.h>
@class BlahViewController;

@interface AppDelegate : NSObject <NSApplicationDelegate, NSTableViewDataSource, NSTableViewDelegate>

@property (assign) IBOutlet NSWindow *window;

/* Left view controller and place holding view */
@property (strong) BlahViewController *viewController;
@property (weak) IBOutlet NSView *placeholderView;

/* Right view (which is an NSTableView) */
@property (weak) IBOutlet NSTableView *tableView;


@end

and AppDelegate.m,

#import "AppDelegate.h"
#import "BlahViewController.h"

@interface AppDelegate ()
@property NSMutableArray *tableData;
@end

@implementation AppDelegate

- (id)init
{
    self = [super init];
    if (self) {

        _tableData = [[NSMutableArray alloc] init];
        [_tableData addObject:[[BlahViewController alloc] initWithNibName:@"BlahViewController" bundle:nil]];
        [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"NSConstraintBasedLayoutVisualizeMutuallyExclusiveConstraints"];
    }
    return self;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {

    /* Add a managed subview to the place holder view*/
    _placeholderView.layer.backgroundColor = CGColorCreateGenericGray(0.5, 1.0);
    _viewController = [[BlahViewController alloc] initWithNibName:@"BlahViewController" bundle:nil];
    [_viewController.view setFrame:_placeholderView.bounds];
    [_placeholderView addSubview:_viewController.view];

    /* Additional constraints so the managed subview resizes with the place holder view */
    NSDictionary *views = @{ @"CTRL_VIEW" : _viewController.view };
    [_placeholderView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[CTRL_VIEW]|" options:0 metrics:nil views:views]];
    [_placeholderView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[CTRL_VIEW]|" options:0 metrics:nil views:views]];

}

-(NSInteger) numberOfRowsInTableView:(NSTableView *)tableView
{
    return [_tableData count];
}

-(id) tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
    return [_tableData[row] view];
}


-(CGFloat) tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row {
    return 150.;
}

@end

Update @jrturton

Adding the constraints in -(void) tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row worked.

With constraints added.

@swalkner

The constraints I added are very basic,

-(void) tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row {
NSView *view = [rowView viewAtColumn:0];
NSDictionary *views = NSDictionaryOfVariableBindings(view);
[view.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-12-[view]-12-|" options:0 metrics:nil views:views]];
[view.superview addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-12-[view]-12-|" options:0 metrics:nil views:views]];
}
Notecase answered 13/1, 2013 at 5:17 Comment(2)
how does your tableView:didAddRowView:forRow: look like? I'd like to achieve the same thing but don't get it to work...Akee
Noting special. Literally just apply the constraints to the view (see above I made an edit for you).Notecase
C
6

You can access the view after it has been added to the table in the delegate method tableView:willDisplayCell:forTableColumn:row:. At this point you can add the constraints, but you might want to be careful not to keep adding the same constraints over and over again.

As you found out yourself, the method tableView:didAddRowView:forRow: would be a better place, as this will only get called once per new view.

You may also be able to achieve this by setting a flexible width autoresizing mask on your view instead of any horizontal size constraints. The table will probably be taking this into account when adding cell views. You can mix and match autoresizing and constraints if you're careful.

I know some iOS, some Autolayout and only a little OS X so I can't give you much more than that. We don't have to worry about cell widths changing much in iOS land!

Clarita answered 13/1, 2013 at 7:20 Comment(6)
Thanks for the advice from iOS world :) The more I learn about iOS, the more jealous I get. It seems like a much more modern API. I think OSX is catching up. New in Lion I found tableView:didAddRowView:forRow: in NSTableViewDelegate following your advice.Notecase
That sounds perfect. I'll update my answer to include that, if it has worked for you? That way future readers won't have to hit the comments.Clarita
Yep, this answered the problem, tableView:didAddRowView:forRow: seems to be called after adding the view but before the run loop ends. I added the constraints I needed and it seems to work.Notecase
I've been pulling my hair out on this for a while, its just plain ridiculous that it has not been properly documented by apple. It such a common use case.Karolekarolina
tableView:willDisplayCell:forTableColumn:row: is called for cell-based NSTableView, so it's not a good solution for dynamically changing constraints.Trstram
Sorry, I meant that it's only called for cell-based tables, and it's not called for view-based tables.Trstram
C
0

macOS 10.13 seems to include this capability out of the box with usesAutomaticRowHeights, but it is does not yet have explanation in the Cocoa docs:

https://developer.apple.com/documentation/appkit/nstableview/2870126-usesautomaticrowheights

Also, this is featured in IB as a configuration setting on cell view height.

I haven't been able to verify, but maybe someone can.

Cyanine answered 6/9, 2017 at 20:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.