I have a program that has a NSTableView populated with files to be uploaded. Once the file is sent, the Text Cell with the file's name gets a hyperlink placed into it (the array data is given an NSMutableString with an NSLinkAttributeName attribute). How do I allow users to click this link to open the webpage in their default browser?
Asked
Active
Viewed 378 times
2 Answers
2
After much searching and trying multiple methods, this is what I came up with as a solution.
Creating a custom class that extends NSTableViewCell:
class TableViewCellCursor: NSTableCellView {
internal var active = false
//MARK: - View Life Cycle
override func awakeFromNib() {
superview?.awakeFromNib()
self.createTrackingArea()
}
//MARK: - IBActions
override func mouseEntered(theEvent: NSEvent) {
if (NSCursor.currentCursor() == NSCursor.arrowCursor() && active) {
NSCursor.pointingHandCursor().set()
}
}
override func mouseExited(theEvent: NSEvent) {
if (NSCursor.currentCursor() == NSCursor.pointingHandCursor() && active) {
NSCursor.arrowCursor().set()
}
}
//Informs the receiver that the mouse cursor has moved into a cursor rectangle.
override func cursorUpdate(event: NSEvent) {
if (active) {
NSCursor.pointingHandCursor().set()
}
}
//MARK: - Util
func createTrackingArea() {
var focusTrackingAreaOptions:NSTrackingAreaOptions = NSTrackingAreaOptions.ActiveInActiveApp
focusTrackingAreaOptions |= NSTrackingAreaOptions.MouseEnteredAndExited
focusTrackingAreaOptions |= NSTrackingAreaOptions.AssumeInside
focusTrackingAreaOptions |= NSTrackingAreaOptions.InVisibleRect
var focusTrackingArea:NSTrackingArea = NSTrackingArea(rect: NSZeroRect,
options: focusTrackingAreaOptions,
owner: self, userInfo: nil)
self.addTrackingArea(focusTrackingArea)
}
}
Checking first responder status when the NSTableView selection changes. This is necessary because the table's selection can be changed, even when it is not the firstResponder:
func tableViewSelectionDidChange(aNotification: NSNotification) {
if (self.firstResponder == filesToTransferTable) {
changeSelectedRowTextColorTo(NSColor.whiteColor(), unselectedColor: NSColor.blueColor())
} else {
changeSelectedRowTextColorTo(NSColor.blackColor(), unselectedColor: NSColor.blueColor())
}
}
func changeSelectedRowTextColorTo(selectedColor: NSColor, unselectedColor: NSColor) {
let selectedRows = filesToTransferTable.selectedRowIndexes
for (index, tableEntry) in enumerate (tableData) {
if tableData[index]["FileName"] is NSMutableAttributedString {
var name = tableData[index]["FileName"] as! NSMutableAttributedString
var range = NSMakeRange(0, NSString(string:name.string).length)
name.beginEditing()
name.removeAttribute(NSForegroundColorAttributeName, range: range)
if (selectedRows.containsIndex(index)) {
name.addAttribute(NSForegroundColorAttributeName, value:selectedColor, range:range)
} else {
name.addAttribute(NSForegroundColorAttributeName, value:unselectedColor, range:range)
}
name.endEditing()
tableData[index]["FileName"] = name
}
filesToTransferTable.reloadDataForRowIndexes(NSIndexSet(index: index), columnIndexes: NSIndexSet(index:0))
}
}
Adding KVO for checking when FirstResponder changes:
//This is somewhere in your code where you initialize things
//KVO for first responder behavior regarding tableView and updating attributedStrings' colors
self.addObserver(self, forKeyPath: "firstResponder", options: NSKeyValueObservingOptions.Old | NSKeyValueObservingOptions.New, context: nil)
override func observeValueForKeyPath(keyPath: String, ofObject object: AnyObject, change: [NSObject : AnyObject], context: UnsafeMutablePointer<Void>) {
if (change[NSKeyValueChangeNewKey] is NSTableView) {
changeSelectedRowTextColorTo(NSColor.whiteColor(), unselectedColor: NSColor.blueColor())
} else if (change[NSKeyValueChangeOldKey] is NSTableView) {
changeSelectedRowTextColorTo(NSColor.blackColor(), unselectedColor: NSColor.blueColor())
}
}
Finally, checking if the main window (the app itself) is in focus (if this is not done, then the colors won't change appropriately when the window loses focus):
//Put these in the same place as the KVO code
NSNotificationCenter.defaultCenter().addObserver(self, selector: "windowDidBecomeKey:",
name: NSWindowDidBecomeKeyNotification , object: self)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "windowDidResignKey:",
name: NSWindowDidResignKeyNotification , object: self)
func windowDidBecomeKey(notification: NSNotification) {
if (self.firstResponder == filesToTransferTable) {
changeSelectedRowTextColorTo(NSColor.whiteColor(), unselectedColor: NSColor.blueColor())
} else {
changeSelectedRowTextColorTo(NSColor.blackColor(), unselectedColor: NSColor.blueColor())
}
}
func windowDidResignKey(notification: NSNotification) {
if (self.firstResponder == filesToTransferTable) {
changeSelectedRowTextColorTo(NSColor.blackColor(), unselectedColor: NSColor.blueColor())
}
}

P.M.
- 436
- 6
- 12
0
Text fields automatically support clicking on embedded links, but only if they are at least selectable (if not editable). So, set your text field to be selectable.

Ken Thomases
- 88,520
- 7
- 116
- 154
-
Sorry, the NSTableView's cells are Text Cells, not Text Views. I've set the Text Cell with the hyperlink to "selectable," but I still can't click the link to open it. Do I have to use a different type of cell than a Text Cell? Thanks! – P.M. Mar 31 '15 at 23:01
-
Is the text cell configured to allow rich text? That's also necessary for it to use attributed text. – Ken Thomases Mar 31 '15 at 23:34
-
Yep, the cell is configured to allow rich text, is selectable, and the action is set to "Send on Enter Only." For now I've been using NSTable's tableViewSelectionDidChange, but this feels more like a hack than actually giving the behavior I'd like. – P.M. Apr 07 '15 at 21:25
-
Have you tried in a text field (label) that's not in a table, just as an experiment? Can you get it work there? In your table, does the link work if you click it after the row has already been selected? – Ken Thomases Apr 07 '15 at 23:48
-
I can get a link to work in a text view, but not in a selected row. Is there some way to embed text views into table cells? How would I populate this if I did? – P.M. Apr 14 '15 at 22:40
-
I've found that if I make the cell itself only to "selectable", and the parent table column "editable," the user can get into the cell as if to edit it (though they can't), and then they can click the link. Not an ideal way to click the link though :/ – P.M. Apr 14 '15 at 23:03
-
Do you mean they have to click the cell once to select it and then click a second time on the text field to use the link? If so, you can probably fix that with an override of `-validateProposedFirstResponder:forEvent:` in a subclassed table view. – Ken Thomases Apr 15 '15 at 01:08
-
I actually ended up with the desired behavior, but it required extending the NSTableCellView in a custom class, and lot's of checking FirstResponder status. I'll post my findings below. – P.M. Jul 24 '15 at 21:27