Set NSTableView clickedRow - macos

How to programmatically simulate Control-Click on a table view row to display the table context menu and the clickedRow border? Just like the Notes app does on clicking the ellipsis button.
I was able to trigger the menu by:
let point = tableView.convert(NSApp.currentEvent!.locationInWindow, from: nil)
let row = tableView.row(at: point)
tableView.menu?.popUp(positioning: tableView.menu!.item(at: 0), at: point, in: tableView)
But can't figure out a simple way of drawing the clicked row border and setting the table view's clickedRow.

Well, I was pretty close. Seems like menu(for: NSEvent) -> NSMenu? does the trick:
#IBAction func showContextMenu(_ sender: NSButton!) {
guard let event = NSApp.currentEvent,
let menu = tableView.menu(for: event) else {
return
}
NSMenu.popUpContextMenu(menu, with: event, for: tableView)
}

Related

How to create table with highlighted-on-mouseover cells on macOS cocoa?

I'm trying to create a tray popover app with table very similar to the one Dropbox has in it's popover view.
There is a table of files and when you hover mouse over a table cell, cell will highlight and show additional controls.
I'm not sure if NSTableView is suitable for this at all?
Does anyone have any advice?
This would be an ideal use of NSTableView. Using a view-based NSTableView, you'll be easily able to create the look of those cells (views). The highlight on mouse-over should be accomplishable if you add an NSTrackingArea to the table view (scroll view might be better) with -[NSView addTrackingArea:], which gives you callbacks for -mouseMoved: events. From that method you can use the locationInWindow property on the NSEvent, and then use NSTableView's -rowAtPoint: call to query which row you should change to display the hover event.
As a possible amendment here, I had to do the following to make this work:
highlightedRow: MyCustomRow?
func viewWillAppear(){
configureTableHighlight()
}
func configureTableHighlight() {
let trackingArea = NSTrackingArea(rect: scrollView.frame, options: [.mouseMoved, .activeInKeyWindow, .inVisibleRect], owner: self, userInfo: nil)
scrollView.addTrackingArea(trackingArea)
}
override func mouseMoved(with event: NSEvent) {
let pointInTableView = tableView.convert(event.locationInWindow, to: nil)
let row = tableView.row(at: pointInTableView)
if row == -1 {
highlightedRow?.highlight = false
highlightedRow = nil
return
}
guard let view = tableView.view(atColumn: 0, row: row, makeIfNecessary: false) as? MyCustomRow else {
return
}
if(highlightedRow == view){
return
}
view.highlight = true;
highlightedRow?.highlight = false
highlightedRow = view;
}
This might depend on where you add the trackingView however.
Additional reference:
mouseover detection in NSTableView's NSCell?

How to show Menu on button press instead of right mouse click in os x

When I assign a Menu to a button, the menu will be shown when the user right click the button.
I want to open the menu when the user left click the button.
Something similar to this:
I got it working using this code, However the menu is shown at the top right corner of my screen. What am I doing wrong?
#IBOutlet var meeenu: NSMenu!
#IBAction func Options(sender: NSButtonCell) {
meeenu.popUpMenuPositioningItem(meeenu.itemAtIndex(0), atLocation: NSEvent.mouseLocation(), inView: self.view)
}
I got it working finally using this code:
#IBOutlet var meeenu: NSMenu!
#IBAction func Options(sender: NSButtonCell) {
meeenu.popUpMenuPositioningItem(meeenu.itemAtIndex(0), atLocation: NSEvent.mouseLocation(), inView: nil)
}

Show popOver when right click on TableViewCell

I have a NSTableView and want to show a popOver if the User right click on a row. So i have this function:
override func rightMouseDown(theEvent: NSEvent) {
super.rightMouseDown(theEvent)
var point: NSPoint = talbeView.convertPoint(theEvent.locationInWindow, fromView: nil)
var row = tableView.rowAtPoint(point)
var rec = tableView.rectOfRow(row)
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let popOverViewController = storyboard!.instantiateControllerWithIdentifier("RightMousPopOver") as! NSViewController
var cell: DocumentCellView = tableView.viewAtColumn(0, row: row, makeIfNecessary: true) as! DocumentCellView
self.presentViewController(popOverViewController, asPopoverRelativeToRect: rec, ofView: cell, preferredEdge: 2, behavior: NSPopoverBehavior.Transient)
}
But the popOver appears only if I right click on the first row. I have debug the row and its right. Also if i change the row manuelle, the popOver show on the right row, but again only if I click on the first row.
I'm little bit confused. What is wrong withe my code?
Ok i think i found the answer. tableView.rectOfRow(row) is false. cell.frame is the right way to get the NSRec

Connect to ViewController from AppDelegate (Swift)

