For my cross-platform project (Mac & Windows), I coded a generic C++ collection view (i.e. table view) leveraging the NSStackView from the AppKIt in Objective-C (as I can mix ObjC and C++ together easily) on the Mac.
The individual rows are themselves a horizontal NSStackView, both for the header displaying the column titles and the data rows.
I also embed a search box and a scroll view to manage large number of data rows.
The actual view hierarchy is as following:
NSStackView(vertical)
L NSSearchField
L NSStackView(horizontal) - the header row
L NSScrollView(vertical)
L NSStackView(vertical)
L NSStackView(horizontal) - the data row
L ...
Here’s how it looks:
The collection view is within a split view on the right.
When I resize the left view, the collection view stick to the split line except for the header row (see below):
To display hierarchical information, I customized the NSStackView for both collection and list of rows with isFlipped returning YES.
Here’s my code:
@interface FlippedStackView : NSStackView
{
}
- ( id ) initWithBackground: ( BOOL ) value;
- ( BOOL ) isFlipped;
@end
@implementation FlippedStackView
- ( id ) initWithBackground: ( BOOL ) value
{
self = [ super init ];
if( self )
{
if( value == YES )
{
self.spacing = 0.0;
[ self.layer setBackgroundColor:[ NSColor windowBackgroundColor ].CGColor ];
}
}
return self;
}
- ( BOOL ) isFlipped
{
return YES;
}
@end
// --------------------
@implementation MacUICollectionView
- ( id ) initWithBackground: ( BOOL ) value
{
M_LOG_FUNCTION( MacUICollectionView::initWithBackground )
self = [ super init ];
if( self )
{
// General layout setting
self.translatesAutoresizingMaskIntoConstraints = NO;
self.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
self.orientation = NSUserInterfaceLayoutOrientationVertical;
self.wantsLayer = YES;
self.spacing = 0.0;
if( value == YES )
{
self.spacing = 0.0;
[ self.layer setBackgroundColor:[ NSColor windowBackgroundColor ].CGColor ];
}
// Initialize search field and header values
_searchField = nil;
_header = nil;
// Create the scroll view
NSScrollView * scrollView = [[NSScrollView alloc] init ];
scrollView.autoresizingMask = NSViewWidthSizable | NSViewHeightSizable;
scrollView.hasVerticalScroller = YES;
scrollView.autohidesScrollers = YES;
scrollView.drawsBackground = NO;
// Get the contentView of the NSScrollView (which is the NSClipView)
[ [ scrollView contentView] setDrawsBackground: NO ];
// Create stack view for rows
_stackViewForRows = [ [ FlippedStackView alloc ] initWithBackground: false ]; // Customize frame size
_stackViewForRows.translatesAutoresizingMaskIntoConstraints = NO;
_stackViewForRows.orientation = NSUserInterfaceLayoutOrientationVertical;
_stackViewForRows.spacing = 0.0;
// Add row stack view to scroll view
scrollView.documentView = _stackViewForRows;
[ self addView: scrollView inGravity:NSStackViewGravityTop ];
}
return self;
}
// --------------------
- ( BOOL ) isFlipped
{
M_LOG_FUNCTION( MacUICollectionView::isFlipped )
return YES;
}
// --------------------
- ( void ) displaySearchField: ( NSSearchField * ) value
{
M_LOG_FUNCTION( MacUICollectionView::displaySearchField )
if( _searchField == value )
return;
if( _searchField != nil )
[ self removeView: _searchField ];
if( value != nil )
{
[ self insertView: value atIndex: 0 inGravity: NSStackViewGravityLeading ];
NSNumber * hMargin = @( ( CGFloat ) kMacUIStandardPadding );
[ self addConstraints: [ NSLayoutConstraint constraintsWithVisualFormat: @"|->=hMargin-[value]->=hMargin-|"
options: 0
metrics: NSDictionaryOfVariableBindings( hMargin )
views: NSDictionaryOfVariableBindings( value ) ] ];
[ self addConstraints: [ NSLayoutConstraint constraintsWithVisualFormat: @"V:|->=hMargin-[value]->=hMargin-|"
options: 0
metrics: NSDictionaryOfVariableBindings( hMargin )
views: NSDictionaryOfVariableBindings( value ) ] ];
}
_searchField = value;
}
// --------------------
- ( void ) displayHeader: ( NSView * ) value
{
M_LOG_FUNCTION( MacUICollectionView::displayHeader )
if( _header == value )
return;
if( _header != nil )
// Remove the header
[ self removeView: _header ];
else
{
// Add the header
[ self insertView: value atIndex: ( _searchField == nil ? 0 : 1 ) inGravity: NSStackViewGravityTop ];
}
_header = value;
}
// --------------------
- ( void ) addRow: ( NSView * ) value atIndex: ( int ) index
{
M_LOG_FUNCTION( MacUICollectionView::addRow )
[ _stackViewForRows insertView: value atIndex: index inGravity: NSStackViewGravityLeading ];
}
// --------------------
- ( void ) removeRow: ( NSView * ) value
{
M_LOG_FUNCTION( MacUICollectionView::removeRow )
[ _stackViewForRows removeView: value ];
}
@end
So, my question is: how do I force the header row’s NSStackView to stick on the right ? I tried several layout constraint instructions but without success…