Row animations for NSTableView with Bindings - cocoa

I have an NSTableView which uses an array controller as binding to a dynamic array and it works great.
Now I realized that when I am appending / inserting an element to the array the row is directly added to the table view without the row insert animation. When I am often adding elements to the array the table view looks weird due to the missing animation.
Is there any "official" way or workaround to get the row animations when I am inserting or removing an element of the dynamic array?

I know this questions is pretty old, but I ran into a similar issue when trying to animate an insertion within a NSTableView which uses bindings. This is what I ended up doing:
Unbind the array controller from the tableview content.
Insert the new object into the array controller (without unbinding first this would automatically insert it into the tableview without animation).
Animate the tableview insertion.
Re-bind the array controller to the tableview content.
// insertIndex and objectToInsert established earlier
[self.myTableView unbind:NSContentBinding];
[self.myArrayController insertObject:objectToInsert atArrangedObjectIndex:insertIndex];
[NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
[self.myTableView insertRowsAtIndexes:[NSIndexSet indexSetWithIndex:insertIndex] withAnimation:NSTableViewAnimationSlideDown];
} completionHandler:^{
[self.myTableView bind:NSContentBinding toObject:self.myArrayController withKeyPath:#"arrangedObjects" options:#{NSRaisesForNotApplicableKeysBindingOption : #(YES)}];
}];

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.

Return position of selected row in NSOutlineView

