Simple clickable link in Cocoa and Swift - swift

Im writing a desktop app in swift for mac 10.11 and I would like to add a link to the about page.
Very much like this: https://developer.apple.com/library/mac/qa/qa1487/_index.html
I haven't been able to find a good tutorial or reference.
Any help would be much appreciated

Swift 4, xCode 9
#IBDesignable
class HyperlinkTextField: NSTextField {
#IBInspectable var href: String = ""
override func resetCursorRects() {
discardCursorRects()
addCursorRect(self.bounds, cursor: NSCursor.pointingHand)
}
override func awakeFromNib() {
super.awakeFromNib()
// TODO: Fix this and get the hover click to work.
let attributes: [NSAttributedStringKey: Any] = [
NSAttributedStringKey.foregroundColor: NSColor.linkColor,
NSAttributedStringKey.underlineStyle: NSUnderlineStyle.styleSingle.rawValue as AnyObject
]
attributedStringValue = NSAttributedString(string: self.stringValue, attributes: attributes)
}
override func mouseDown(with theEvent: NSEvent) {
if let localHref = URL(string: href) {
NSWorkspace.shared.open(localHref)
}
}
}

Modified the existing answers to allow for a substring of the label's text to become underlined and blue, so you can do something like: This is the answer
// A text field that can contain a hyperlink within a range of characters in the text.
#IBDesignable
public class SubstringLinkedTextField: NSTextField {
// the URL that will be opened when the link is clicked.
public var link: String = ""
#available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'link' instead.")
#IBInspectable public var HREF: String {
get {
return self.link
}
set {
self.link = newValue
self.needsDisplay = true
}
}
// the substring within the field's text that will become an underlined link. if empty or no match found, the entire text will become the link.
public var linkText: String = ""
#available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'linkText' instead.")
#IBInspectable public var LinkText: String {
get {
return self.linkText
}
set {
self.linkText = newValue
self.needsDisplay = true
}
}
override public func awakeFromNib() {
super.awakeFromNib()
self.allowsEditingTextAttributes = true
self.isSelectable = true
let url = URL(string: self.link)
let attributes: [NSAttributedStringKey: AnyObject] = [
NSAttributedStringKey(rawValue: NSAttributedStringKey.link.rawValue): url as AnyObject
]
let attributedStr = NSMutableAttributedString(string: self.stringValue)
if self.linkText.count > 0 {
if let range = self.stringValue.indexOf(substring: self.linkText) {
attributedStr.setAttributes(attributes, range: range)
} else {
attributedStr.setAttributes(attributes, range: NSMakeRange(0, self.stringValue.count))
}
} else {
attributedStr.setAttributes(attributes, range: NSMakeRange(0, self.stringValue.count))
}
self.attributedStringValue = attributedStr
}
}

The easiest way is to subclass NSTextField to create a HyperlinkTextField. Below is an example:
First, let's add a HyperlinkTextField class to your project:
// HyperlinkTextField.swift
import Cocoa
#IBDesignable
class HyperlinkTextField: NSTextField {
#IBInspectable var href: String = ""
override func awakeFromNib() {
super.awakeFromNib()
let attributes: [String: AnyObject] = [
NSForegroundColorAttributeName: NSColor.blueColor(),
NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue
]
self.attributedStringValue = NSAttributedString(string: self.stringValue, attributes: attributes)
}
override func mouseDown(theEvent: NSEvent) {
NSWorkspace.sharedWorkspace().openURL(NSURL(string: self.href)!)
}
}
Next, in Interface Builder, drag a label from the Object library to your window.
Select that label, go to the menu View > Utilities > Show Identity Inspector (or press Cmd + Opt + 3) and change the class to HyperlinkTextField
Go to the Attributes Inspector (Cmd + Opt + 4) and set Href to the URL you want to visit.
The label shows black text in Interface Builder but everything will be fine when you run your app. Clicking on the label will open the link in your default browser.
One thing I couldn't achieve was to make the HyperlinkTextField shows up as blue and underlined in Interface Builder. Comments on how to do that are welcome.

