0

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?

Chris Loonam
  • 5,735
  • 6
  • 41
  • 63
P.M.
  • 436
  • 6
  • 12

2 Answers2

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