NSButton in NSTableCellView: How to find desired objectValue? - cocoa

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.

Related

Custom NSView in NSTableView not showing all subviews

I'm trying to create a custom NSView to display in a column in a view-based NSTableView. The view contains 2 subviews, an NSTextField and an NSButton. I want the button to stay the width set by the constraints, and the textfield to resize when the NSView is resized. Below is a small animation showing the NSView and its subviews, and the constraints I created.
As you can see, resizing the NSView works as expected.
Now, when displaying this custom NSView in an NSTableView, it looks as if the button just disappears, and resizing the column makes the textfield resize with it (the 'Category' column).
The coded used to create the NSView in tableView:viewForTableColumn:row:
let identifier = tableColumn!.identifier
if identifier == "Category" {
var view = tableView.makeViewWithIdentifier(identifier, owner: self) as? TableCategoryView
if view == nil {
view = TableCategoryView(frame: tableView.frame)
view!.identifier = identifier
}
return view
}
The strange thing is, when there are no constraints on the 2 views, the button and textfield are both happily displayed inside the column, but they then of course don't resize with the table column width.
What am I doing wrong?
EDIT: It looks like something else is wrong. The NSView itself isn't resizing at all with the table column.
I think TableCategoryView is your new class and it is subclassed from NSView and shall replace the NSTableCellView you get when creating (in IB) a view based NSTableView. If you really want to create your own TableCellView it should be a direct subclass of NSTableCellView not NSView.
But in your case (add a button to the TableCellView) you do not need to create a new class. The existing TableCellView object already has a TextField (a property) with the name textField. Then simply drag (means: add) a button into the existing TableCellView (resize it and set the constraints) and drag a link from the button to a corresponding method in the delegate of the TableView. In the "corresponding method" you can ask for the clicked row and column and identify the click button. I did so for a TableView and for me it works well.

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);
}

How difficult is to implement a custom NSCell with a NSStepper and a label ?

