Skip to content

Instantly share code, notes, and snippets.

@mminer
Last active February 7, 2023 07:14
Show Gist options
  • Save mminer/597c1b2c40adcf3c319f7feeade62ed4 to your computer and use it in GitHub Desktop.
Save mminer/597c1b2c40adcf3c319f7feeade62ed4 to your computer and use it in GitHub Desktop.
NSTextView subclass that displays and opens hyperlinks.
// You don't necessarily need this subclass if your NSTextView is selectable.
// If it isn't though, this allows you to have an uneditable, unselectable label where links work as expected.
import AppKit
class HyperlinkTextView: NSTextView {
override func mouseDown(with event: NSEvent) {
super.mouseDown(with: event)
openClickedHyperlink(with: event)
}
override func resetCursorRects() {
super.resetCursorRects()
addHyperlinkCursorRects()
}
/// Displays a hand cursor when a link is hovered over.
private func addHyperlinkCursorRects() {
guard let layoutManager = layoutManager, let textContainer = textContainer else {
return
}
let attributedStringValue = attributedString()
let range = NSRange(location: 0, length: attributedStringValue.length)
attributedStringValue.enumerateAttribute(.link, in: range) { value, range, _ in
guard value != nil else {
return
}
let rect = layoutManager.boundingRect(forGlyphRange: range, in: textContainer)
addCursorRect(rect, cursor: .pointingHand)
}
}
/// Opens links when clicked.
private func openClickedHyperlink(with event: NSEvent) {
let attributedStringValue = attributedString()
let point = convert(event.locationInWindow, from: nil)
let characterIndex = characterIndexForInsertion(at: point)
guard characterIndex < attributedStringValue.length else {
return
}
let attributes = attributedStringValue.attributes(at: characterIndex, effectiveRange: nil)
guard let urlString = attributes[.link] as? String, let url = URL(string: urlString) else {
return
}
NSWorkspace.shared.open(url)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment