This question already has answers here:
Question about Objective-C selectors; Can I use parameters and how?
(2 answers)
Closed 9 years ago.
how can i pass a parameter to the following selector?
NSMenuItem *item3 = [[NSMenuItem alloc] initWithTitle:title3 action:#selector(uncheckrow:) keyEquivalent:#""];
i would like to pass on the number of the row to the method.
Thanks
EDIT
the row i want to pass is the row of the table i rightclicked in the first place. but it could be any integer variable..
Override the menuForEvent: in your tableview's subclass, Take some variable in your controller say 'rowToApply'
- (NSMenu*) menuForEvent:(NSEvent*)event
{
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSInteger row = [self rowAtPoint:point];
BOOL clickWasOnItem = (row > -1);
if(clickWasOnItem) {
controller.rowToApply = [self itemAtRow:row];// Controller can be datasource(or) delegate (or) someObject which can be accessible)
}
return [super menuForEvent:event];
}
you can use the rowToApply: in contextualMenu item's action method (uncheckrow:).
The menu item itself (item3 your snippet) will be sent to the method, so you can identify the particular selected menu item that way. You can also set the NSMenuItem's tag property, for example: [item3 setTag:3] and then in your uncheckrow method you can use NSInteger row = [sender tag];
Edit: For updated question
If you want to send an arbitrary integer to this method then there are options, but they aren't particularly elegant. AppKit will always send the NSMenuItem as the sender, so you need to make the integer available to the NSMenuItem being unchecked. Note that NSMenuItems have access to their parent NSMenus, through the menu method. So if the parent NSMenu itself has some property you can take advantage of, you can use [[sender menu] thatProperty]. Since you're showing the NSMenu in response to a right click on a table's row, you could set the property before you show the menu. I recommend subclassing NSMenu to add your own "spawningRowIndex" property (choose a better name though). Before showing the NSMenu set the property and when you receive uncheckrow you can use [[sender menu] spawningRowIndex].
Related
I am having an NSBrowser in one of my windows. It has a checkbox, image and textbox as you see in the screenshot.
I am struggling to do two things:
Change the row selection color. By default it is blue.
Action on the checkbox
The checkbox + image + textbox is added to the subclass of NSBrowserCell like this:
- (instancetype)init
{
self = [super init];
if (self) {
_buttonCell = [[NSButtonCell alloc] init];
[_buttonCell setButtonType:NSButtonTypeSwitch];
[_buttonCell setTarget:self];
[_buttonCell setAction:#selector(cellButtonClick:)];
[_buttonCell setTitle:#""];
_imageCell = [[NSImageCell alloc] init];
_textCell = [[NSTextFieldCell alloc] init];
}
return self;
}
I've added target and Action too, but it is not called.
How do I achieve these two things?
Any guidance will be appreciated.
Caveat: customizing NSBrowser/NSBrowserCell is usually hard. It's a legacy view class that's been essentially abandoned for years, and it has a lot of undocumented quirks.
Having said that, try overriding -highlightColorWithFrame:inView: to use a different color for highlighting the row. It that's all you need, then I think that should do it.
As for the action, NSCell (unlike views and controls) is not a subclass of NSResponder and doesn't perform any automatic mouse event processing. (Cells are just helper objects to draw the view of a control/element.)
You'll probably have to catch the event at the browser view level, then perform hit testing to find the column/row that contains your check box cell. If you find a hit, then post the action message yourself (-sendAction:to:from:)—which is literally what a NSControl view does.
This question already has answers here:
What's the best way to communicate between view controllers?
(4 answers)
Closed 8 years ago.
In my TableViewController.m I can easily get the selected row by doing [tableView indexPathForSelectedRow] but I want to get this value from DIFFERENT view. So when my TableViewController goes to my DetailViewController. I want my DetailViewController to have the data of the selected cell.
In a proper architecture, a Controller is designed so that it acts as a black box to other Controllers. In your example, it's not a very good practice that the Details controller searches for the TableViewController's selected item. Instead your TableViewController should use the DetailViewController as a 'service' and somehow pass the selected object as an argument, or set it as a property of the 'service provider' (DetailController).
For example
// this is still the TavleViewController's code
id selectedObject = // ... get selected object somehow... indexPathForSelectedRow or whatever
DetailViewController *newView = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
[newView showDetails:selectedObject];
Here, the function showDetails is just an example, it's a function you implement to pass the seleted object as argument.
EDIT:
How you do it depends on your model. Supose each object in your model represents a person. Your MainTableView has a list of names and the detailView shows the details about the selected Person. In such a case, which is typcial, you would have a class that represents Person, and the list of persons would be somewhere in an array. So...
NSIndexPath *i = [tableView indexPathForSelectedRow];
Person *selectedPerson = [self.myArrayOfPersons objectAtIndex:i.row];
// here you instantiate or show the details view
[detailsView showDetails:selectedPerson]; // this is one option, you could also use a property
// for example
detailsView.selectedPerson = selectedPerson; // this is an alternative to the showDetails,
In the DetailsViewController you could have something like this
- (void)showDetails:(Person *)person
{
// just fill your controls witht the person's information: age, birth place, address...
[self.someTextView setText:person.name]
}
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.
Is there a way to get a reference to the view controller of my superview?
There were several instances that I needed this on the past couple of months, but didn't know how to do it. I mean, if I have a custom button on a custom cell, and I wish to get a reference of the table view controller that controls the cell I`m currently in, is there a code snippet for that? Or is it something that I should just solve it by using better design patterns?
Thanks!
Your button should preferably not know about its superviews view controller.
However, if your button really needs to message objects that it shouldn't know the details about, you can use delegation to send the messages you want to the buttons delegate.
Create a MyButtonDelegate protocol and define the methods that everyone that conforms to that protocol need to implement (the callback). You can have optional methods as well.
Then add a property on the button #property (weak) id<MyButtonDelegate> so that any class of any kind can be set as the delegate as long as it conforms to your protocol.
Now the view controller can implement the MyButtonDelegate protocol and set itself as the delegate. The parts of the code that require knowledge about the view controller should be implemented in the delegate method (or methods).
The view can now send the protocol messages to its delegate (without knowing who or what it is) and the delegate can to the appropriate thing for that button. This way the same button could be reused because it doesn't depend on where it is used.
When I asked this question I was thinking of, in a situation where I have custom cells with buttons on them, how can the TableViewController know which cell's button was tapped.
More recently, reading the book "iOS Recipes", I got the solution:
-(IBAction)cellButtonTapped:(id)sender
{
NSLog(#"%s", __FUNCTION__);
UIButton *button = sender;
//Convert the tapped point to the tableView coordinate system
CGPoint correctedPoint = [button convertPoint:button.bounds.origin toView:self.tableView];
//Get the cell at that point
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:correctedPoint];
NSLog(#"Button tapped in row %d", indexPath.row);
}
Another solution, a bit more fragile (though simpler) would be:
- (IBAction)cellButtonTapped:(id)sender
{
// Go get the enclosing cell manually
UITableViewCell *parentCell = [[sender superview] superview];
NSIndexPath *pathForButton = [self.tableView indexPathForCell:parentCell];
}
And the most reusable one would be to add this method to a category of UITableView
- (NSIndexPath *)prp_indexPathForRowContainingView:(UIView *)view
{
CGPoint correctedPoint = [view convertPoint:view.bounds.origin toView:self];
return [self indexPathForRowAtPoint:correctedPoint];
}
And then, on your UITableViewController class, just use this:
- (IBAction)cellButtonTapped:(id)sender
{
NSIndexPath *pathForButton = [self.tableView indexPathForRowContainingView:sender];
}
If you know which class is the superview of your view controller, you can just iterate through the subviews array and typecheck for your superclass.
eg.
UIView *view;
for(tempView in self.subviews) {
if([tempView isKindOfClass:[SuperViewController class] ])
{
// you got the reference, do waht you want
}
}
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)
}
}