如何在UILabel中检测并制作可点击的链接,而不是使用UITextView

时间:2022-02-13 06:56:54

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.

我正在构建一个聊天应用程序,出于性能原因,我需要使用UILabel而不是UITextView来显示聊天消息。我以前使用过TextView,但滚动时的数据检测速度非常慢且不连贯。

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

问题是目前没有链接/电话/地址等... 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?

如何知道字符串中存在链接或电话号码的位置,然后在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.

我想获取任何字符串并找出是否包含链接以及这些链接的位置,然后将tapGestureRecognizer添加到标签并根据点击发生的位置执行操作。

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.

我试图合并一个外部库(TTTAttributedLabel),但我正在使用swift,发现swift的文档有限。我确实设法导入了库,但没有自动检测到链接。

1 个解决方案

#1


3  

Reference source -

参考来源 -

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

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

It is Converted into swift 4.0

它被转换为swift 4.0

Try this -

尝试这个 -

Create a sub class for UILabel like below -

为UILabel创建一个子类,如下所示 -

Swift 4.0

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 -

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

#1


3  

Reference source -

参考来源 -

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

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

It is Converted into swift 4.0

它被转换为swift 4.0

Try this -

尝试这个 -

Create a sub class for UILabel like below -

为UILabel创建一个子类,如下所示 -

Swift 4.0

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 -

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