Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how do I detect and make clickable links in a UILabel NOT using UITextView

I am building a chat app and for performance reasons I need to use UILabel's instead of UITextView's to display the chat messages. I have previously used TextView's but with data detection on the scrolling is very slow and choppy.

The problem is there is currently no link/phone/address etc... detection for UILabels.

How can I know where a link or phone number exists in a string, and then highlight and make it clickable within a UILabel?

I have read many articles on how to add attributes for links to do just that but they have all been links which you know the range or substring of.

I would like to take any string and find out whether is contains links and where those links are and then add the tapGestureRecognizer to the label and perform actions based on where the tap occurred.

I have tried to incorporate an external library (TTTAttributedLabel) but I'm using swift and found the documentation for swift to be limited. I did manage to import the library but the links are not being automatically detected.

like image 933
alionthego Avatar asked Feb 15 '17 04:02

alionthego


1 Answers

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
    }
}
like image 144
Prema Janoti Avatar answered Oct 11 '22 20:10

Prema Janoti