5

I'm following an example from Apple to create a hyperlink in a NSTextField (via an attributed string), and though the hyperlink itself works, there is an issue with the cursor. Namely, when you hover over the hyperlink, it displays the normal I-beam cursor until you click it. After clicked once, it displays the proper pointing-hand cursor.

I've searched for a while and there doesn't seem to be an easy answer to this issue, which is confusing because hyperlinks seem as if they would be pretty common. People have suggested using hyperlinked NSButtons (how do you get underline on hover, then?) and NSTextViews previously, but that seems sort of hack-y. What's the proper way to do hyperlinks in OSX programming?

Note: I stumbled across this article which shows a way of doing this with textviews and "category". Is this the proper way to do things? I want to be writing maintainable and clean code.

Thank you!

Connor
  • 533
  • 4
  • 12

2 Answers2

5

Building on Apple's example and @stevesliva's answer, here's what I came up with:

import Cocoa

extension NSAttributedString {

    /// Return an attributed string that looks like a hyperlink
    ///
    /// Based on code at <https://developer.apple.com/library/mac/qa/qa1487/_index.html>
    ///
    /// - parameters:
    ///    - string: text to be turned into a hyperlink
    ///    - URL: destination of the hyperlink
    /// - returns: `NSAttributedString`
    static func hyperlinkFromString(string: String, withURL URL: NSURL) -> NSAttributedString {

        let attrString = NSMutableAttributedString(string: string)
        let range = NSMakeRange(0, attrString.length)

        attrString.beginEditing()

        attrString.addAttribute(NSLinkAttributeName,
            value: URL.absoluteString,
            range: range)

        attrString.addAttribute(NSForegroundColorAttributeName,
            value: NSColor.blueColor(),
            range: range)

        attrString.addAttribute(NSUnderlineStyleAttributeName,
            value: NSNumber(int: Int32(NSUnderlineStyle.StyleSingle.rawValue)),
            range: range)

        attrString.fixAttributesInRange(range)

        attrString.endEditing()

        return attrString
    }
}

/// Subclass of NSTextField used to display hyperlinks
class HyperlinkTextField: NSTextField {
    /// Set content to be a hyperlink
    ///
    /// Based on code at <https://developer.apple.com/library/mac/qa/qa1487/_index.html>
    ///
    /// - parameters:
    ///    - title: text displayed in field
    ///    - URL: destination of hyperlink
    func setHyperlinkWithTitle(title: String, URL: NSURL) {
        allowsEditingTextAttributes = true
        selectable = true
        attributedStringValue = NSAttributedString.hyperlinkFromString(title,
            withURL: URL)
    }

    /// Always display a pointing-hand cursor
    override func resetCursorRects() {
        addCursorRect(bounds, cursor: NSCursor.pointingHandCursor())
    }
}
Kristopher Johnson
  • 81,409
  • 55
  • 245
  • 302
3

Good question! Hyperlinks in OSX controls, while functional, seem a little clunky.

I believe it's because the NSTextField doesn't have the focus until the first click.

The likely solution is to use this related question's answer(s). I was going to copy and paste some code, but the dscussion there is useful. The bottommost answer about how to subclass the view is likely the most robust.

The NSCursor class methods contain a list of the other possible cursor types.

Community
  • 1
  • 1
stevesliva
  • 5,351
  • 1
  • 16
  • 39
  • NSTextField not having focus until the first click makes a lot of sense, because that's the exact behavior I was observing when playing around with it. Very confusing! I had seen that post before, but I tried out the accepted answer and it didn't work (and I'm not sure why). I have no reason to believe subclassing would fix the problem (plus it seems messy), but if that's the only option I suppose I'll have to. – Connor Jun 27 '14 at 03:26
  • I won't take offense if you post your own specific answer, but I was fairly certain that your specific question is akin to changing the cursor over any other NSView subclass. Though I don't consider this a duplicate-- maybe there is a better way. – stevesliva Jun 27 '14 at 03:52
  • I didn't notice that addCursorRect:cursor: was a subclass of NSView, so that could potentially work. However, imagine a hyperlink within an NSAttributedString were to wrap around a line, then an NSRect wouldn't work - you'd need two of them. – Connor Jun 27 '14 at 04:00
  • Right. Using `NSView bounds` is going to change the cursor over the entire rectangular view. I have similarly ended up registering for clicks in entire table cells rather than ending up with nicely clickable links. pffft. – stevesliva Jun 27 '14 at 04:25
  • Okay, I got it to work. Subclassing an NSButton did work (as per the last example in the related question you linked), even though the accepted answer didn't! Thanks. – Connor Jun 27 '14 at 19:30