Reference source -
Create tap-able "links" in the NSAttributedString of a UILabel?
It is Converted into swift 4.0
Try this -
Create a sub class for UILabel like below -
Swift 4.0
class CustomLabel: UILabel {
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
var textStorage = NSTextStorage() {
didSet {
textStorage.addLayoutManager(layoutManager)
}
}
var onCharacterTapped: ((_ label: UILabel, _ characterIndex: Int) -> Void)?
let tapGesture = UITapGestureRecognizer()
override var attributedText: NSAttributedString? {
didSet {
if let attributedText = attributedText {
textStorage = NSTextStorage(attributedString: attributedText)
} else {
textStorage = NSTextStorage()
}
}
}
override var lineBreakMode: NSLineBreakMode {
didSet {
textContainer.lineBreakMode = lineBreakMode
}
}
override var numberOfLines: Int {
didSet {
textContainer.maximumNumberOfLines = numberOfLines
}
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setUp()
}
override init(frame: CGRect) {
super.init(frame: frame)
setUp()
}
func setUp() {
isUserInteractionEnabled = true
layoutManager.addTextContainer(textContainer)
textContainer.lineFragmentPadding = 0
textContainer.lineBreakMode = lineBreakMode
textContainer.maximumNumberOfLines = numberOfLines
tapGesture.addTarget(self, action: #selector(CustomLabel.labelTapped(_:)))
addGestureRecognizer(tapGesture)
}
override func layoutSubviews() {
super.layoutSubviews()
textContainer.size = bounds.size
}
@objc func labelTapped(_ gesture: UITapGestureRecognizer) {
guard gesture.state == .ended else {
return
}
let locationOfTouch = gesture.location(in: gesture.view)
let textBoundingBox = layoutManager.usedRect(for: textContainer)
let textContainerOffset = CGPoint(x: (bounds.width - textBoundingBox.width) / 2 - textBoundingBox.minX,
y: (bounds.height - textBoundingBox.height) / 2 - textBoundingBox.minY)
let locationOfTouchInTextContainer = CGPoint(x: locationOfTouch.x - textContainerOffset.x, y: locationOfTouch.y - textContainerOffset.y)
let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer,
in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
onCharacterTapped?(self, indexOfCharacter)
}
}
Within your viewDidLoad
method of View controller create an instance of that class like below -
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let label = CustomLabel()
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-[view]-|",
options: [], metrics: nil, views: ["view" : label]))
view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-[view]-|",
options: [], metrics: nil, views: ["view" : label]))
let attributedString = NSMutableAttributedString(string: "String with a link", attributes: nil)
let linkRange = NSMakeRange(14, 4); // for the word "link" in the string above
let linkAttributes: [NSAttributedStringKey : AnyObject] = [
NSAttributedStringKey.foregroundColor : UIColor.blue, NSAttributedStringKey.underlineStyle : NSUnderlineStyle.styleSingle.rawValue as AnyObject,
NSAttributedStringKey.link: "http://www.apple.com" as AnyObject ]
attributedString.setAttributes(linkAttributes, range:linkRange)
label.attributedText = attributedString
label.onCharacterTapped = { label, characterIndex in
// DO YOUR STUFF HERE
}
}