Get row of NSPopUpButton in view based NSTableView - objective-c

I have a view based table view with one row containing NSPopupButtons. When the user changes the popUpButton I need to get get the table row in which this popUpButton is contained.
I first expected to get the row with
NSInteger clickedRow = [tableView rowForView: ((NSPopUpButton*) sender)];
But the sender of the action always is a NSMenuItem object not the NSPopUpButton. NSMenuItem however is not a view so I cannot use rowForView with that.
Currently my IBAction looks like this:
- (IBAction)changedPopUp:(id)sender {
NSMenuItem* selectedMenuItem = ((NSMenuItem*) sender);
NSPopUpButton* popupButton = (NSPopUpButton*)[selectedMenuItem view];
NSInteger clickedRow = [tableView rowForView:popupButton];
//[...]
}
But the view property is not set automatically and I find it quite inconvenient to set it manually for every NSMenuItem.
Is there no easy way to get the table row?

Don't set an action and target for each NSMenuItem. Set the action and target for the NSPopupButton instead. Then, sender will be the NSPopupButton instance and you can use rowForView.
The drawback is of course that you cannot have different actions for different menu items.

Related

IBAction for checkbox in NSTableView cell, unexpected sender

I have a cell-based NSTableView that includes a checkbox in the first column. Everything displays and seems to function correctly, except the handling of the checkbox action, which looked like so:
- (IBAction)ActiveCheckboxAction:(id)sender {
// Do stuff with the checkbox
}
I was surprised to discover that when ActiveCheckboxAction is called, sender points to the table view; not the checkbox. To get around this problem I was looking for a way to access the checkbox from the currently selected row, so I modified the code, like so:
- (IBAction)ActiveCheckboxAction:(id)sender {
NSTableCellView *cellView = [tableView viewAtColumn:0 row:[tableView selectedRow] makeIfNecessary:NO];
NSButton *checkBox = [[cellView subviews] objectAtIndex:0];
// Do stuff with the checkbox
}
Here, cellView is always nil, so I'm guessing that viewAtColumn applies to only view-based table views. In examining the table view I can see the checkboxes in the _datacell for the first column correctly described as an (NSButton *), but I can't figure out how to access this cell to get to the checkbox.
Two questions:
Why would sender be pointing to the table view?
What can I do to get the checkbox from the selected row?
Thanks!
Why would sender be pointing to the table view?
The sender is the control, in this case the table view. NSTableView is a subclass of NSControl.
What can I do to get the checkbox from the selected row?
The checkbox cell is [sender selectedCell] in the action method.
Instead of the action method you can use tableView:setObjectValue:forTableColumn:row:. The object value is the state of the checkbox as NSNumber.

How do I use a UISegmentedControl to control UITableView cells?

I've successfully set up a Tab Bar controller with. Table View in the first view and a simple UIView in the second tab. The UIView has it's own class. I've added a segmented control to the latter and ultimately I want to change the images used in the table view cells depending on which segment is selected.
The cell images are part of an array and have been set up using a custom cell.
I've set up the segmented control in the UIView class adding #property an #syntesize but I can't for the life of me figure out how to change the cell images from the UIView class.
Thanks in advance!
The segment control in my UIView.h is defined like so
#property (weak, nonatomic) IBOutlet UISegmentedControl *selectedSegment
I've not yet included an IBAction as I'm not 100% where it would need to go or how it would be written.
In my table view my cell images are currently defined like so in cellForRowAtIndexPath
cell.itemImageView.image = [UIImage imageNamed:[icons objectAtIndex:indexPath.row]];
My thoughts were to put a switch statement within the cell method with each case pointing to a different icon set so
switch (something)
{
case 0:
cell.itemImageView.image = [UIImage imageNamed:[iconSetOne objectAtIndex:indexPath.row]];
break;
case 1:
cell.itemImageView.image = [UIImage imageNamed:[iconSetTwo objectAtIndex:indexPath.row]];
break;
default
break;
}
But I'm pretty sure that's the wrong way of doing it and the "something" bit is what I'm stuck on.
1) In the simple UIView where you have your segmented control:
Define an outlet _tableViewController that you connect to the controller of your table view
Define an action method segmentedControlChanged: that responds to the segmented control being triggered
2) In the table view controller:
Define a property selectedSegmentIndex of type NSInteger
3) Now add the implementation for the action method:
- (void) segmentedControlChanged:(id)sender
{
UISegmentedControl* segmentedControl = (UISegmentedControl*)sender;
_tableViewController.selectedSegmentIndex = segmentedControl.selectedSegmentIndex;
[_tableViewController.tableView reloadData];
}
4) In the table view controller you can now add the switch statement that you suggested to tableView:cellForRowAtIndexPath::
switch (self.selectedSegmentIndex)
{
[...]
}
Note that invoking reloadData in step 3 is a crude way to force the table view to update all cells with the correct images. There are other methods in UITableView that give you more fine-grained control over which cells you want updated (e.g. reloadSections:withRowAnimation:).