Exactly what I needed. Here is the Swift3 version:
import Cocoa
#IBDesignable
class HyperTextField: NSTextField {
#IBInspectable var href: String = ""
override func awakeFromNib() {
super.awakeFromNib()
let attributes: [String: AnyObject] = [
NSForegroundColorAttributeName: NSColor.blue,
NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue as AnyObject
]
self.attributedStringValue = NSAttributedString(string: self.stringValue, attributes: attributes)
}
override func mouseDown(with event: NSEvent) {
NSWorkspace.shared().open(URL(string: self.href)!)
}
}

let link = NSTextField()
link.isBezeled = false
link.drawsBackground = false
link.isEditable = false
link.isSelectable = true
link.allowsEditingTextAttributes = true
let url = URL(string: "http://www.google.com")
let linkTextAttributes: [NSAttributedStringKey: Any] = [
NSAttributedStringKey.underlineStyle: NSUnderlineStyle.styleSingle.rawValue,
NSAttributedStringKey.foregroundColor: NSColor.blue,
NSAttributedStringKey.link: url as Any
]
let string = "whatever"
link.attributedStringValue = NSAttributedString(string: string, attributes: linkTextAttributes)
window.contentView?.addSubview(link)

swift 4.2
class HyperlinkTextField: NSTextField {
private var detector: NSDataDetector!
override func awakeFromNib() {
super.awakeFromNib()
detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
}
func setText(text: String ){
let matches = detector.matches(in: text, options: [], range: NSRange(location: 0, length: text.utf16.count))
for match in matches {
guard let range = Range(match.range, in: text) else { continue }
let url = text[range]
let attributedString = NSMutableAttributedString(string: text)
attributedString.addAttribute(.link, value: url, range: match.range)
self.attributedStringValue = attributedString
}
}
}

Related

Create a hashtag in the text view swift

I found this question: I'm implementing a hashtag recognizer in UITextView but it never highlights two words with the same beginning?. By doing that I want to check the user's run time input text on the text view and each time user types a #hashtag, It will automatically highlight after adding a comma.
Code:-
import UIKit
class ViewController: UIViewController,UITextViewDelegate {
#IBOutlet weak var txtView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
txtView.delegate = self
}
func textViewDidChange(_ textView: UITextView) {
textView.resolveHashTags()
}
}
extension UITextView {
func resolveHashTags() {
let nsText = NSString(string: self.text)
let words = nsText.components(separatedBy: CharacterSet(charactersIn: "#ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_").inverted)
let attrString = NSMutableAttributedString()
attrString.setAttributedString(self.attributedText)
for word in words {
if word.count < 3 {
continue
}
if word.hasPrefix("#") {
let matchRange:NSRange = nsText.range(of: word as String)
let stringifiedWord = word.dropFirst()
if let firstChar = stringifiedWord.unicodeScalars.first, NSCharacterSet.decimalDigits.contains(firstChar) {
} else {
attrString.addAttribute(NSAttributedString.Key.link, value: "hash:\(stringifiedWord)", range: matchRange)
}
}
}
self.attributedText = attrString
}
}
Current ScreenShot:-
Question:- Can someone please explain to me how to highlight the text after adding a comma? I've tried to implement by above code but no results yet.
Any help would be greatly appreciated.
Thanks in advance.

How do I underline characters in the comments using Swift

For example, I'm writing a function which I want to show an add method. I wanted to underline the comment above like so.
// This is the comment I want to underline
// Adding a few other things you can do with comments would also be helpful
func helpMeUnderlineThisComment(comment: String) -> String {
for char in comment {
if char == comment {
return char.underlined()
}
}
return nil
}
You can simply do it by making an extension:
extension NSAttributedString {
var underlined: NSAttributedString {
return applying(attributes: [.underlineStyle: NSUnderlineStyle.single.rawValue])
}
func applying(attributes: [NSAttributedString.Key: Any]) -> NSAttributedString {
let copy = NSMutableAttributedString(attributedString: self)
let range = (string as NSString).range(of: string)
copy.addAttributes(attributes, range: range)
return copy
}
}
How to use it?
class ViewController: UIViewController {
#IBOutlet weak var textLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.textLabel.text = "Some Text"
self.textLabel.attributedText = textLabel.attributedText?.underlined
}}