I have created a new OS X Cocoa Application using the standard Xcode Swift template (using StoryBoards).
I have implemented an IBAction in AppDelegate.swift to handle when the users selects "Open..." from the "File" menu. If the chosen file is a valid image file, I create an NSImage which I then want to display in the view of ViewController.
#IBAction func openFile(sender: NSMenuItem) {
var openPanel = NSOpenPanel()
openPanel.beginWithCompletionHandler { (result :Int) -> Void in
if result == NSFileHandlingPanelOKButton {
if let imageURL = openPanel.URL {
let image = NSImage(contentsOfURL: imageURL)
// PRESENT image IN THE VIEW CONTROLLER
}
}
}
However, I don't see any way to connect to ViewController from AppDelegate. I have only managed to find suggestions that I should look at self.window! in AppDelegate, but there is no such thing as a window in AppDelegate.
Thanks,
Michael Knudsen
It seems that AppDelegate can connect to objects only within Application Scene in a storyboard. If you want to get a ViewController, instantiate it from a storyboard.
sample:
#IBAction func menuAction(sender: AnyObject) {
if let storyboard = NSStoryboard(name: "Main", bundle: nil) {
let controller = storyboard.instantiateControllerWithIdentifier("VC1") as NSViewController
if let window = NSApplication.sharedApplication().mainWindow {
window.contentViewController = controller // just swap
}
}
}
You can access the mainWinow property and the contentViewController property to create a reference to your custom ViewController class. This is similar to the iOS rootViewController property.
let rootViewController = NSApplication.shared().mainWindow?.windowController?.contentViewController as! ViewController
Now you can use this reference to access IBOutlets on your main storyboard from your AppDelegate.
rootViewController.myTextView.textStorage?.mutableString.setString("Cats and dogs.")
This is good for a simple app with one Window with one ViewController.
I was stuck trying to do this same thing recently and managed to get the event I needed to update my view by creating the #IBAction in my ViewController and control dragging to my Application's First Responder (above the menu in my storyboard view).
Here's the question that got me out of the woods:
Application Menu Items Xcode
And thanks to Bluedome for the suggestion to connect it to First Responder's action.
If you control-drag from the menu to the first responder (red cube above menu) and picked an existing action, then you can "responder chain" to your view controller. In my case I attached Open to openFile and then in my view controller I added the following
override var acceptsFirstResponder: Bool {
return true
}
func openFile(sender: NSMenuItem) {
print("In view controller")
}
and it worked without any changes in AppDelegate. Most of the menus are already hooked up to first responder so just add the matching function name in your view controller.
See this comment and this document on Event Handling Basics for more info.
In Swift 5 and accessing new windows array:
#IBAction func menuAction(sender: AnyObject) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateInitialViewController()
// The windows in the array are ordered from back to front by window level;
// thus, the last window in the array is on top of all other app windows.
// On app launch, UIApplication.shared.windows.count == 1 anyway.
if let window = UIApplication.shared.windows.last {
window.rootViewController = controller
}
}

Growing NSTableView Row Height

I have a view-based NSTableView in a MacOSX app that structures data nicely. I would like to implement the NSTableView to have row heights which grow with the content of the data entered into one of the NSTextViews. I've subclassed an NSTextView to "grow" with the user text but the issue is that having the field embedded in the TableView causes the field to be clipped.
Does anyone have any suggestions as to how to go about implementing a growing row size?
You need to implement -tableView:heightOfRow: in your table view delegate and return the appropriate height for the row. Furthermore, you need to monitor the text views for changes in their height and call -noteHeightOfRowsWithIndexesChanged: on the table view when any of them changes. To monitor the height of the text views, it should suffice to observe the NSViewFrameDidChangeNotification that they will post.
(If you're using auto layout generally in your UI, I think you will have to leave the text views with translatesAutoresizingMaskIntoConstraints on, place them manually, and set their autoresizing masks as appropriate. Then, you would avoid setting any other constraints on them. This is because you need the frame to be set by the text layout manager but not by auto layout.)
I've managed to implement this in Swift 3 (with the help of this great tip and this and this SO answer):
Make sure the table view cell in the NSTableView has a delegate connection to your subclass/view controller which adopts the NSTextFieldDelegate protocol.
Also give it these constraints to make sure it resizes according to the height of the row:
In the delegate use this code:
var editedString: String? = nil
var textCellForHeight: NSTextFieldCell = NSTextFieldCell.init()
func control(_ control: NSControl, textShouldBeginEditing fieldEditor: NSText) -> Bool {
editedString = fieldEditor.string ?? ""
return true
}
func control(_ control: NSControl, textShouldEndEditing fieldEditor: NSText) -> Bool {
editedString = nil
return true
}
func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
if commandSelector == #selector(insertNewline(_:)) {
textView.insertNewlineIgnoringFieldEditor(self)
editedString = textView.string ?? ""
//The NSAnimationContext lines get rid of the animation that normally happens when the height changes
NSAnimationContext.beginGrouping()
NSAnimationContext.current().duration = 0
myTableView.noteHeightOfRows(withIndexesChanged: IndexSet.init(integer: selected))
NSAnimationContext.endGrouping()
myTable.needsDisplay = true
return true
}
return false
}
func tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat {
if let temp = editedString { //we know it’s currently being edited if it’s not nil
textCellForHeight.stringValue = temp
//in my case, there was a specific table column that I knew would be edited
//you might need to decide on the column width another way.
let column = myTable.tableColumns[myTable.column(withIdentifier: “TheColumnThatsBeingEdited”)]
let frame: NSRect = NSMakeRect(0, 0, column.width, CGFloat.greatestFiniteMagnitude)
return textCellForHeight.cellSize(forBounds: frame).height
}
return yourStandardHeightCGFloat
}

Resources