Determine the last selected row when returning from detailView of UITableView

Ok I push a detailView when row is selected in the UITableView. In the detailView I set som data and pass that to the webservice. I want to retrieve some data either before the detailView is pushed or when it will disapear. Based on the data retrieved will I set an UIImage as an indicator.
I wonder how I can determine the indexpath.row the detailView got pushed for and use that for setting the UIImage. Is this possible?
I have a UISegmentedControl which loads two different datasources for the UITableView.
For testing I pass the indexpath.row when the detailView is pushed and callback to the UITableView class along with a boolean if the UIImage should be display.
But my problem is that indexpath.row is 0 when the UITableView loads.
And when changing datasource back and forth and passing the indexpath.row with it, the indicator will be display on wrong rows.
The two datasources for the table is:
if (index == 0) {
dictionary = [firstArray objectAtIndex:indexPath.row];
}
else {
dictionary = [secondArray objectAtIndex:indexPath.row];
}
I then set the indicator for that row (will be the UIImage later):
if (indexPath.row == selectedIndex) {
cell.indicator.text = #"Begin";
}
When the detailView is pushed in didSelectRowAtIndexPath i pass in the indexPath.row:
NSInteger rowIndex = indexPath.row;
[self.detailViewController initWithDetailsSelected:rowIndex:dictionary];
When the detailView is done the UITableView class receives the callback:
- (void)processAttendanceSuccessful:(BOOL)successful:(NSInteger)_selectedIndex{
self.selectedIndex = _selectedIndex;
}
So based on a boolean a UIImage should be display for the correct row in the UITableView.
Any suggestions how I can go about this. It must be quite common to set some value in a detailedView and display some data for a specific row in the UITableView, but having a rather hard time finding any good tutorials for this.
I only find how to pass data into the detailView, which I dont have any problem with.
Thanks in advance people.
Don't deselect the row in tableView:didSelectRowAtIndexPath:. When you return to this view controller, just use indexPathForSelectedRow method of the UITableView object.
But in the long run this makes little sense. Since the table view reuses those cells, you also need to remember the rows that have the image. You will probably have to maintain an array for this. In which case, you can either pass the array and the index path to the detail view controller and update them there or use delegate or notification mechanism to update the array from within the table view controller.
I would use an ivar of int, using negative values to represent null selection:
#interface MyController : UIViewController {
int lastSelection;
}
...
#end
#implementation MyController
...
- (void)initWithBlablabla{
...
lastSelection = -1;
}
...
#end
However, I think you should better re-think about your design referring to KVO programming guide. In general MVC patterns, a controller is not the right place to store the state of the model.

How to select one NSCell in an NSTableView?