Custom Attributed string action in swift

I want to make the attributed string action in swift. I want append my custom variable to table view cell data.
I added colour and line also for that string, now i want to add the link for that added attributed string.
Please find my code with the following.
I add this code into cell for row at Index path.
let value = NSMutableAttributedString(string: " tap here.", attributes:[NSAttributedStringKey.link: URL(string: "http://www.google.com")!]).withTextColor(UIColor.disSatifyclr).withUnderlineColor(UIColor.disSatifyclr).withUnderlineStyle(.styleSingle)
if (cell.desclbl.text?.contains("more about donating, "))! {
let description = descriptions[indexPath.section][indexPath.row]
let attributedString = NSMutableAttributedString(string: description)
attributedString.append(value)
cell.desclbl.attributedText = attributedString
}
Here you can handle action using UITextView instead of UILabel on tap of string
class ViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
textView.attributedText = prepareLink()
textView.tintColor = UIColor.black
textView.isSelectable = true
textView.isEditable = false
textView.delegate = self
}
func prepareLink() -> NSMutableAttributedString {
let formattedString = NSMutableAttributedString(string: " tap here ")
let linkAttribute = [
NSAttributedString.Key.foregroundColor: UIColor.green,
NSAttributedString.Key.underlineColor: UIColor.green,
NSAttributedString.Key.underlineStyle: 1,
NSAttributedString.Key.link: URL(string: "http://www.google.com")!
] as [NSAttributedString.Key : Any]
formattedString.addAttributes(linkAttribute, range: NSMakeRange(0, formattedString.length))
return formattedString
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if (URL.absoluteString == "http://www.google.com") {
// Do Something
}
return true
}
}
Note: If you have to continue with UILabel then you have to implement UITapGesture to handle action.

Change search field's icon

