NSTableView double click action not being called - cocoa

I have an NSViewController which is set as the datasource and delegate of a single column tableview which uses a NSTableCellView subclass. I want to respond to a double-click on a row so I've set the table column to be not editable and have the following code:
myViewController.h
- (IBAction)didDoubleClickFolderRow:(id)sender;
myViewController.m
- (void)awakeFromNib
{
[self.table setDoubleAction:#selector(didDoubleClickFolderRow:)];
[self.table setTarget:self];
}
- (IBAction)didDoubleClickFolderRow:(id)sender
{
NSLog(#"yep a row was double-clicked!");
}
Yet the method never gets called. What am I missing?

Rather obscurely this turns out to have been caused by me overriding resignFirstResponder on another view, to get around another problem.
Not sure why a double click on a tableview fires a resignFirstResponder, but it seems to.

Related

SelectedRow of NSOutlineView always returns -1

I have a View-based NSOutlineView, and have in the class a selection change event:
- (void)outlineViewSelectionDidChange:(NSNotification *)notification
{
NSLog(#"Selected Row inside:%ld",[self selectedRow]);
}
This is the way I create my NSOutlineView:
ovc = [[OutlineViewController alloc] init];
[myOutlineView setDelegate:(id<NSOutlineViewDelegate>)ovc];
[myOutlineView setDataSource:(id<NSOutlineViewDataSource>)ovc];
MyOutlineView is created in IB.
Every time I click on a row, the event gets fired, however the result is always -1.
NSLog(#"Item 0:%#",[self viewAtColumn:1 row:0 makeIfNecessary:YES]);
Always returns NULL.
Is there something specific I should do ? Thanks.
=== EDIT ===
I have published my simplified code showing the issue: http://www.petits-suisses.ch/OutlineView.zip
Instead of checking the selectedRow of self object, which is just a object initialized in AppController which is a wrong instance. You need to check on the notification object as shown below.
NSLog(#"Selected Row:%ld",[[notification object] selectedRow]);
Also clickedRow is meaningful in the target’s implementation of the action. So the clickedRow gives correct value if checked inside a Action or DoubleAction method.
Your NSOutlineView "Controller" class is actually a subclass of NSOutlineView, this is different then the NSOutLineView in your XIB file. If you look at the notification object being sent it is an instance of NSOutlineView, not "OutlineViewController", therefore you are calling selectedRow on an incorrect instance.
This code should be place in a subclass of NSViewController as opposed to NSOutlineView. Then create an outlet from the outlineView to the controller.

View-based NSTableView + NSButton

I've got a view-based NSTableView, using Cocoa Bindings to change the values of some labels and images in the cell. It all works great. However, I want to add a button to the cell. I've got the button working, but its action method only has the button as sender, which means I have no idea of the content of the cell that the button is in. Somehow I need to store some extra data on the button - at the very least the row index that the button is in. I subclassed NSButton and used my subclass in the cell, but Interface Builder doesn't know about the extra property so I can't bind to it. If I wanted to bind it in code, I don't know the name of the object or keypath that would be passed to it.
How can I get this to work?
You can use rowForView in your action method to get the row value
- (IBAction)doSomething:(id)sender
{
NSInteger row = [_myTableView rowForView:sender];
}
You can use the Identity field in Interface Builder to associate a table cell view from the nib with an instance in your code:
Additionally you have to implement - tableView:viewForTableColumn:row: in your table view's delegate. (Don't forget to connect the delegate in IB)
- (NSView*)tableView:(NSTableView*)tableView viewForTableColumn:
(NSTableColumn*)tableColumn row:(NSInteger)row
{
SSWButtonTableCellView *result = [tableView makeViewWithIdentifier:#"ButtonView" owner:self];
result.button.title = [self.names objectAtIndex:row][#"name"];
result.representedObject = [self.names objectAtIndex:row];
return result;
}
I added representedObject property in my NSTableCellView subclass, which I set in the above table view delegate method.
Your custom table cell view can later use that object in it's action. e.g.:
- (IBAction)doSomething:(id)sender
{
NSLog(#"Represented Object:%#", self.representedObject);
}

mouseDown: in a custom NSTextField inside an NSTableView

I have a view-based NSTableView. Each view in the table has a custom text field.
I'd like to fire an action when the user clicks on the text field (label) inside the table's view (imagine having a hyperlink with a custom action in each table cell).
I've created a basic NSTextField subclass to catch mouse events. However, they only fire on the second click, not the first click.
I tried using an NSButton and that fires right away.
Here's the code for the custom label:
#implementation HyperlinkTextField
- (void)mouseDown:(NSEvent *)theEvent {
NSLog(#"link mouse down");
}
- (void)mouseUp:(NSEvent *)theEvent {
NSLog(#"link mouse up");
}
- (BOOL)acceptsFirstResponder {
return YES;
}
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent {
return YES;
}
#end
Had the same problem. The accepted answer here didn't work for me. After much struggle, it magically worked when I selected "None" as against the default "Regular" with the other option being "Source List" for the "Highlight" option of the table view in IB!
Edit: The accepted answer turns out to be misleading as the method is to be overloaded for the table view and not for the text field as the answer suggests. It is given more clearly at https://stackoverflow.com/a/13579469/804616 but in any case, being more specific feels a bit hacky.
It turned out that NSTableView and NSOultineView handle the first responder status for NSTextField instances differently than for an NSButton.
The key to get the label to respond to the first click like a button is to overwrite [NSResponder validateProposedFirstResponder:forEvent:] to return YES in case of my custom text field class.
Documentation:
http://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSResponder_Class/Reference/Reference.html#//apple_ref/occ/instm/NSResponder/validateProposedFirstResponder:forEvent:
The behavior that you're seeing is because the table view is the first responder, which it should be or the row won't change when you click on the label -- this is the behavior that a user expects when clicking on a table row. Instead of subclassing the label, I think it would be better to subclass the table view and override mouseDown: there. After calling the super's implementation of mouseDown:, you can do a hit test to check that the user clicked over the label.
#implementation CustomTable
- (void)mouseDown:(NSEvent *)theEvent
{
[super mouseDown:theEvent];
NSPoint point = [self convertPoint:theEvent.locationInWindow fromView:nil];
NSView *theView = [self hitTest:point];
if ([theView isKindOfClass:[NSTextField class]])
{
NSLog(#"%#",[(NSTextField *)theView stringValue]);
}
}
#end
In the exact same situation, embedding an NSButton with transparent set to true/YES worked for me.
class LinkButton: NSTextField {
var clickableButton:NSButton?
override func viewDidMoveToSuperview() {
let button = NSButton()
self.addSubview(button)
//setting constraints to cover the whole textfield area (I'm making use of SnapKit here, you should add the constraints your way or use frames
button.snp_makeConstraints { (make) -> Void in
make.edges.equalTo(NSEdgeInsetsZero)
}
button.target = self
button.action = Selector("pressed:")
button.transparent = true
}
func pressed(sender:AnyObject) {
print("pressed")
}
You use window.makeFirstResponser(myTextfield) to begin editing the text field. You send this message from the override mouseDown(withEvent TheEvent:NSEvent) method

NSButton in NSTableCellView: How to find desired objectValue?

I have a view-based NSTableView that is populated through bindings. My textFields & imageViews are bound to the NSTableCellView's objectValue's properties.
If I want to have an edit/info button in my NSTableCellView:
Who should be the target of the button's action?
How would the target get the objectValue that is associated with the cell that the button is in?
I'd ultimately like to show a popover/sheet based on the objectValue.
I found an additional answer: The Answer above seems to assume you're using bindings on your table view. Since I'm kind of a noob I found a way to get the button inside the table view cell.
- (IBAction)getCellButton:(id)sender {
int row = [xmlTable rowForView:sender];
}
This way when you click on the button inside the row, you don't have to have the row selected. It will return the int value of the row to match up with a datasource in an array without bindings.
Your controller class can be the target. To get the object value:
- (IBAction)showPopover:(id)sender {
NSButton *button = (NSButton *)sender;
id representedObject = [(NSTableCellView *)[button superview] objectValue];
}
Or, use a subclass of NSTableCellView, make the cell view the target of the button's action, and call [self objectValue] to get the object.

Disable/Enable NSButton if NSTextfield is empty or not

I´m newbie with cocoa. I have a button and a textField in my app. I want the button disabled when the textfield is empty and enabled when the user type something.
Any point to start? Any "magic" binding in Interface Builder?
Thanks
[EDITED]
I´ve tried to set the appDelegate as the NSTextfield´s delegate and added this method (myTextfield and myButton are IBOutlets):
- (void)textDidChange:(NSNotification *)aNotification
{
if ([[myTextField stringValue]length]>0) {
[myButton setEnabled: YES];
}
else {
[myButton setEnabled: NO];
}
}
But nothing happens...
I´ve tried to set the appDelegate as the NSTextfield´s delegate and added this method (myTextfield and myButton are IBOutlets):
- (void)textDidChange:(NSNotification *)aNotification
{
if ([[myTextField stringValue]length]>0) {
[myButton setEnabled: YES];
}
else {
[myButton setEnabled: NO];
}
}
That's the hard way, but it should work just fine. Either you haven't hooked up the text field's delegate outlet to this object, you haven't hooked up the myTextField outlet to the text field, or you haven't hooked up the myButton outlet to the button.
The other way would be to give the controller a property exposing the string value, bind the text field's value binding to this stringValue property, and bind the button's enabled binding to the controller's stringValue.length.
You could also give the controller two properties, one having a Boolean value, and set that one up as dependent upon the string property, and bind the button to that. That's a cleaner and possibly more robust solution, though it is more work.
Here's a solution using bindings.
Below I setup a NSTextField that is bound to the file owner's "text" property. "text" is a NSString. I was caught by "Continuously Updates Value". Thinking my solution didn't work but really it wasn't updating as the user typed, and only when the textfield lost focus.
And now setting up bindings on the button, simply set its enabled state to the length of the file owner's text property.
Annd, the working product.
If you use controlTextDidChange instead of textDidChange, you can get rid of the notification stuff and just rely on being the NSTextField's delegate.
Thanks Peter. What I missed (in my hard way version) is this piece of code in the awakeFromNib in the appDelegate:
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(textDidChange:) name:NSControlTextDidChangeNotification object:myTextField];
It works perfect. Now I´m trying the easy way, but I´m afraid I´m not still good enough with the bindings.
To bind the property
#property (retain) IBOutlet NSString *aStringValue;
with the textfield´s value, what I have to use in IB for "Bind to:", "Controller Key" and "Model Key Path"?

Resources