NSView-based table view with auto layout grows, but does not shrink rows - cocoa

I have a NSTableView in view-based mode (not cell-based) with usesAutomaticRowHeights=YES. The rows have dynamic height that might change at any time. This setup successfully grows table view rows (row content is never clipped), but table view rows don't shrink to the intrinsic row view height when rows get shorter. Calling noteHeightOfRowsWithIndexesChanged: on the table view after layout does not seem to fix the problem, my tableView:heightOfRow: is also not called again after noteHeightOfRowsWithIndexesChanged:.
Is there anything I have missed/isn't documented about using auto layout in NSView-based tableviews with variable row height? After all, growing rows always works without any additional code, they just do not shrink on their own.
NSTableView* tv = [[NSTableView alloc] init];
[tv setTranslatesAutoresizingMaskIntoConstraints:NO];
[tv setUsesAutomaticRowHeights:YES]; // Use auto layout for row height
NSTableColumn* column = [[NSTableColumn alloc] init];
[tv addTableColumn:column];
// Set table view data source and delegate, rows are then loaded...

After debugging the default table view layout for a long time, I finally found the issue. The table view row views come with some autoresizingmask settings that interfere with content, when using autolayout for the row content views. You need to set the row view's translatesAutoresizingMaskIntoConstraints to NO. This can be achieved for a view-based NSTableView using the delegate method tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row:
- (void)tableView:(NSTableView *)tableView didAddRowView:(NSTableRowView *)rowView forRow:(NSInteger)row
{
// Remember that rowView != row content view!
[rowView setTranslatesAutoresizingMaskIntoConstraints:NO]; // Disable constraints interfering with row view content
}
Interestingly, this does not seem to be an issue if content does not shrink past the initial row height or maximum row height it has grown to, due to dynamic height increases.

Related

How to expand table view cell and show content beneath it?

I want to have the table view cell expand and show the buttons that I have laid out below the visible view when the cell isn't selected. So far I have managed to expand the cell so that the entire view shows with the buttons, but there is one major problem with this....
The buttons that are supposed to be revealed only when the cell is selected always appear in the table, and the table view looks really weird becuase for each cell there are buttons overlapping the next cell which were supposed to be hidden!
I have tired making a subclass of the cell, but I am stuck because when I override the setSelected method to show the button, all the buttons from all the cells show up, not just the one I clicked, Ill provide my code below.
I there an easier way to show the buttons without using a subclass? And if not how could I use the subclass in a way that wouldn't show all the buttons for all the cells?
Cell Subclass (.m file)
- (void)awakeFromNib {
// Initialization code
editHidden.hidden = YES;
removeHidden.hidden = YES;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
editHidden.hidden = NO;
removeHidden.hidden = NO;
// Configure the view for the selected state
}
Your table view delegate needs to implement tableView:heightForRowAtIndexPath:. Implement this method to return the correct height for your cell given the state that it is in (collapsed or expanded). When it comes time to expand your cell you should update your state and call [tableView beginUpdates]; [tableView endUpdates]; to have it recalculate and relayout the tableview.

How to add row-span in view-based NSTableView?

I'm trying to get a table layout as shown below working with view-based NSTableViews that have been introduced in Lion.
There were multiple approaches described for cell-based NSTableViews, e.g. Mimic the artwork column, but these don't really apply for view based table views.
The idea is that the table gets populated by an array of objects, in one (or more) column spanning rows indicating that objects share some data in common. numberOfRowsInTableView: returns to total number of items (19 in the case of the attached image).
Has anyone tried something like this?
Layout
I was able to do this by using two separate NSTableViews that have their NSScrollView's scrolling synchronized. To learn how to synchronize multiple scroll view's (with the exact subclass code) read Scroll View Programming Guide for Mac - Synchronizing Scroll Views
I have groupTableView that has a single column and shows the views that represent the group. I also have itemTableView that has columns representing the items of the group. I use the same delegate/datasource methods with if-else-statements to check which NSTableView is being used and respond accordingly with the proper # of rows, cell view, etc.. Additionally, I implement the following delegate method to adjust the group table view's row height to be equal the sum of the item row heights of the rows in the group:
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
{
if (tableView == self.groupTableView) {
NSUInteger firstRowIndexForGroup = ...;
NSUInteger lastRowIndexForGroup = ...;
CGFloat groupHeight = 0.0;
for (NSUInteger currentRowIndex = firstRowIndexForGroup; currentRowIndex <= lastRowIndexForGroup; currentRowIndex++) {
groupHeight += [self.itemTableView rectOfRow:lastRowIndexForGroup].size.height;
}
return groupHeight - [self.itemTableView intercellSpacing].height;
} else {
return self.itemTableView.rowHeight;
}
}
You must also call -noteHeightOfRowsWithIndexesChanged: every time the table view needs a group view because the height changes according to the number of rows in the group.
- (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
if (tableView == self.groupTableView) {
GroupRowView *view = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
// configure view
[tableView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndex:row]];
return view;
} else {
ItemRowView *view = [tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
// configure view
return view;
}
}
I disabled row selection and horizontal and vertical scroll bars on the group table view. I also made the horizontal grid solid for both table views. Finally, I position the group table view directly next to the item table view with no gap in between so it appears as if it's simply another column in a single table view. The result is a flawless implementation that has zero lag between the table views. To the user, it appears as if it's a single table view.
Could you not use an NSOutlineView instead? It's specifically designed to have grouping support.