I try to implement search behavior like in Xcode: if you enter something in search field, icon changes color.
I delegate both searchFieldDidStartSearching and searchFieldDidEndSearching to controller and change the image.
The problem is icon's image changes only when window lose it's focus.
class ViewController: NSViewController {
#IBOutlet weak var searchField: NSSearchField!
func searchFieldDidStartSearching(_ sender: NSSearchField) {
print("\(#function)")
(searchField.cell as! NSSearchFieldCell).searchButtonCell?.image = NSImage.init(named: "NSActionTemplate")
}
func searchFieldDidEndSearching(_ sender: NSSearchField) {
print("\(#function)")
(searchField.cell as! NSSearchFieldCell).searchButtonCell?.image = NSImage.init(named: "NSHomeTemplate")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
Thanks in advance for any ideas/suggestions.
Although I don't know the reason, it works:
NSApp.mainWindow?.resignMain()
NSApp.mainWindow?.becomeMain()
Here is the whole code:
class MyViewController: NSViewController {
private lazy var searchField: NSSearchField = {
let searchField = NSSearchField(string: "")
if let searchButtonCell = searchField.searchButtonCell {
searchButtonCell.setButtonType(.toggle)
let filterImage = #imageLiteral(resourceName: "filter")
searchButtonCell.image = filterImage.tinted(with: .systemGray)
searchButtonCell.alternateImage = filterImage.tinted(with: .systemBlue)
}
searchField.focusRingType = .none
searchField.bezelStyle = .roundedBezel
searchField.delegate = self
return searchField
}()
...
}
extension MyViewController: NSSearchFieldDelegate {
func searchFieldDidStartSearching(_ sender: NSSearchField) {
sender.searchable = true
}
func searchFieldDidEndSearching(_ sender: NSSearchField) {
sender.searchable = false
}
}
extension NSSearchField {
var searchButtonCell: NSButtonCell? {
(self.cell as? NSSearchFieldCell)?.searchButtonCell
}
var searchable: Bool {
get {
self.searchButtonCell?.state == .on
}
set {
self.searchButtonCell?.state = newValue ? .on : .off
self.refreshSearchIcon()
}
}
private func refreshSearchIcon() {
NSApp.mainWindow?.resignMain()
NSApp.mainWindow?.becomeMain()
}
}
extension NSImage {
func tinted(with color: NSColor) -> NSImage? {
guard let image = self.copy() as? NSImage else { return nil }
image.lockFocus()
color.set()
NSRect(origin: NSZeroPoint, size: self.size).fill(using: .sourceAtop)
image.unlockFocus()
image.isTemplate = false
return image
}
}
I was having the same issue. A simple override fixed this issue for me
extension NSSearchField{
open override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
}
}
As you can see when you click inside the view it's still focussed on the search text field(as you can still type in it after you clicked underneath it). Since the change image is on when it loses focus, you should check if you clicked outside of the text field.
Solve problem by subclassing NSSearchFieldCell and assign this class to field's cell.
You don't even need to subclass NSSearchFieldCell.
When you create your NSSearchField from code, you can do something like this:
if let searchFieldCell = searchField.cell as? NSSearchFieldCell {
let image = NSImage(named: "YourImageName")
searchFieldCell.searchButtonCell?.image = image
searchFieldCell.searchButtonCell?.alternateImage = image // Optionally
}
If you're using storyboards, you can do the same in didSet of your #IBOutlet.

why do interactive parts of UITextview require a long press?

I need to make part of a text interactive, this is a screenshot of what I made so far, which is good, but the problems are
1- it requires a long tap
2- and force me to have the text selectable
any idea about how to solve this?
import UIKit
class ViewController: UIViewController, UITextViewDelegate
{
#IBOutlet weak var textView: UITextView!
override func viewDidLoad()
{
super.viewDidLoad()
let attributedString = NSMutableAttributedString(string: "text with a link", attributes: nil)
attributedString.setSubstringAsLink(substring: "link", linkURL: "CUSTOM://WHATEVER")
let linkAttributes: [String : AnyObject] = [NSForegroundColorAttributeName : UIColor.redColor(), NSUnderlineColorAttributeName : UIColor.redColor(), NSUnderlineStyleAttributeName : NSUnderlineStyle.StyleSingle.rawValue]
textView.linkTextAttributes = linkAttributes
textView.attributedText = attributedString
textView.selectable = true
textView.editable = false
textView.userInteractionEnabled = true
textView.delegate = self
}
func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool
{
if URL == "CUSTOM://WHATEVER"
{
print("????")
}
else
{
print("!!!!")
}
return true
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
extension NSMutableAttributedString
{
public func setSubstringAsLink(substring substring: String, linkURL: String) -> Bool
{
let range = self.mutableString.rangeOfString(substring)
if range.location != NSNotFound
{
self.addAttribute(NSLinkAttributeName, value: linkURL, range: range)
return true
}
return false
}
}
I don't know how to solve your specific issues but I guess you are better of subclassing UILabel instead of UITextView. Also, try to not re-invent the wheel, use TTAttributedLabel or similar libraries to do what you want.
I resolved this problem like below,
let attributedString = NSMutableAttributedString(string: "Press this link text")
attributedString.addAttribute(NSLinkAttributeName, value: "http://www.google.com", range: NSRange(location: 6, length: 14))
let linkAttributes = [
NSForegroundColorAttributeName: UIColor.greyishBrownColor(),
NSUnderlineColorAttributeName: UIColor.greyishBrownColor(),
NSUnderlineStyleAttributeName: NSUnderlineStyle.StyleSingle.rawValue
]
agreementsTextView.delegate = self
agreementsTextView.attributedText = attributedString
agreementsTextView.linkTextAttributes = linkAttributes
// Delegate Method
func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
if URL.absoluteString == "http://www.google.com" || URL.relativePath == "http://www.google.com" {
print("Tapped")
}
return false
}
Range might be wrong, this should fix your problem