Create tap-able “links” in the NSAttributedString of a UILabel?

前端 未结 30 3081
半阙折子戏
半阙折子戏 2020-11-22 02:07

I have been searching this for hours but I\'ve failed. I probably don\'t even know what I should be looking for.

Many applications have text and in this text are web

30条回答
  •  甜味超标
    2020-11-22 02:46

    Here is my answer based on @Luca Davanzo's answer, override the touchesBegan event instead of a tap gesture:

    import UIKit
    
    public protocol TapableLabelDelegate: NSObjectProtocol {
       func tapableLabel(_ label: TapableLabel, didTapUrl url: String, atRange range: NSRange)
    }
    
    public class TapableLabel: UILabel {
    
    private var links: [String: NSRange] = [:]
    private(set) var layoutManager = NSLayoutManager()
    private(set) var textContainer = NSTextContainer(size: CGSize.zero)
    private(set) var textStorage = NSTextStorage() {
        didSet {
            textStorage.addLayoutManager(layoutManager)
        }
    }
    
    public weak var delegate: TapableLabelDelegate?
    
    public override var attributedText: NSAttributedString? {
        didSet {
            if let attributedText = attributedText {
                textStorage = NSTextStorage(attributedString: attributedText)
            } else {
                textStorage = NSTextStorage()
                links = [:]
            }
        }
    }
    
    public override var lineBreakMode: NSLineBreakMode {
        didSet {
            textContainer.lineBreakMode = lineBreakMode
        }
    }
    
    public override var numberOfLines: Int {
        didSet {
            textContainer.maximumNumberOfLines = numberOfLines
        }
    }
    
    
    public override init(frame: CGRect) {
        super.init(frame: frame)
        setup()
    }
    
    public required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setup()
    }
    
    public override func layoutSubviews() {
        super.layoutSubviews()
        textContainer.size = bounds.size
    }
    
    
    /// addLinks
    ///
    /// - Parameters:
    ///   - text: text of link
    ///   - url: link url string
    public func addLink(_ text: String, withURL url: String) {
        guard let theText = attributedText?.string as? NSString else {
            return
        }
    
        let range = theText.range(of: text)
    
        guard range.location !=  NSNotFound else {
            return
        }
    
        links[url] = range
    }
    
    private func setup() {
        isUserInteractionEnabled = true
        layoutManager.addTextContainer(textContainer)
        textContainer.lineFragmentPadding = 0
        textContainer.lineBreakMode = lineBreakMode
        textContainer.maximumNumberOfLines  = numberOfLines
    }
    
    public override func touchesBegan(_ touches: Set, with event: UIEvent?) {
        guard let locationOfTouch = touches.first?.location(in: self) else {
            return
        }
    
        textContainer.size = bounds.size
        let indexOfCharacter = layoutManager.glyphIndex(for: locationOfTouch, in: textContainer)
    
        for (urlString, range) in links {
            if NSLocationInRange(indexOfCharacter, range), let url = URL(string: urlString) {
                delegate?.tapableLabel(self, didTapUrl: urlString, atRange: range)
            }
        }
    }}
    

提交回复
热议问题