Using NSTableView Animations with Bindings
Asked Answered
F

6

16

I have a NSTableView that is bound to a NSArrayController. The NSArrayController's contentSet property is bound to a NSMutableSet. Everything works great.

Now I want to use the animations built in to NSTableView to remove rows. I can do this with - [NSTableView removeRowsAtIndexes:withAnimation:] and the row quickly animates away, however the object I removed from the tableview is still hanging out in the NSMutableSet that is backing the tableview. Obviously I need to remove it. If I try to remove it through the NSArrayController's removeObject: method then the object disappears from the tableview immediately which means the animation doesn't occur or gets cut off halfway through.

Bindings work wonders and make things so much easier but what exactly is the proper method for keeping the data source and tableview in sync when both bindings and NSTableView animations are being used? The answer should also address how to add rows to a bound NSTableView using animations.

Finespun answered 24/11, 2012 at 13:22 Comment(5)
One possible way could be to subclass NSArrayController and override the adding and removing methods to notify your table view which object/index will be removed/added/moved. Not altogether sure if this would work or not.Geothermal
I don't think so because within the NSArrayController subclass there would still have to be a call to [super removeObject:] which would immediately remove the corresponding row from the NSTableView and causing any animation to be suppressed.Finespun
Carter: Well, you'd have to keep a separate array with content, and just use the NSArrayController as a signal to update your own content as needed. I really can't think of any way.Geothermal
@Finespun The evidence I've seen whilst poking around is that it simply isn't possible just yet, though it may become available in a future iteration of the OS. Meanwhile, a possible alternative for you to use might be NSCollectionView, which does support animation while using bindings. I think, however, that it would be understatement to say that the use of NSCollectionView introduces its own set of issues with which you'd have to contend.Ovotestis
Do you can use nsarray instead of nsmutableset, and then first call [tableview removeRow...] and then delete object from array at same index?Gigantic
M
9

The model needs to be updated right after the animation is complete:

@IBAction func onRemoveClick(sender: AnyObject?) {
    let selection = listController.selectionIndexes
    NSAnimationContext.runAnimationGroup({
        context in
        self.tableView.removeRowsAtIndexes(selection, withAnimation: .EffectFade | .SlideUp)
    }, completionHandler: {
        self.listController.removeObjectsAtArrangedObjectIndexes(selection)
    })
}

Works in my app with bindings. Tested on OS X 10.9, 10.10 & 10.11.

Mycology answered 8/9, 2015 at 0:57 Comment(0)
O
1

I've just been playing with this on OS X 10.9, and everything seems to be working fine for me. Here's my code (I have a '-' button in each row of my view-based table:

- (IBAction)removeRow:(id)sender {
    NSUInteger selectedRow = [self.myTable rowForView:sender];
    if (selectedRow == -1) {
        return;
    }
    [self.myTable removeRowsAtIndexes:[NSIndexSet indexSetWithIndex:selectedRow] withAnimation:NSTableViewAnimationSlideUp];
    [self.myArrayHookedUpToTheNSArrayController removeObjectAtIndex:selectedRow];
}

Maybe something changed in 10.9? All of this is running from the main thread, could that be why? (Have you tried calling the code inside a dispatch_async(dispatch_get_main_queue(), block())?

Ornithorhynchus answered 9/11, 2013 at 6:13 Comment(0)
E
0

When you remove item from NSTableView, you should also update your mutableSet variable. When you remove the item from mutableSet, you need to tell NSArrayController to update. To do this

[self willchangeValueForKey:@"mutableSet"]; //your mutableset variable Name
[self.myTable removeRowsAtIndexes:[NSIndexSet indexSetWithIndex:selectedRow] withAnimation:NSTableViewAnimationSlideUp];
[mutableSet removeObject:item];
[self didchangeValueForKey:@"mutableSet"];
Enclasp answered 10/4, 2014 at 23:23 Comment(0)
A
0

Found myself in this same situation: wanted to use bindings as much as possible (minimize amount of glue code) and still be able to add small pieces of logic specific to my app.

I have an NSTableView that exposes a delete button on every one of its rows. The delete button is hooked up to an IBAction on my NSViewController subclass. The table is properly bound to an NSArrayController (done in my Storyboard via IB). I also wanted an animation on row deletion.

I'm using swift (but I think it should be pretty straightforward to translate this to objective-c). The only way I got this to work with bindings, was to use a timer to deferred the deletion of the object from the NSArrayController (using half a second delay below - change it to suit your needs):

import Cocoa

class ProjectsController: NSViewController {

    @IBOutlet var arrayController: NSArrayController!
    @IBOutlet weak var tableView: NSTableView!

    @IBAction func deleteRow( object: AnyObject ) {
        let row = tableView.rowForView( object as! NSView )
        if ( row > -1 ) {
            let indexSet = NSIndexSet( index:row )
            tableView.removeRowsAtIndexes( indexSet, withAnimation: NSTableViewAnimationOptions.EffectFade )
            NSTimer.scheduledTimerWithTimeInterval( 0.5, target: self, selector: "rowDeleted:", userInfo: row, repeats: false )
        }
    }

    func rowDeleted( timer:NSTimer ) {
        let row = timer.userInfo as! Int
        arrayController.removeObjectAtArrangedObjectIndex( row )
    }
}
Axilla answered 19/8, 2015 at 20:50 Comment(0)
M
0

There were some updates on Big Sur. To get smooth animations one has to use NSAnimationContext. Running without it the NSTableView will reload all its data.

- (IBAction)delete:(nullable id)sender
{
    NSIndexSet *indexSet = [self.tableView indexesToProcessForContextMenu];
    [NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
        context.allowsImplicitAnimation = YES;
        [self.tableView removeRowsAtIndexes:indexSet withAnimation:NSTableViewAnimationEffectFade | NSTableViewAnimationSlideUp];
    } completionHandler:^{
        [self.resultsArrayController removeObjectsAtArrangedObjectIndexes:indexSet];
    }];
    
    //CODE THAT CAUSES TABLE RELOAD ON BIG SUR
    //[self.tableView removeRowsAtIndexes:indexSet withAnimation:NSTableViewAnimationEffectFade | NSTableViewAnimationSlideUp];
    //[self.resultsArrayController removeObjectsAtArrangedObjectIndexes:indexSet];
}
Myosin answered 17/7, 2021 at 14:56 Comment(0)
T
-1

It seems from discussion in the comments to your question that there isn't a simple "bindings" appropriate answer. So as a work-around couldn't you just issue a simple "performSelector:withObject:afterDelay" command right after you start you animation? Obviously the delay time would be approximate of how log the animation takes and in the selector is where you remove the object from the NSMutableSet.

Trager answered 3/12, 2012 at 12:10 Comment(1)
Prior to posting my question this is actually the approach I implemented and it works fine for removing objects, but so far as I can tell it is impossible when trying to animate rows in.Finespun

© 2022 - 2024 — McMap. All rights reserved.