View-backed NSTableView: Inserted Column Width/Margin Issues - macos

I have a view-based NSTableView that usually has one column in it. However, at a button press, I want a new column to slide in from the left (very similar to what happens on an iPhone when you click the Edit button in Mail). For now, the view that I want to slide is in a very simple view that draws a solid background: its drawRect: just does
[[NSColor blueColor] set];
[[NSBezierPath bezierPathWithRect:[self bounds]] fill];
In the delegate for my NSTableView, I have the following:
- (IBAction)buttonPressed:(id)sender
{
NSTableColumn *newColumn = [[NSTableColumn alloc] initWithIdentifier:#"InPreviewColumn"];
[newColumn setWidth:40];
[newColumn setMinWidth:[newColumn width]];
/* To make up for there not being an insertColumnAt: method,
hide the column, add it, and move it to the front before showing it. */
[newColumn setHidden:YES];
[availableFontsView beginUpdates];
[availableFontsView addTableColumn:newColumn];
[availableFontsView moveColumn:1 toColumn:0];
[availableFontsView endUpdates];
[newColumn setHidden:NO];
}
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
/* ... code for main column ... */
else if([[tableColumn identifier] isEqualToString:#"InPreviewColumn"])
{
USSolidBackgroundView *v = [tableView makeViewWithIdentifier:[tableColumn identifier] owner:self];
if(!v)
{
v = [[[USSolidBackgroundView alloc] initWithFrame:NSMakeRect(0, 0, [tableColumn width], 0)] autorelease];
[v setIdentifier:[tableColumn identifier]];
[v setAutoresizingMask:NSViewMinXMargin | NSViewWidthSizable | NSViewMaxXMargin];
}
return v;
}
}
Yet, when I do this, I end up with this result (there's a big non-blue margin between the blue part of the new column and the start of the main column):
When the additional column isn't present, there's no margin so I'm pretty sure that the problem isn't with the other view (since there's no margin when it's the only view displayed).
I've used logging statements to verify that solid color view's bounds always has a width of 40 (in its drawRect:) and, at least when the view is created, the table column has a width of 40 as well.
So where does this margin come from? No matter how I size the column, it seem that only roughly half of it is blue. So, the bigger the column, the bigger the margin.
How do I make the entire extra column blue?

The issue was my being stupid: In the view that draws the font names, I was basing the position on [self frame]. Which is wrong.
[self bounds] is the way to go. Which I knew. Can't believe I made this mistake. Amateur hour.
If you wander by this question, feel free to vote to close it or flag it or whatever it is that's supposed to work on Stackoverflow.
My apologies.

Related

Scroll NSTableView to endOfDocument not the last row

I have NSTableView with dynamic row heights. I need to scroll to the end. I tried scrollToEndOfDocument: but it gives same result as scrollRowToVisible: which is start of last row
- (void)viewDidLoad {
[super viewDidLoad];
//[[self tableView] scrollRowToVisible:[[self tableView] numberOfRows] - 1];
[[self tableView] scrollToEndOfDocument:self];
}
Your scrollRowToVisible approach should work. Here's a quick sample project that implements variable heights with a button that scrolls to the last row. That last row is fully visible after scrolling.
Update:
For table cells larger than the surrounding NSClipView, the above technique will only scroll to the top of the cell. To scroll to the bottom of the last cell, you can use:
let point = NSPoint(x: 0, y: tableView.frame.height)
tableView.scroll(point)
or since OP was in ObjC:
[[self tableView] scrollPoint: NSMakePoint(0, [self tableView].frame.size.height)]

NSScrollView starting at middle of the documentView

I have the following code:
[[ticketsListScrollView documentView] setFrame: NSMakeRect(0, 0, [ticketsListScrollView frame].size.width, 53 * [tickets count])];
[[ticketsListScrollView documentView] setFlipped:YES];
for(int i = 0; i < [tickets count]; i++) {
TicketsListViewController *viewController = [[TicketsListViewController alloc] initWithNibName:#"TicketsListViewController" bundle:nil];
viewController.dateLabelText = tickets[i][#"date"];
viewController.timeLabelText = tickets[i][#"time"];
viewController.subjectLabelText = tickets[i][#"title"];
NSRect frame = [[viewController view] frame];
frame.origin.y = frame.size.height * i;
[viewController view].frame = frame;
[[ticketsListScrollView documentView] addSubview:[viewController view]];
}
if the list is large enough (many views), the NSScrollView starts at top-left, which is great. For less views (the views do not take the whole documentView, then NSScrollView starts at the middle.
Any idea why?
Thank you!
Views are not flipped by default, which means your document view is being pinned to the lower-left corner (the default, non-flipped view origin) of the scroll view. What you're seeing is a view not tall enough to push the "top" subview to the top of the scroll view. I see you tried flipping this view, so you already know about this, but you're not doing it correctly.
I'm not sure why you're not getting an error or a warning when calling -setFlipped: since the isFlipped property is read-only. In your document view (the view that's scrolled, and in which you're placing all those subviews), you can override it:
- (BOOL)isFlipped {
return YES;
}
Of course you'll have to put this in a custom NSView subclass and set that as your scroll view's document view's class in IB if you're not creating it at runtime. You'll also need to adjust the frames you use for layout, since you're currently expressing them in the coordinate system of the scroll view's frame. You should be expressing them in your container/layout view's bounds coordinates, which will also be flipped, and so, likely different from your scroll view's coordinates. You'll also need to implement -intrinsicContentSize (and call -invalidateIntrinsicContentSize when adding/removing subviews) so auto-layout can size the container appropriately.

NSTableView's frame inside a NSClipView/NSScrollView using auto layout

I'm trying to create a NSTableView inside a NSScrollView (the standard configuration, that is) in code, using auto layout. I can't figure out how to make this work.
Here's my loadView:
- (void)loadView
{
NSView *view = [[NSView alloc] init];
NSScrollView *tableScroll = [[NSScrollView alloc] init];
NSTableView *fileTable = [[NSTableView alloc] init];
[tableScroll setDocumentView:fileTable];
[tableScroll setHasVerticalScroller:YES];
[tableScroll setHasHorizontalScroller:NO];
fileTable.delegate = self;
fileTable.dataSource = self;
[fileTable setHeaderView:nil];
[fileTable setAllowsColumnReordering:NO];
NSTableColumn *column = [[NSTableColumn alloc] initWithIdentifier:#"column1"];
[fileTable addTableColumn:column];
[tableScroll setTranslatesAutoresizingMaskIntoConstraints:NO];
[fileTable setTranslatesAutoresizingMaskIntoConstraints:NO];
[view addSubview:tableScroll];
NSDictionary *topViews = NSDictionaryOfVariableBindings(tableScroll);
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[tableScroll]|" options:0 metrics:nil views:topViews]];
[view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"|[tableScroll]|" options:0 metrics:nil views:topViews]];
self.fileTable = fileTable;
self.view = view;
}
What happens is that my table view's frame will be always equal to the bounds of the NSClipView. The view is inside a window and gets resized with it, and when I do that it'll resize the scrollview, the clip view and the table, but I can never scroll anywhere.
Looking at constraints I get, the NSScrollView gets constraints that set the clip view to fill it, the clip view has no constraints at all and the table view has a bunch of constraints related to the NSTableRowViews inside it.
If I add a constraint like |[fileTable(>=500)] to the clip view I'll get 500 pixels of NSTableView, but obviously I don't want to do that.
Even though this was answered by the poster in the comments above, I thought I’d put the answer here (having run into the same issue). If you are adopting auto layout, you would typically uncheck “Translates Mask Into Constraints” in the xib. However, for classes like NSScrollView and NSTableView, you should generally let them manage their own internal views by setting their translatesAutoresizingMaskIntoConstraints property to YES. It is still ok to set constraints that are external to these views, i.e. to resize in relation to their superview.
If you set translatesAutoresizingMaskIntoConstraints to NO, then you will need to supply constraints for all of the internal views, which unless you specifically need custom behavior (almost never), you will not want to do. This was the specific problem above.
An obvious side effect of not setting this correctly is that a table (for example) will not properly scroll beyond what is visible in the view.

How to highlight a row in NSTableView

I have NSTableView in my application. right now say is 8 columns by 48 rows.
I have a function that runs a specific a specific column to see wether or not the value in each cell is greater than a certain value. If it is I would like the application to highlight the row.
I did some reading and I am still looking for the functions calls or a process that will let me extract the cells/rows/or rect and let me change the color.
What are the functions and the steps in changing the color of the cells?
- [NSTableView selectRowIndexes:byExendingSelection:]
Source
To change the color of the cell you can try this method
-(void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
[cell setDrawsBackground:YES];
if(row==0)
[cell setBackgroundColor:[NSColor color];
else if(row==1||row==2)
[cell setBackgroundColor:[NSColor color];
else
[cell setBackgroundColor:[NSColor color];
}
this will make your row with different color.
Set a tableView delegate and implement the
– tableView:dataCellForTableColumn:row:
method. Then write a custom dataCell class to do the custom drawing.
If you extract the data as strings, you can use :-
NSAttributedString
to change the colour of both text and background.
-(void)tableView:(NSTableView *)tableView willDisplayCell:(id)cell forTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
...gives you the pre-made cell (usually a NSTextFieldCell) which you can then color. Don't need to return it - Apple has already made it and is about to use it, just modify the object as you see fit. Works great.

Is there a "right" way to have NSTextFieldCell draw vertically centered text?

I have an NSTableView with several text columns. By default, the dataCell for these columns is an instance of Apple's NSTextFieldCell class, which does all kinds of wonderful things, but it draws text aligned with the top of the cell, and I want the text to be vertically centered in the cell.
There is an internal flag in NSTextFieldCell that can be used to vertically center the text, and it works beautifully. However, since it is an internal flag, its use is not sanctioned by Apple and it could simply disappear without warning in a future release. I am currently using this internal flag because it is simple and effective. Apple has obviously spent some time implementing the feature, so I dislike the idea of re-implementing it.
So; my question is this: What is the right way to implement something that behaves exactly like Apple's NStextFieldCell, but draws vertically centered text instead of top-aligned?
For the record, here is my current "solution":
#interface NSTextFieldCell (MyCategories)
- (void)setVerticalCentering:(BOOL)centerVertical;
#end
#implementation NSTextFieldCell (MyCategories)
- (void)setVerticalCentering:(BOOL)centerVertical
{
#try { _cFlags.vCentered = centerVertical ? 1 : 0; }
#catch(...) { NSLog(#"*** unable to set vertical centering"); }
}
#end
Used as follows:
[[myTableColumn dataCell] setVerticalCentering:YES];
The other answers didn't work for multiple lines. Therefore I initially continued using the undocumented cFlags.vCentered property, but that caused my app to be rejected from the app store. I ended up using a modified version of Matt Bell's solution that works for multiple lines, word wrapping, and a truncated last line:
-(void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSAttributedString *attrString = self.attributedStringValue;
/* if your values can be attributed strings, make them white when selected */
if (self.isHighlighted && self.backgroundStyle==NSBackgroundStyleDark) {
NSMutableAttributedString *whiteString = attrString.mutableCopy;
[whiteString addAttribute: NSForegroundColorAttributeName
value: [NSColor whiteColor]
range: NSMakeRange(0, whiteString.length) ];
attrString = whiteString;
}
[attrString drawWithRect: [self titleRectForBounds:cellFrame]
options: NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin];
}
- (NSRect)titleRectForBounds:(NSRect)theRect {
/* get the standard text content rectangle */
NSRect titleFrame = [super titleRectForBounds:theRect];
/* find out how big the rendered text will be */
NSAttributedString *attrString = self.attributedStringValue;
NSRect textRect = [attrString boundingRectWithSize: titleFrame.size
options: NSStringDrawingTruncatesLastVisibleLine | NSStringDrawingUsesLineFragmentOrigin ];
/* If the height of the rendered text is less then the available height,
* we modify the titleRect to center the text vertically */
if (textRect.size.height < titleFrame.size.height) {
titleFrame.origin.y = theRect.origin.y + (theRect.size.height - textRect.size.height) / 2.0;
titleFrame.size.height = textRect.size.height;
}
return titleFrame;
}
(This code assumes ARC; add an autorelease after attrString.mutableCopy if you use manual memory management)
Overriding NSCell's -titleRectForBounds: should do it -- that's the method responsible for telling the cell where to draw its text:
- (NSRect)titleRectForBounds:(NSRect)theRect {
NSRect titleFrame = [super titleRectForBounds:theRect];
NSSize titleSize = [[self attributedStringValue] size];
titleFrame.origin.y = theRect.origin.y + (theRect.size.height - titleSize.height) / 2.0;
return titleFrame;
}
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView {
NSRect titleRect = [self titleRectForBounds:cellFrame];
[[self attributedStringValue] drawInRect:titleRect];
}
For anyone attempting this using Matt Ball's drawInteriorWithFrame:inView: method, this will no longer draw a background if you have set your cell to draw one. To solve this add something along the lines of
[[NSColor lightGrayColor] set];
NSRectFill(cellFrame);
to the beginning of your drawInteriorWithFrame:inView: method.
FYI, this works well, although I haven't managed to get it to stay centered when you edit the cell... I sometimes have cells with large amounts of text and this code can result in them being misaligned if the text height is greater then the cell it's trying to vertically center it in. Here's my modified method:
- (NSRect)titleRectForBounds:(NSRect)theRect
{
NSRect titleFrame = [super titleRectForBounds:theRect];
NSSize titleSize = [[self attributedStringValue] size];
// test to see if the text height is bigger then the cell, if it is,
// don't try to center it or it will be pushed up out of the cell!
if ( titleSize.height < theRect.size.height ) {
titleFrame.origin.y = theRect.origin.y + (theRect.size.height - titleSize.height) / 2.0;
}
return titleFrame;
}
No. The right way is to put the Field in another view and use auto layout or that parent view's layout to position it.
Though this is pretty old question...
I believe default style of NSTableView implementation is intended strictly for single line text display with all same size & font.
In that case, I recommend,
Set font.
Adjust rowHeight.
Maybe you will get quietly dense rows. And then, give them padding by setting intercellSpacing.
For example,
core_table_view.rowHeight = [NSFont systemFontSizeForControlSize:(NSSmallControlSize)] + 4;
core_table_view.intercellSpacing = CGSizeMake(10, 80);
Here what you'll get with two property adjustment.
This won't work for multi-line text, but very good enough for quick vertical center if you don't need multi-line support.
I had the same problem and here is the solution I did :
1) In Interface Builder, select your NSTableCellView. Make sure it as big as the row height in the Size Inspector. For example, if your row height is 32, make your Cell height 32
2) Make sure your cell is well placed in your row (I mean visible)
3) Select your TextField inside your Cell and go to your size inspector
4) You should see "Arrange" item and select "Center Vertically in Container"
--> The TextField will center itself in the cell

Resources