Multiple UITableView but some rows smaller than others

I have multiple UITableViews for layout purposes on the screen. These are functionally working fine, I'm just boggled as to why they are displaying differently.
My 3 table views are: loginTableView, forgotPass and openAccount The delegate and datasource of all 3 is file's owner. This is what I have on the viewDidLoad:
#define TABLE_CELL_HEIGHT 50;
loginTableView.rowHeight = TABLE_CELL_HEIGHT;
forgotPass.rowHeight = TABLE_CELL_HEIGHT;
openAccount.rowHeight = TABLE_CELL_HEIGHT;
The loginTableView cells show up fine but the other two tables' cells are smaller in height than it. I'm using a custom method to create the cells (same method for all tables) and I can't spot any difference between how the different tables are set up.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return TABLE_CELL_HEIGHT;
}
try implementing this method in your tableView delegate.
Ah - it looks like the correct table view had "Separator = None" set in interface builder where the other two table views did not. This was adding a separator and making them the tiniest bit smaller than the other table view.

NSTableView setting the sort column?

I have a NSTableView with multiple columns. clicking each of the columns sorts by the column like in iTunes. However when the tableview first loads the rows are unsorted and no tablecolumn is highlighted or displaying the up/down indicator image. I'm wondering if theres a simple way I can programmatically set the column the table is sorted by and set the indicator image on startup.
The only solution I can think of is using [NSTableView setIndicatorImage: inTableColumn:] and [NSTableView setHighlightedColumn:], but that makes it so that clicking on the header doesnt highlight the column. I would rather not have to use tableView:mouseDownInHeaderOfTableColumn: and rewrite the whole click on header to sort thing.
You might try setting your sort discriptor.
- (void)setSortDescriptors:(NSArray *)array
- (void)windowControllerDidLoadNib:(NSWindowController *) windowController
{
[super windowControllerDidLoadNib:windowController];
NSSortDescriptor* sortDescriptor = [[[NSSortDescriptor alloc] initWithKey: #"order" ascending: YES] autorelease];
[oTable setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
}
http://lists.apple.com/archives/cocoa-dev/2006/May/msg01434.html

How can I detect that the number of rows in an NSTableView has changed?

I have a custom NSTableView subclass which is bound to a data source (an NSArray) which updates asynchronously. When items are added to the array, rows are automatically added to the tableview. Awesome!
My question is this: How can I detect that this magic has happened so that I can perform some other tasks related to the display of my custom tableview? Is there a method that I can override in my subclass which will be called when the tableview is updated?
You don't need to subclass NSTableView to change its height based on the number of rows. In your controller, just monitor the data array using KVO and adjust the frame size of the tableview's scrollview (you can find it using enclosingScrollView) when rows are added or removed. I've used this technique before and it's worked well. You can use the tableview's rowHeight and intercellSpacing methods to calculate the height of the frame.
Looked high and low for days on this solution. It worked like a charm, thanks! Here's a sample of my code for others to follow:
// tv = NSTableView
// view = NSView
int height = ([tv rowHeight] + [tv intercellSpacing].height) * [itemNodes count];
NSScrollView *sv = [tv enclosingScrollView];
NSRect svFrame = [sv frame];
svFrame.size.height = height;
[sv setFrame:svFrame];
NSRect viewFrame = [view frame];
viewFrame.size.height = height;
[view setFrame:viewFrame];

Resources