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.
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.
@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]];
}
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With