I have a small NSTableView with a checkbox. Whenever the checkbox is not checked, I want one of the adjacent NSCells to be grayed out and inaccessible.
However, I can't figure out how to address only one specific cell. -dataCellForRow of NSTableColumn always changes the template cell for the whole table column.
How can I access one single cell?
Edit: I fill the table view using the NSTableViewDataSource protocol.
You don't "access a cell". NSTableView asks for data only when necessary, you don't populate it or control it directly.
Instead, you create a controller object which implements the NSTableViewDatasource and optionally NSTableViewDelegate protocols. The table view then sends the datasource messages to your controller and your controller supplies the appropriate data.
You can allow editing for an object displayed in the table view by implementing the ‑tableView:setObjectValue:forTableColumn:row: datasource method. This method will be called on your controller object when the user clicks the checkbox. It is your controller's responsibility to update the model appropriately.
When the model is updated, your controller should tell the table view to reload. The table view will then ask your controller for the value of any cell that requires display using the ‑tableView:objectValueForTableColumn:row: datasource method. This will include the cell that you need to disable. Your controller needs to supply the appropriate value for the cell.
If you need more control of the cell, you can implement the
‑tableView:willDisplayCell:forTableColumn:row: delegate method. This is called just before a cell is displayed, and you can modify the cell appropriately.
More info about using data sources is in the docs.
The other option (instead of using a datasource) is to use Cocoa Bindings and an NSArrayController that you bind to your collection of model objects. In that case, you can bind the Enabled binding of the table column to some property of your model object that controls the cell's enabled state. It is your responsibility to ensure that the state of that property is correct.
If you need to make the property dependent on the value of another property, you can use the dependent key mechanism outlined in the Key-Value Observing documentation.
Could you bind the editability of that column to the value that is being displayed in the checkbox? i.e. if it is checked, it is editable, otherwise it isn't?
I am trying to remember the exact editor interface, and I am not next to my Mac at home, so I am not able to do a total walk through on it - hope this can point you in the right direction.
Since SDK Version 10.7, there's -viewAtColumn:row:makeIfNecessary: on NSTableView. The majority of information I found on the web don't take the new methods into account, so here it is for all the others looking for an answer to this question.
From Mouse Event to Cell Selection
First, add a protocol for your controller to handle cell selection from a table view, like this:
#protocol XYZCellSelectionDelegate <NSObject>
- (void)cellViewWasSelectedAtRow:(NSInteger)row column:(NSInteger)column;
#end
Then subclass NSTableView and override -mouseDown:
// In your Custom Table View subclass:
- (void)mouseDown:(NSEvent *)event
{
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSInteger selectedRowIndex = [self rowAtPoint:point];
NSInteger selectedColumnIndex = [self columnAtPoint:point];
if ([self.calendarViewDelegate respondsToSelector:#selector(cellViewWasSelectedAtRow:column:)])
{
[self.calendarViewDelegate cellViewWasSelectedAtRow:selectedRowIndex column:selectedColumnIndex];
}
[super mouseDown:event];
}
Afterwards, you can use -viewAtColumn:row:makeIfNecessary: like this in the delegate/controller object:
- (void)cellViewWasSelectedAtRow:(NSInteger)row column:(NSInteger)column
{
NSView *selectedView = [self.tableView viewAtColumn:column row:row makeIfNecessary:YES];
// Do something with the cell to the right
NSInteger nextColumn = column + 1;
NSView *cellNextToIt = [self.calendarTableView viewAtColumn:nextColumn row:row makeIfNecessary:YES];
}
Note: Nowadays, I'd pass the table view to the delegate as a parameter instead of relying on the delegate to keep a reference to the table view.

Move focus to newly added record in an NSTableView

I am writing an application using Core Data to control a few NSTableViews. I have an add button that makes a new a record in the NSTableView. How do I make the focus move to the new record when this button is clicked so that I can immediately type its name? This is the same idea in iTunes where immediately after clicking the add playlist button the keyboard focus is moved to the new line so you can type the playlist's name.
Okay well first of all, if you haven't already got one, you need to create a controller class for your application. Add an outlet for the NSArrayController that your objects are stored in, and an outlet for the NSTableView that displays your objects, in the interface of your controller class.
IBOutlet NSArrayController *arrayController;
IBOutlet NSTableView *tableView;
Connect these outlets to the NSArrayController and the NSTableView in IB. Then you need to create an IBAction method that is called when your "Add" button is pressed; call it addButtonPressed: or something similar, declaring it in your controller class interface:
- (IBAction)addButtonPressed:(id)sender;
and also making it the target of your "Add" button in IB.
Now you need to implement this action in your controller class's implementation; this code assumes that the objects you have added to your array controller are NSStrings; if they are not, then replace the type of the new variable to whatever object type you are adding.
//Code is an adaptation of an excerpt from "Cocoa Programming for
//Mac OS X" by Aaron Hillegass
- (IBAction)addButtonPressed:(id)sender
{
//Try to end any editing that is taking place in the table view
NSWindow *w = [tableView window];
BOOL endEdit = [w makeFirstResponder:w];
if(!endEdit)
return;
//Create a new object to add to your NSTableView; replace NSString with
//whatever type the objects in your array controller are
NSString *new = [arrayController newObject];
//Add the object to your array controller
[arrayController addObject:new];
[new release];
//Rearrange the objects if there is a sort on any of the columns
[arrayController rearrangeObjects];
//Retrieve an array of the objects in your array controller and calculate
//which row your new object is in
NSArray *array = [arrayController arrangedObjects];
NSUInteger row = [array indexOfObjectIdenticalTo:new];
//Begin editing of the cell containing the new object
[tableView editColumn:0 row:row withEvent:nil select:YES];
}
This will then be called when you click the "Add" button, and the cell in the first column of the new row will start to be edited.
I believe an easier and more proper way to do it is by implementing it this way.
-(void)tableViewSelectionDidChange:(NSNotification *)notification {
NSLog(#"%s",__PRETTY_FUNCTION__);
NSTableView *tableView = [notification object];
NSInteger selectedRowIndex = [tableView selectedRow];
NSLog(#"%ld selected row", selectedRowIndex);
[tableView editColumn:0 row:selectedRowIndex withEvent:nil select:YES];
I.e.
Implement tableViewSelectionDidChange:(NSNotification *)notification
fetch the selected row index
Call editColumn:(NSInteger)column row:(NSInteger)row withEvent:(NSEvent *)theEvent select:(BOOL)select from there with the row index.
Important note: this solution will also trigger the editing when user will simply select a row. If you only want editing triggered when adding a new object, this is not for you.
Just create a separate #IBAction in your controller and invoke the NSArrayController.add method manually. After that you can select the row
#IBAction func addLink(_ sender: Any) {
// Get the current row count from your data source
let row = links.count
arrayController.add(sender)
DispatchQueue.main.async {
self.tableView.editColumn(0, row: row, with: nil, select: true)
}
}