How difficult is to implement a custom NSCell with a NSStepper and a label displaying the value incremented/decremented by the stepper?
I can't add subviews to it, so how can I add such subcomponents?
thanks
Yes it is possible that you can take NSStepperCell in your NSTableView. But for this value to be appear in your NSTableView, You have to take two NSTableColumn in one column drag and drop NSStepperCell and in another by default it will be textcell so let it be. Now bind both your column to array controller with model keypath. In NSStepperCell model key path should be float value and other will be sinple string value. Once you done this then write one action method and bind to the NSStepperCell. Below is the action method written to appear the value.
-(IBAction)doIncrement:(id)sender
{
step+=1.0; ///This step is the float value of NSStepperCell and binded with arrayController
NSNumber *num=[NSNumber numberWithFloat:step];
NSMutableDictionary *dict=[NSMutableDictionary dictionary];
for(dict in [arrayController arrangedObjects])
{
[dict setObject:num forKey:#"displayStepperValue"]; //#"displayStepperValue" this is another column textcell which will print steppercell value when you click on the arrows of NSStepperCell.
}
[arrayController rearrangeObjects]; //here refershing the arraycontroller
}

Binding a checkbox to Shared User Defaults

I am building a table to display some data in my preferences pane. All of the data lives in NSUserDefaults. There is a checkbox in the table that will enable/disable data for the listed device. The checkbox is the only cell that is editable.
The table is correctly displaying the data from the Shared User Defaults. So I know that I have the table content properly mapped to the correct Shared User Defaults Model Key Path. However, when I toggle the checkbox, the new data is not being written to the defaults at all.
Here is a glimpse at the checkbox setup...
I have tried assigning a selector action to the NSButton (checkbox), thinking that I could set the default programmatically. Oddly enough, the action never gets triggered. I setup a simple action that just did an NSLog. It never got fired when clicking the checkbox.
Update: So that you can see what my defaults data structure look like, here is the output from the defaults command. There isn't really any code behind this table.
{
ClimateDeviceData = (
{
deviceName = Nest;
deviceSetting = "76";
display = 1;
structure = Home;
uuid = d01AA02AB145204VR;
}
);
ClimateLoginAtLaunch = 1;
ClimateMenuBarIconStyle = "Nest Temp Settings";
}
Update #2: At this point I would accept a solution on simply being able to invoke a selector from the Check Box.
Should you not bind the NSButton (checkbox) to the Shared User Defaults Controller instead of what it is pictured, the Table Cell View?
I am doing roughly the same thing in an app. Not exactly, but basic principle is the same. A table populated with bindings, and a button in there (Can be a check box or another button, doesn't matter).
I tried the action on the button, but it didn't work either, so at the end, I used KVC concept.
I use an arrayController in the XIB referencing a mutable array in my code that stores several instances of a custom object that has a status property (boolean).
The view based tableview is bound to the arrayController using the arrangedObjects controller key.
The button is bound to the tableCellView (the arranged objects), using that keyPath: objectValue.status (effectively fetching the status property of the custom object on that line).
In my controller code, I use the following lines to create the mutable array holding the custom objects:
smartApp *appFound = [[smartApp alloc] initWithApplicationIdentifier:key];
if (appFound)
{
[appFound setStatus:[NSNumber numberWithBool:YES]];
[appFound addObserver:self
forKeyPath:#"status"
options:(NSKeyValueObservingOptionNew |
NSKeyValueObservingOptionOld)
context:NULL];
[_appsArray addObject:appFound];
}
I add an observer on the 'status' keypath of that object. And I add the pending code, observing the object for value changes:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)anObject
change:(NSDictionary *)change
context:(void *)context
{
if ([anObject isKindOfClass:[smartApp class]])
{
if ([keyPath isEqual:#"status"])
{
NSLog(#"Clicked on row: %lu", (unsigned long)[self.appsArray indexOfObject:anObject]);
}
}
}
Once you're in the method, you can do what you want. It will definitely get called, and you get the object matching the line you clicked on, the change dictionary, and the keypath.
Hope that helps
Yeah, it's different for view-based table views. (I'm just learning this myself.) In a view-based table view the Table Cell View's objectValue is the object represented by that row. You bind your text field, checkbox, etc. to keypaths of that objectValue.

View-based NSOutlineView header cell font issues

I'm currently trying to use a new view-based NSOutlineView in my Cocoa app. As I'm not using bindings, so I implemented all required delegate and datasource methods in my controller.
In interface builder I've added a NSOutlineView with a highlighting set to SourceList and Content Mode set to View Based. Thus, there were two default table cell views provided (one Header cell with HeaderCell set as identifier and one data cell with DataCell set as identifier)
This is what it looks like in interface builder, header cell views correctly show a grey-blue textField while data cell views have a image view and a textField with correct color and font settings
To provide the views, I use the following code, to return a DataCell-view or a HeaderCell-view and set the textField of the cell accordingly, based on the corresponding identifier set in interface builder.
- (NSView *)outlineView:(NSOutlineView *)outlineView
viewForTableColumn:(NSTableColumn *)tableColumn
item:(id)item {
NSTableCellView *result = nil;
if ([item isKindOfClass:[NSMutableDictionary class]]) {
result = [outlineView makeViewWithIdentifier:#"HeaderCell" owner:self];
id parentObject = [outlineView parentForItem:item] ? [outlineView parentForItem:item] : groupedRoster;
[[result textField] setStringValue:[[parentObject allKeys] objectAtIndex:0]];
} else {
result = [outlineView makeViewWithIdentifier:#"DataCell" owner:self];
[item nickname] ? [[result textField] setStringValue:[item nickname]] : [[result textField] setStringValue:[[item jid] bare]];
}
return result;
}
Running everything it looks like the following.
Could anybody provide me with hints, to why the header cell is neither bold, nor correctly colored, when selected?
You need to implement the -outlineView:isGroupItem: delegate method and return YES for your header rows. That will standardize the font and replace the disclosure triangle on the left with a Show/Hide button on the right. You will still need to manually uppercase your string to get the full effect.
I'm not sure if the group row delegate method above makes the selection style look okay or not. However, you normally don't want the header rows to be selectable at all in source lists, which you by returning NO for header items from the -outlineView:shouldSelectItem: delegate method.
I have created a little sample project which includes a source list and also uses the -outlineView:isGroupItem: method as #boaz-stuller has suggested.
Display a list of items
Edit the items in a master-detail fashion
Remove and add items
Usage of bindings
Check out besi/mac-quickies on github.
Most of the stuff is either done in IB or can be found in the AppDelegate

Resources