I have an NSOutlineView which when a row in the outline view is double-clicked, an NSPopover is displayed using the following code:
-(void)doubleClick:(id)nid{
NSLog(#"Test double click");
[_popover showRelativeToRect:[nid bounds] ofView:nid preferredEdge:NSMaxXEdge];
}
The code works fine but places the popover in the middle of the vertical height of the outline view. I would like the popup to appear next to the row which is selected (double-clicked) in the outline view. Is there a call I can make to the Outline View to return the position of the selected row? I couldn't seem to find such a method in the documentation. Clearly I would replace [nid bounds] which such a call. Else, any other suggestions of how I could work around this would be appreciated.
Don't forget that NSOutlineView inherits from NSTableView. You're looking for -frameOfCellAtColumn:row:. The column is probably 0. The row you can obtain with -selectedRow.
If your outline view is view-based, you could also pass the row view (-rowViewAtRow:makeIfNecessary:) and NSZeroRect for the rect to -showRelativeToRect:..., so it automatically tracks the row.

NSTableView rowViewAtRow: always returning nil

I'm trying to display an NSPopover with additional information to a selected row of an NSTableView. For that I need to get a reference to the view representation of the selected row so I can "attach" my popover to it:
NSInteger row = [[self membersTableView] selectedRow];
NSTableRowView *aView = [[self membersTableView] rowViewAtRow: row makeIfNecessary: YES];
[self setQuickLookPopoverController: [QuickLookPopoverController showPopoverFor: anObject at: aView]];
In the above, the result of aView is always nil. According to Apple documentation, this is the method to obtain a view object, given a selected row. Especially the last sentence of the discussion is a bit weird:
Discussion This method will first attempt to return a currently
displayed view in the visible area. If there is no visible view, and
makeIfNecessary is YES, a prepared temporary view is returned. If
makeIfNecessary is NO, and the view is not visible, nil will be
returned.
In general, makeIfNecessary should be YES if you require a resulting
view, and NO if you only want to update properties on a view only if
it is available (generally this means it is visible).
An exception will be thrown if row is not within the numberOfRows. The
returned result should generally not be held onto for longer than the
current run loop cycle. It is better to call
rowViewAtRow:makeIfNecessary: whenever a view is required..
Why is this method always returning nil?
Solved it. I used NSTableView's method (NSRect) rectOfRow: (NSInteger) rowIndex which will give the frame of the required row.
Thanks for showing me the right direction, I had the same problem! I ended up doing the following, but note that I disabled selection of empty rows and that the following code is inside an IBAction:
[popOver showRelativeToRect:[sender bounds]
ofView:[sender rowViewAtRow:[sender selectedRow]
makeIfNecessary:YES]
preferredEdge:NSMaxXEdge];

NSTableView content view inset

I am looking to inset the contents of an NSTableView so that there is a gap between the top of the table view and the first cell.
On iOS this is easy with UITableView - achieved by using setContentInset:.
Turn headers back on and substitute the header view with your own subclass. Override its -drawRect: to draw only your background color. Also override -headerRectOfColumn: to prevent any of the column headers from being drawn. I'm not sure if this prevents column dragging or sorting but I'll bet it does.
The question asked how to adjust content insets similar to iOS. The currently selected answer shows how to move the first row down, but that's not quite the same thing. Adjusting the content insets will also move the start of the scrollbar to the inset position, just like iOS. This is useful when placing content underneath a "vibrant" or transparent toolbar.
An NSTableView itself does not have content insets. On macOS content insets are usually part of NSScrollView. To get access to the scroll view of NSTableView's view controller you can use the enclosingScrollview method of NSView, disable automatic adjustment and set the insets like this:
(warning old school Obj-C here)
self.enclosingScrollView.automaticallyAdjustsContentInsets = NO;
self.enclosingScrollView.contentInsets = NSEdgeInsetsMake(50.f,0.f,0.f,0.f);
Calling these from viewDidLoad is usually fine, however some types of table views will override your values with their own.
NSOutlineView set to source-list mode comes with lots of default values overridden to make the view look like the Finder sidebar.
There is no "clean" way to set the content-insets of these views. They stubbornly override your values, I've found that if you subclass NSOutlineView and overload setFrameSize: it will do the trick. So like this (inside the NSOutlineView subclass):
- (void)setFrameSize:(NSSize)newSize {
[super setFrameSize:newSize];
self.enclosingScrollView.automaticallyAdjustsContentInsets = NO;
self.enclosingScrollView.contentInsets = NSEdgeInsetsMake(100.f,0.f,0.f,0.f);
}
This will do the trick, but the initial scroll position will be strange. Calling scrollToBeginningOfDocument: from the initWithCoder: method of the subclass will scroll it to the correct initial position.
It's not very clean but you can achieve that by having the first row higher than the rest. Implement heightOfRow table delegate method:
- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row
{
if (row == 0) {
return normalRowHeight + topPadding;
} else {
return normalRowHeight;
}
}
The drawback is that you would also need to implement custom highlighting and custom cell drawing to take into account the extra space for the first row.
scrollView.automaticallyAdjustsContentInsets = false
scrollView.contentInsets = NSEdgeInsets(top: 40, left: 0, bottom: 0, right: 0)

How to find NSOutlineView row index when using NSTreeController

I'm using an NSTreeController to manage nodes for an NSOutlineView. When the user adds a new item, I create a new object and insert it:
EntityViewEntityNode *newNode = [EntityViewEntityNode nodeWithName:#"New entity" entity:newObject];
// Insert at end of group
//
NSIndexPath *insertAt = [pathOfGroupNode indexPathByAddingIndex:[selected.children count]];
[entityCollectionTreeController insertObject:newNode atArrangedObjectIndexPath:insertAt];
Now I'd like to open the table column for edit so the user can name the new item. This seems logical:
NSInteger row = [entityCollectionOutlineView rowForItem:newNode];
[entityCollectionOutlineView editColumn:0 row:row withEvent:nil select:YES];
However, row is always -1 indicating the object isn't found. Poking around reveals that the tree controller is not actually putting my objects directly in the tree, but is wrapping them in a node object of its own.
Anyone have insight into how I would go about getting a row index relative to the outline view, so I can do this (without, hopefully, enumerating everything in the outline view and figuring out the mapping back to my node?)
When you insert a new node it gets selected automatically in the outline view. So you can get the proper row by using...
NSInteger selectedRow = [entityCollectionOutlineView selectedRow];

Resources