Get button's row in view based table - objective-c

How do you get the row for a button in a view based table when you click the button? The row is not selected when the button is clicked, but I found that if you log sender.superview.superview in the button's action method, I get: NSTableRowView: 0x1001b3a90 - row: 2. So, the row is there in the log, but I don't know how to get at it programmatically. I have the button's action method in a subclass of NSTableCellView.

-[NSTableView rowForView:] says this in its documentation:
This is typically needed in the action method for an NSButton (or NSControl) to find out what row (and column) the action should be performed on.

In Swift 5.1 -
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
if let checkBoxCell = tableView.makeView(withIdentifier:NSUserInterfaceItemIdentifier(rawValue: "<ColumnIdentifier>"), owner: self) as! NSButton? {
checkBoxCell.tag = row;
checkBoxCell.target = self;
checkBoxCell.action = #selector(TableViewService.checkBoxAction)
return checkBoxCell
}
return nil
}
#objc func checkBoxAction(button:NSButton){
print(button.tag);
}

Here I'm giving you a simple example. In this I'm adding a UIButton in content view. When I clicked on button I call a Method and there I get Row number and call as I required
//Adding a button
UIButton *btnBuyer=[UIButton buttonWithType:UIButtonTypeCustom];
btnBuyer.frame=CGRectMake(238, 10, 26, 32);
btnBuyer.tag=indexPath.row;
[btnBuyer setImage:[UIImage imageNamed:#"buyIcon.png"] forState:UIControlStateNormal];
[btnBuyer addTarget:self action:#selector(goBuyTab:)forControlEvents:UIControlEventTouchUpInside];
[cellNew.contentView addSubview:btnBuyer];
And When User Clicks on this I got Id from the following method
-(void)goBuyTab:(UIButton *)sender{
NSLog(#"after click buy button function called goBuyTab");
NSLog(#"sender.tag in goBuyTab : %d",sender.tag);
int selectedRow=sender.tag;// This is row number
}
Hope this is what you required.

I share this code for Swift 4.
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
........
but.action = #selector(ViewController.buttonAction)
.........
}
#objc func buttonAction(but: NSButton){
let tablerow: NSTableRowView = but.superview?.superview as! NSTableRowView;
let index = table?.row(for: tablerow);
print("\(index));
}

in osx you can use this in your button cell action method in the controller:
- (IBAction)yourMethod:(NSButtonCell *)sender {
NSInteger selected = [yourTableView selectedRow];
NSLog(#"sender sends :%ld", selected);
}
you need to set an outlet from the controller to the NSTableView.

Related

How to pass a Parameter to the addTarget method of button in swift

I have a CollectionViewCell and i am working on cellForItemAt method of it. I have a button in each Collection view cell which defines to 3 different section in my CollectionView.
I am adding below code to set the button target:
cell.buttonView.addTarget(self, action: #selector(buttonPressed(sender:)), for: UIControlEvents.touchUpInside)
Now, i have created a new method: #objc func buttonPressed(sender: UIButton) where i am adding a title to the UIAlertController like:
let alertController = UIAlertController(title: , message: "Below actions are", preferredStyle: .actionSheet)
So basically, each time any button is tapped in the cell, the Alert should open and also each time the title will be different for it.
How do i pass the title here dynamically?
If the title to be passed is button's title, then you can simply do this :-
#objc func buttonPressed(sender: UIButton){
let title = sender.title(for: .normal)
}
And if it is some other data in section, you can use tags on your buttons, and setting them to indexPath.row :-
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell.buttonView.tag = indexPath.row
}
And in your Button's action, access button's tag
#objc func buttonPressed(sender: UIButton){
let objectIndex = sender.tag
let object = yourArray[objectIndex]
let title = object.title
}

Downpicker not appearing in UITableView

I am attempting to use the Darkseal Downpicker inside a UITableView. I am programatically adding a UITextField and then adding the Downpicker as follows:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var textField = UITextField()
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.selectionStyle = .none
textField.text = "TEST"
textField.frame = CGRect(x: CGFloat(0), y: CGFloat(0), width: CGFloat(170), height: CGFloat(30))
var dp: DownPicker!
let test1 = ["test one", "test two"]
dp = DownPicker(textField: textField, withData: test1)
cell.contentView.addSubview(dp)
return cell
}
If I just add the textfield:
cell.contentView.addSubview(textField)
Then it displays the text field. However if I add the DownPicker as shown above:
cell.contentView.addSubview(dp)
I get nothing displayed, just an empty tableview. Any help understanding why the DownPicker is not displayed would be appreciated. Thanks.
You need to add both ur UITextField and ur DownPicker into the View. If it's anything like the UIPickerView then your DownPicker will only show when you select the UITextField. Also you need to add your DownPicker to the view rather then the cell.
view.addSubView(dp)
EDIT
"It takes any UITextField already present in your code (including those added to a Storyboard):" - https://github.com/Darkseal/DownPicker
EDIT 2
From looking at the docs what you want to do is add yourself as the observer on the DataPicker by doing the following inside the cellForRowAt func
dp.addTarget(self, action: #selector(dp_selected(sender:)), for: .valueChanged)
You want to add a func the class with cellForRowAt func inside it
func dp_selected(sender: AnyObject) {
guard let dp = sender as? DropDown else { return }
if let textfield = dp.getTextField() as? UITextField {
guard let indexPath = tableView.indexPath(for: textfield.superview) else { return }
}
}
This function will check if the sender is of type DropDown class and if it is will try to get the UITextField that it's attached to and if that is returned it will attemt to get the IndexPathat which it's being used at this can be nil if the cell is no longer visible on the screen but hopefully that will never be the case and have added a check to make sure it exists.

Pass Data to next ViewController Using UIBarButtonItem in Swift

I have a UITableView that allows multiple selections. I'd like to pass the selections to the next "Q2ViewController" when user clicks the rightBarButtonItem "Next".
Note: I'm not using Storyboard. After searching through, I haven't found a solution without using storybaord.
var options = ["breakfast", "lunch", "dinner", "dessert"]
var selected = -1
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Q1View"
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Next", style: .Done, target: self, action: "didTapNext:")
self.tableView.allowsMultipleSelection = true
var nib = UINib(nibName: "OptionCell", bundle: nil)
tableView?.registerNib(nib, forCellReuseIdentifier: "OptionCellIdentifier")
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.options.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("OptionCellIdentifier", forIndexPath: indexPath) as OptionCell
cell.textLabel?.text = self.options[indexPath.row]
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
println("You selected cell #\(indexPath.row)!")
}
}
Anywhere in your code you must create the "next" view controller. Either in your App Delegate or directly in the view controller shown above.
If you create the view controller outside the above one you will have to pass the above controller a reference to the destination view controller.
If you create the view controller inside the above one save a reference to it in an instance variable.
Or create a method that creates the VC on pressing the "Next" button and pushes the VC to the navigation controller.
You can use UITableView's indexPathsForSelectedRows() or similar to get the selection and then call a method/set a instance variable on the destination view controller.
But seriously, why don't you use storyboards?

Contextual menu on only certain items in a "Source List"

I have a window with a Source List (NSOutlineView). My source list has just two levels. Level one is header and level two is data. I want to have a contextual menu on some of the data cells. Not all.
First, I try to attach a menu on the table cell view who represents the data cell -> nothing happens.
Second, I attach a menu on the Outline View in IB -> the contextual menu opens on each cells (header and data). I search for stopping the opening of the menu, but I don't find anything.
Do you have some ideas ?
Thank you
OS X 10.8.2 Lion, Xcode 4.5.2, SDK 10.8
If you subclass NSOutlineView, you can override menuForEvent: to return a menu only if the user clicked on the correct row. Here's an example:
- (NSMenu *)menuForEvent:(NSEvent *)event;
{
//The event has the mouse location in window space; convert it to our (the outline view's) space so we can find which row the user clicked on.
NSPoint point = [self convertPoint:[event locationInWindow] fromView:nil];
NSInteger row = [self rowAtPoint:point];
//If the user did not click on a row, or is not exactly one level down from the top level of hierarchy, return nil—that is, no menu.
if ( row == -1 || [self levelForRow:row] != 1 )
return nil;
//Create and populate a menu.
NSMenu *menu = [[NSMenu alloc] init];
NSMenuItem *delete = [menu addItemWithTitle:NSLocalizedString( #"Delete", #"" ) action:#selector(delete:) keyEquivalent:#""];
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
//Set the Delete menu item's represented object to the clicked-on item. If the user chooses this item, we'll retrieve its represented object so we know what to delete.
[delete setRepresentedObject:[self itemAtRow:row]];
return menu;
}
This assumes we're compiling with ARC, so you don't need to autorelease the menu object being created.
This extension + subclass (both NSOutlineView and NSTableView) does the sensible thing of seeing whether a menu is attached to a cell view or row view. Just a general, reusable subclass!
Set the menu on the cell view in outlineView:viewForTableColumn:item: – menu is a NSResponder property.
(Below is in Swift)
// An extension lets us both subclass NSTableView and NSOutlineView with the same functionality
extension NSTableView {
// Find a cell view, or a row view, that has a menu. (e.g. NSResponder’s menu: NSMenu?)
func burnt_menuForEventFromCellOrRowViews(event: NSEvent) -> NSMenu? {
let point = convertPoint(event.locationInWindow, fromView: nil)
let row = rowAtPoint(point)
if row != -1 {
if let rowView = rowViewAtRow(row, makeIfNecessary: true) as? NSTableRowView {
let column = columnAtPoint(point)
if column != -1 {
if let cellView = rowView.viewAtColumn(column) as? NSTableCellView {
if let cellMenu = cellView.menuForEvent(event) {
return cellMenu
}
}
}
if let rowMenu = rowView.menuForEvent(event) {
return rowMenu
}
}
}
return nil
}
}
class OutlineView: NSOutlineView {
override func menuForEvent(event: NSEvent) -> NSMenu? {
// Because of weird NSTableView/NSOutlineView behaviour, must set receiver’s menu otherwise the target cannot be found
self.menu = burnt_menuForEventFromCellOrRowViews(event)
return super.menuForEvent(event)
}
}
class TableView: NSTableView {
override func menuForEvent(event: NSEvent) -> NSMenu? {
// Because of weird NSTableView/NSOutlineView behaviour, must set receiver’s menu otherwise the target cannot be found
self.menu = burnt_menuForEventFromCellOrRowViews(event)
return super.menuForEvent(event)
}
}
It's not clear from your question whether your outline is view based or cell based. That's important.
If you're view based, then your view instances can implement
- (NSMenu *)menuForEvent:(NSEvent *)theEvent
and return the menu appropriate to that item -- or nil f you don't want a menu at all.
If you're cell based, or if you don't want to handle this in the view class for some reason, you'll need to subclass NSOutlineView and implement - (NSMenu *)menuForEvent:(NSEvent *)theEvent there. Again, you'll figure out which cell is hit or active, and decide from that what menu you want.
- (void)rightMouseDown:(NSEvent *)event
An NSView will not pass this to the next view, This method looks to see that the current class has a menuForEvent:, if it does then it is called. If it does not then it is finished and nothing else will happen. This is why you will not see an NSTableCellView respond to a menuForEvent: because the table view swallows the rightMouseDown:.
You may subclass the tableview and handle the rightMouseDown: event and call the NSTableCellView's rightMouseDown: and handle displaying your menu that you have constructed in your storyboard and hooked up to your NSTableViewCell.
Here is my solution in a subclassed NSTableView:
- (void)rightMouseDown:(NSEvent *)event
{
for (NSTableRowView *rowView in self.subviews) {
for (NSView *tableCellView in [rowView subviews]) {
if (tableCellView) {
NSPoint eventPoint = [event locationInWindow];
// NSLog(#"Window Point: %#", NSStringFromPoint(eventPoint));
eventPoint = [self convertPoint:eventPoint toView:nil];
eventPoint = [self convertPoint:eventPoint toView:self];
// NSLog(#"Table View Point: %#", NSStringFromPoint(eventPoint));
NSRect newRect = [tableCellView convertRect:[tableCellView bounds] toView:self];
// NSLog(#"Rect: %#", NSStringFromRect(newRect));
BOOL rightMouseDownInTableCellView = [tableCellView mouse:eventPoint inRect:newRect];
// NSLog(#"Mouse in view: %hhd", mouseInView);
if (rightMouseDownInTableCellView) {
if (tableCellView) {
// Lets be safe and make sure that the object is going to respond.
if ([tableCellView respondsToSelector:#selector(rightMouseDown:)]) {
[tableCellView rightMouseDown:event];
}
}
}
}
}
}
}
This will find where the right mouse event occurred, check to see if we have the correct view and pass the rightMouseDown: to that view.
Please let me know if this solution works for you.

NSTableView Right Clicked Row Index

I'm looking for a way to get right-clicked row index from NSTableView but I can't find any delegate methods or class attributes for it. Any suggestion is appreciated.
Use the NSTableView method - (NSInteger)clickedRow to get the index of the last clicked row. The returned NSInteger will be the index of the right clicked row.
You do not need to subclass NSTableView for this solution. clickedRow is also available on NSOutlineView.
While I haven't done this, I am pretty sure you can by overriding NSView's - (NSMenu*)menuForEvent:(NSEvent*)theEvent. The example in this link does a point conversion to determine the index.
-(NSMenu*)menuForEvent:(NSEvent*)theEvent
{
NSPoint mousePoint = [self convertPoint:[theEvent locationInWindow] fromView:nil];
int row = [self rowAtPoint:mousePoint];
// Produce the menu here or perform an action like selection of the row.
}
Updated Answer
If you want to get clicked row index on menu opening, the answer is NSTableView.clickedRow. Anyway this property is available only in specific moments, and usually just -1.
When is this index to be available? That's in NSMenuDelegate.menuWillOpen method. So you conform the delegate and implement the method on your class, and access the clickedRow property. It's done.
final class FileNavigatorViewController: NSViewController, NSMenuDelegate {
let ov = NSOutlineView() // Assumed you setup this properly.
let ctxm = NSMenu()
override func viewDidLoad() {
super.viewDidLoad()
ov.menu = ctxm
ctxm.delegate = self
}
func menuWillOpen(_ menu: NSMenu) {
print(outlineView.clickedRow)
}
}
Clicked row index is available until you click an item in the menu. So this also works.
final class FileNavigatorViewController: NSViewController {
let ov = NSOutlineView() // Assumed you setup this properly.
let ctxm = NSMenu()
let item1 = NSMenuItem()
override func viewDidLoad() {
super.viewDidLoad()
ov.menu = ctxm
ov.addItem(item1)
ov.target = self
ov.action = #selector(onClickItem1(_:))
}
#objc
func onClickItem1(_: NSObject?) {
print(outlineView.clickedRow)
}
}
I tested this on macOS Sierra (10.12.5).
Old Answer
Starting from OS X 10.11, Apple finally added a method to access clickedRow easily. Just subclass NSTableView and override this method and you'll get the clickedRow as far as I experienced.
func willOpenMenu(menu: NSMenu, withEvent event: NSEvent)
This needs subclassing, but anyway, the cleanest and simplest way to access clickedRow.
Also, there's a pairing method.
func didCloseMenu(menu: NSMenu, withEvent event: NSEvent?)
Just select row on right-click by implementing menuForEvent: in NSTableView subclass:
#implementation MyTableView
- (NSMenu *)menuForEvent:(NSEvent *)theEvent
{
int row = [self rowAtPoint:[self convertPoint:theEvent.locationInWindow fromView:nil]];
if (row == -1)
return nil;
if (row != self.selectedRow)
[self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO];
return self.menu;
}
#end
if you dont need to open NSMenu but need to know "right click action with row number", i think most simple way is below. (Swift4 Code) Don't need any other connected Outer NSMenu class.
class SomeViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
#IBOutlet weak var tableView: NSTableView!
...
override func viewDidLoad() {
...
tableView.action = #selector(some method()) // left single click action
tableView.doubleAction = #selector(someMethod()) // left double click action
}
// right click action
override func rightMouseDown(with theEvent: NSEvent) {
let point = tableView.convert(theEvent.locationInWindow, from: nil)
let row = tableView.row(at: point)
print("right click")
print(row)
}
I had the same question but I also needed a solution that would work with multiple selected rows (because when multiple rows are selected and you right-click on one of them, NSTableView highlights all of them). Here's the property I added for this in a subclass of NSTableView:
var rightClickRowIndexes: IndexSet {
if clickedRow >= 0 {
return selectedRowIndexes.contains(clickedRow) ? selectedRowIndexes : IndexSet(integer: clickedRow)
} else {
return IndexSet()
}
}