1. Using hitTest(_:with:)
UIView
has a method called hitTest(_:with:)
that has the following definition:
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView?
Returns the farthest descendant of the receiver in the view hierarchy (including itself) that contains a specified point.
UITextView
being a subclass of UIView
, you can implement this method in any subclass of UITextView
you may want to create.
The following Swift 3 example shows a UITableViewController
that contains a single static UITableViewCell
. The UITableViewCell
embeds a UITextView
. Any tap on a link inside the UITextView
will launch Safari app; any tap on basic text inside the UITextView
will trigger a push segue to the following UIViewController
.
LinkTextView.swift
import UIKit
class LinkTextView: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
func configure() {
isScrollEnabled = false
isEditable = false
isUserInteractionEnabled = true
isSelectable = true
dataDetectorTypes = .link
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
// Get the character index from the tap location
let characterIndex = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
// if we detect a link, handle the tap by returning self...
if let _ = textStorage.attribute(NSLinkAttributeName, at: characterIndex, effectiveRange: nil) {
return self
}
// ... otherwise return nil ; the tap will go on to the next receiver
return nil
}
}
TextViewCell.swift
import UIKit
class TextViewCell: UITableViewCell {
@IBOutlet weak var textView: LinkTextView!
}
TableViewController.swift
import UIKit
class TableViewController: UITableViewController {
@IBOutlet weak var cell: TextViewCell!
override func viewDidLoad() {
super.viewDidLoad()
// Add text to cell's textView
let text = "http://www.google.com Lorem ipsum dolor sit amet, consectetur adipiscing elit, http://www.yahoo.com sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
cell.textView.text = text
}
}
2. Using point(inside:with:)
As an alternative to override hitTest(_:with:)
, you can use point(inside:with:)
. point(inside:with:)
has the following declaration:
func point(inside point: CGPoint, with event: UIEvent?) -> Bool
Returns a Boolean value indicating whether the receiver contains the specified point.
The following code shows how to implement point(inside:with:)
instead of hitTest(_:with:)
in your UITextView
subclass:
LinkTextView.swift
import UIKit
class LinkTextView: UITextView {
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
func configure() {
isScrollEnabled = false
isEditable = false
isUserInteractionEnabled = true
isSelectable = true
dataDetectorTypes = .link
}
override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
// Get the character index from the tap location
let characterIndex = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
// if we detect a link, handle the tap by returning true...
if let _ = textStorage.attribute(NSLinkAttributeName, at: characterIndex, effectiveRange: nil) {
return true
}
// ... otherwise return false ; the tap will go on to the next receiver
return false
}
}
The complete project is available on this GitHub repo: LinkTextViewCell.
You can learn more about managing a UITextView
inside a UITableViewCell
by reading Swifty Approach to Handling UITextViews With Links in Cells.
You can learn more about the difference between hitTest(_:with:)
and point(inside:with:)
by reading Apple's Guide and Sample Code: "Event Delivery: The Responder Chain".
willSelectRowAtIndexPath
– HeidyUITextView
is editable and links selectable, the user might find it a bit hard to edit the links ? – DrawingUIView
s instead. (again, complicated). Maybe someone else has a better solution. – Drawing