How can I make the button inside the NSTableViewRow respond to the presented object
I have been struggling with this problem for quite some time. I am working on the file manager module, so far I could do everything fine except for the cancel button. For some reason, when I click the cancel button for a specific row, the button action targets multiple rows at the same time.
After days of researching the problem, I was able to get the object to successfully cancel the operation represented by the string using:
-(IBAction)btnCancelOperationClick:(id)sender {
NSInteger row = [_tableView rowForView:sender];
if (row != -1) {
FileCopyOperation *opr = [_fileCopyOperations objectAtIndex:row];
[opr cancel];
[_fileCopyOperations removeObjectAtIndex:row];
[_tableView removeRowsAtIndexes:[NSIndexSet indexSetWithIndex:row] withAnimation:NSTableViewAnimationEffectFade];
}
}
This works well, I can undo the operation and update the table. Everything else works as intended, but there must be something wrong with my code or bindings. I load this cell from the tip and then register this icon using:
[_tableView registerNib:[[NSNib alloc]initWithNibNamed:@"FileCopyCell" bundle:nil] forIdentifier:@"FileCopyCell"];
I made the QueueController a file owner and connected everything like this:
I would really appreciate it if someone could point me in the right direction for this to work correctly. Thanks in advance!
Edit to add more sample code.
-(NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row {
FileCopyCell *cell = [tableView makeViewWithIdentifier:@"FileCopyCell" owner:self];
FileCopyOperation *opr = [_fileCopyOperations objectAtIndex:row];
[cell.fileName setStringValue:[NSString stringWithFormat:@"Copying \"%@\"",opr.fName]];
[cell.progressBar setDoubleValue:((opr.bWritten.doubleValue / opr.fSize.doubleValue) * 100)];
[cell.totalBytes setStringValue:[NSString stringWithFormat:@"of %@",[NSByteCountFormatter stringFromByteCount:opr.fSize.longLongValue countStyle:NSByteCountFormatterCountStyleFile]]];
[cell.status setStringValue:[NSString stringWithFormat:@"%@",[NSByteCountFormatter stringFromByteCount:opr.bWritten.longLongValue countStyle:NSByteCountFormatterCountStyleFile]]];
[cell.icon setImage:[[NSWorkspace sharedWorkspace]iconForFile:opr.srcURL.path]];
[cell.cancelButton setTarget:self];
return cell;
}
-(void)windowDidLoad {
[super windowDidLoad];
_fileCopyOperations = [NSMutableArray new];
windowFrame = [self.window frame];
rows = 0;
[_tableView registerNib:[[NSNib alloc]initWithNibNamed:@"FileCopyCell" bundle:nil] forIdentifier:@"FileCopyCell"];
if (!fileCopyManager) {
fileCopyManager = [FileCopyManager sharedFileCopyManager];
[fileCopyManager.fileCopyQueue addObserver:self forKeyPath:@"operationCount" options:NSKeyValueObservingOptionNew context:(void*)fileCopyManager];
}
[_scrollView setHasHorizontalScroller:NO];
[_scrollView setHasVerticalScroller:NO];
}
source to share
The best approach is to subclass NSTableCellView
and let you handle its own actions and the object it represents. For example, a cell representing an instance Foo
can have two properties: Foo
and fooController
. When the ( nonatomic
) Foo
setter is called , the cell can update its own UI to represent the passed in Foo
. When the controller Foo
creates a table cell, it can instantiate itself FooCell
, set itself as fooController
and assign an instance, Foo
and let the cell itself handle itself. The target of the Undo button can be its own cell, and when an action is called -cancel:
it can specify its own fooController
what to do (since the controllerFoo
responsible for updating the queue and table view), and since it is referencing its own Foo
, it can pass that to the controller with some method -cancelFoo:(Foo *)theFoo
, without relying on the controller to look up its index (which might be inaccurate if you animate rows appearing and disappearing. or the user quickly unbinds on the row, but their deletion is delayed and updated asynchronously).
Nice and clean. Clear and orderly containment / separation of duties. The cell handles its own updates and user actions and knows its own foo; the controller foo processes its collection foo, its table view, and the assignment of foos to foo's cells.
source to share
Thanks to Joshua Nozzi, following his recommendation, I moved the button action from a controller to a cell class. In the class, I used the method below to access the presented object and send a message [operation cancel]
.
-(IBAction)cancelOpr:(id)sender {
NSButton *button = (NSButton*)sender;
FileCopyOperation *opr = [(NSTableCellView*)[button superview]objectValue];
[opr cancel];
// This code calls the controller [removeObject:] method that takes care of cleaning everything out and updates the GUI accordingly.
AppDelegate *ad = (AppDelegate*)[[NSApplication sharedApplication]delegate];
QueueWindowController *qc = [ad getQueueController];
[qc.fileCopyOperations performSelectorOnMainThread:@selector(removeObject:) withObject:opr waitUntilDone:YES];
}
You can get the complete project here .
source to share