如何在不使用UITextView的情况下检测和创建可点击链接的UILabel

14
我正在构建一款聊天应用程序,出于性能原因,我需要使用UILabel而不是UITextView来显示聊天消息。我之前使用过TextView,但启用数据检测后滚动非常缓慢和卡顿。
问题在于目前UILabel没有链接/电话/地址等检测功能。
如何知道字符串中存在哪些链接或电话号码,并在UILabel内突出显示并使其可点击?
我阅读了许多文章,以添加链接属性来实现此功能,但它们都是针对已知范围或子字符串的链接。
我想取任何字符串,查找其中是否包含链接以及这些链接在哪里,然后向标签添加tapGestureRecognizer,并根据点击发生的位置执行操作。
我尝试使用外部库(TTTAttributedLabel),但我正在使用Swift,并发现Swift文档有限。我设法导入该库,但未能自动检测到链接。
1个回答

8

参考源 -

如何在UILabel的NSAttributedString中创建可点击的“链接”?

已转换为Swift 4.0版本

请使用以下代码创建一个UILabel的子类 -

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)
 }

}

在View控制器的viewDidLoad方法中创建该类的实例,如下所示:

    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
    }
}

2
你正在在viewDidLoad()中添加一个链接,你知道它的范围是(14, 4)。而在我的情况下,我不知道链接的范围。这就是我想要确定的内容。 - alionthego
据我所知,如果您想使用UILable实现这一点,那么您应该知道范围,否则您应该选择UITextView。 - Prema Janoti
换句话说,这是一个由两部分组成的问题:(1)如何在UILabel中添加可点击链接?答案已经告诉你了。(2)如何在运行时找到链接的位置?答案是使用解析器。例如,如果您的源基于HTML,您可以使用Soup库。稍后我会为您提供更多详细信息。 - John Pang
这似乎不起作用。无论在标签的哪个位置被点击,characterIndex始终返回0。label.onCharacterTapped = { label, characterIndex in print(characterIndex) } - lucius degeer
以下代码每次返回0: let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) - lucius degeer
@OnurŞahindur 谢谢。 - Prema Janoti

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接