ios Swift : 点击 UILabel 的一部分文本

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36043006/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 08:42:19  来源:igfitidea点击:

Swift : tap on a part of text of UILabel

iosswiftuilabelnsattributedstringnslayoutmanager

提问by Ashley

I have a problem that "boundingRectForGlyphRange" always returns CGRect.zero "0.0, 0.0, 0.0, 0.0". "boundingRectForGlyphRange" is not working. For example, I am coding for touching on a part of text of UILabel feature. My text has first part is "any text" and second one is "READ MORE". I want the tap recognizer only work when I touch "READ MORE". If I touch on any point on UILabel, "CGRectContainsPoint" always return true,then the action called

我有一个问题,“boundingRectForGlyphRange”总是返回 CGRect.zero“0.0, 0.0, 0.0, 0.0”。“boundingRectForGlyphRange”不起作用。例如,我正在编码以触摸 UILabel 功能的一部分文本。我的文字的第一部分是“任何文字”,第二部分是“阅读更多”。我希望点击识别器仅在我触摸“阅读更多”时工作。如果我触摸 UILabel 上的任何一点,“CGRectContainsPoint”总是返回 true,然后调用的操作

Here my code:

这是我的代码:

override func viewDidLoad() {
        super.viewDidLoad()

        // The full string

        let firstPart:NSMutableAttributedString = NSMutableAttributedString(string: "Lorem ipsum dolor set amit ", attributes: [NSFontAttributeName: UIFont.systemFontOfSize(13)])
        firstPart.addAttribute(NSForegroundColorAttributeName, value: UIColor.blackColor(),
            range: NSRange(location: 0, length: firstPart.length))
        info.appendAttributedString(firstPart)

        // The "Read More" string that should be touchable
        let secondPart:NSMutableAttributedString = NSMutableAttributedString(string: "READ MORE", attributes: [NSFontAttributeName: UIFont.systemFontOfSize(14)])
        secondPart.addAttribute(NSForegroundColorAttributeName, value: UIColor.blackColor(),
            range: NSRange(location: 0, length: secondPart.length))
        info.appendAttributedString(secondPart)

        lblTest.attributedText = info

        // Store range of chars we want to detect touches for
        moreStringRange = NSMakeRange(firstPart.length, secondPart.length)
        print("moreStringRange\(moreStringRange)")

        tapRec.addTarget(self, action: "didTap:")
        lblTest.addGestureRecognizer(tapRec)

    }


    func didTap(sender:AnyObject) {
        // Storage class stores the string, obviously
        let textStorage:NSTextStorage = NSTextStorage(attributedString: info)
        // The storage class owns a layout manager
        let layoutManager:NSLayoutManager = NSLayoutManager()
        textStorage.addLayoutManager(layoutManager)

        // Layout manager owns a container which basically
        // defines the bounds the text should be contained in
        let textContainer:NSTextContainer = NSTextContainer(size: lblTest.frame.size)
        textContainer.lineFragmentPadding = 0
        textContainer.lineBreakMode = lblTest.lineBreakMode

        // Begin computation of actual frame
        // Glyph is the final display representation
        var glyphRange = NSRange()
        // Extract the glyph range
        layoutManager.characterRangeForGlyphRange(moreStringRange!, actualGlyphRange: &glyphRange)

        // Compute the rect of glyph in the text container
        print("glyphRange\(glyphRange)")
        print("textContainer\(textContainer)")
        let glyphRect:CGRect = layoutManager.boundingRectForGlyphRange(glyphRange, inTextContainer: textContainer)

        // Final rect relative to the textLabel.
        print("\(glyphRect)")

        // Now figure out if the touch point is inside our rect
        let touchPoint:CGPoint = tapRec.locationOfTouch(0, inView: lblTest)

        if CGRectContainsPoint(glyphRect, touchPoint) {
            print("User tapped on Read More. So show something more")
        }
    }

It's just a demo to test for what I want to do:

这只是一个测试我想做的事情的演示:

enter image description here

在此处输入图片说明

Any help would be greatly appreciated.

任何帮助将不胜感激。

回答by Ilesh P

swift 4.2

快速 4.2

Please find the solution here for getting specific text actionof Label.

请在这里找到解决方案,让特定的文本actionLabel

enter image description here

在此处输入图片说明

1) Label declaration

1) 标签声明

@IBOutlet weak var lblTerms: UILabel!

2) Set attributed text to the label

2) 将属性文本设置为标签

let text = "Please agree for Terms & Conditions."
lblTerms.text = text
self.lblTerms.textColor =  UIColor.white
let underlineAttriString = NSMutableAttributedString(string: text)
let range1 = (text as NSString).range(of: "Terms & Conditions.")
        underlineAttriString.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: range1)
        underlineAttriString.addAttribute(NSAttributedString.Key.font, value: UIFont.init(name: Theme.Font.Regular, size: Theme.Font.size.lblSize)!, range: range1)
        underlineAttriString.addAttribute(NSAttributedString.Key.foregroundColor, value: Theme.color.primaryGreen, range: range1)
lblTerms.attributedText = underlineAttriString
lblTerms.isUserInteractionEnabled = true
lblTerms.addGestureRecognizer(UITapGestureRecognizer(target:self, action: #selector(tapLabel(gesture:))))

It looks like the above image.

它看起来像上图。

3) Add the tapLable action method to the controller

3)在控制器中添加tapLable动作方法

@IBAction func tapLabel(gesture: UITapGestureRecognizer) {
   let termsRange = (text as NSString).range(of: "Terms & Conditions")
   // comment for now
   //let privacyRange = (text as NSString).range(of: "Privacy Policy")

   if gesture.didTapAttributedTextInLabel(label: lblTerms, inRange: termsRange) {
       print("Tapped terms")
   } else if gesture.didTapAttributedTextInLabel(label: lblTerms, inRange: privacyRange) {
       print("Tapped privacy") 
   } else {                
       print("Tapped none")
   }
}

4) Add UITapGestureRecognizerextension

4) 添加UITapGestureRecognizer扩展

extension UITapGestureRecognizer {

    func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
        // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
        let layoutManager = NSLayoutManager()
        let textContainer = NSTextContainer(size: CGSize.zero)
        let textStorage = NSTextStorage(attributedString: label.attributedText!)

        // Configure layoutManager and textStorage
        layoutManager.addTextContainer(textContainer)
        textStorage.addLayoutManager(layoutManager)

        // Configure textContainer
        textContainer.lineFragmentPadding = 0.0
        textContainer.lineBreakMode = label.lineBreakMode
        textContainer.maximumNumberOfLines = label.numberOfLines
        let labelSize = label.bounds.size
        textContainer.size = labelSize

        // Find the tapped character location and compare it to the specified range
        let locationOfTouchInLabel = self.location(in: label)
        let textBoundingBox = layoutManager.usedRect(for: textContainer)
        //let textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
                                              //(labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
        let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)

        //let locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
                                                        // locationOfTouchInLabel.y - textContainerOffset.y);
        let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x, y: locationOfTouchInLabel.y - textContainerOffset.y)
        let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
        return NSLocationInRange(indexOfCharacter, targetRange)
    }

}

Good luck! :-)

祝你好运!:-)

回答by Beninho85

After having several issues with this kind of stuff, using a lot of different librairies, etc... I found an interesting solution: http://samwize.com/2016/03/04/how-to-create-multiple-tappable-links-in-a-uilabel/

在遇到这类东西的几个问题后,使用了很多不同的库等......我找到了一个有趣的解决方案:http://samwize.com/2016/03/04/how-to-create-multiple-tappable - uilabel 中的链接/

It's about to extend UITapGestureRegonizer and detect if the tap is in the range of the string when triggered.

即将扩展 UITapGestureRegonizer 并检测触发时点击是否在字符串范围内。

Here is the updated Swift 4 version of this extension:

这是此扩展的更新 Swift 4 版本:

extension UITapGestureRecognizer {

    func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
        // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
        let layoutManager = NSLayoutManager()
        let textContainer = NSTextContainer(size: CGSize.zero)
        let textStorage = NSTextStorage(attributedString: label.attributedText!)

        // Configure layoutManager and textStorage
        layoutManager.addTextContainer(textContainer)
        textStorage.addLayoutManager(layoutManager)

        // Configure textContainer
        textContainer.lineFragmentPadding = 0.0
        textContainer.lineBreakMode = label.lineBreakMode
        textContainer.maximumNumberOfLines = label.numberOfLines
        let labelSize = label.bounds.size
        textContainer.size = labelSize

        // Find the tapped character location and compare it to the specified range
        let locationOfTouchInLabel = self.location(in: label)
        let textBoundingBox = layoutManager.usedRect(for: textContainer)

        let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x, y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y)

        let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x, y: locationOfTouchInLabel.y - textContainerOffset.y)
        let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
        return NSLocationInRange(indexOfCharacter, targetRange)
    }

}

To simplify range conversion, you also need this Range extension

为了简化范围转换,您还需要此范围扩展

extension Range where Bound == String.Index {
    var nsRange:NSRange {
        return NSRange(location: self.lowerBound.encodedOffset,
                   length: self.upperBound.encodedOffset -
                    self.lowerBound.encodedOffset)
    }
}

Once you have this extension, you can add a tap gesture to your label:

一旦你有了这个扩展,你就可以在你的标签上添加一个点击手势:

let tap = UITapGestureRecognizer(target: self, action: #selector(tapLabel(tap:)))
self.yourLabel.addGestureRecognizer(tap)
self.yourLabel.isUserInteractionEnabled = true

Here is the function to handle the tap:

这是处理水龙头的函数:

@objc func tapLabel(tap: UITapGestureRecognizer) {
    guard let range = self.yourLabel.text?.range(of: "Substring to detect")?.nsRange else {
        return
    }
    if tap.didTapAttributedTextInLabel(label: self.yourLabel, inRange: range) {
        // Substring tapped
    }
}

回答by DoesData

For multi-line labels you have to set the textStorage font or the incorrect range will be returned

对于多行标签,您必须设置 textStorage 字体,否则将返回不正确的范围

guard let attributedString = self.attributedText else { return }

let mutableAttribString = NSMutableAttributedString(attributedString: attributedString)
mutableAttribString.addAttributes([NSAttributedString.Key.font: myFont], range: NSRange(location: 0, length: attributedString.length))

let textStorage = NSTextStorage(attributedString: mutableAttribString)

There are a lot of answers to this question. However, there are many people complaining that the tap fails for multi-line labels and that is correct for most answers on this page. The incorrect range for the tap is returned because the textStoragedoesn't have the correct font.

这个问题有很多答案。但是,有很多人抱怨多行标签的点击失败,这对于此页面上的大多数答案都是正确的。因为textStorage没有正确的字体,所以返回了不正确的点击范围。

let textStorage = NSTextStorage(attributedString: label.attributedText!)

You can fix this quickly by adding the correct font to your textStorageinstance:

您可以通过向textStorage实例添加正确的字体来快速解决此问题:

guard let attributedString = self.attributedText else { return -1 }

let mutableAttribString = NSMutableAttributedString(attributedString: attributedString)
mutableAttribString.addAttributes([NSAttributedString.Key.font: myFont], range: NSRange(location: 0, length: attributedString.length))

let textStorage = NSTextStorage(attributedString: mutableAttribString)

Putting it all together you get something like this:

把它们放在一起,你会得到这样的东西:

protocol AtMentionsLabelTapDelegate: class {
  func labelWasTappedForUsername(_ username: String)
}

class AtMentionsLabel: UILabel {
  private var tapGesture: UITapGestureRecognizer = UITapGestureRecognizer()
  weak var tapDelegate: AtMentionsLabelTapDelegate?

  var mentions: [String] = [] // usernames to style

  override init(frame: CGRect) {
    super.init(frame: frame)
    commonInit()
  }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    commonInit()
  }

  func commonInit() {
    isUserInteractionEnabled = true

    lineBreakMode = .byWordWrapping
    tapGesture = UITapGestureRecognizer()
    tapGesture.addTarget(self, action: #selector(handleLabelTap(recognizer:)))
    tapGesture.numberOfTapsRequired = 1
    tapGesture.isEnabled = true
    addGestureRecognizer(tapGesture)
  }


  @objc func handleLabelTap(recognizer: UITapGestureRecognizer) {
    let tapLocation = recognizer.location(in: self)
    let tapIndex = indexOfAttributedTextCharacterAtPoint(point: tapLocation)

    for username in mentions {
      if let ranges = self.attributedText?.rangesOf(subString: username) {
        for range in ranges {
          if tapIndex > range.location && tapIndex < range.location + range.length {
            tapDelegate?.labelWasTappedForUsername(username)
            return
          }
        }
      }
    }
  }

  func indexOfAttributedTextCharacterAtPoint(point: CGPoint) -> Int {
    guard let attributedString = self.attributedText else { return -1 }

    let mutableAttribString = NSMutableAttributedString(attributedString: attributedString)
    // Add font so the correct range is returned for multi-line labels
    mutableAttribString.addAttributes([NSAttributedString.Key.font: font], range: NSRange(location: 0, length: attributedString.length))

    let textStorage = NSTextStorage(attributedString: mutableAttribString)

    let layoutManager = NSLayoutManager()
    textStorage.addLayoutManager(layoutManager)

    let textContainer = NSTextContainer(size: frame.size)
    textContainer.lineFragmentPadding = 0
    textContainer.maximumNumberOfLines = numberOfLines
    textContainer.lineBreakMode = lineBreakMode
    layoutManager.addTextContainer(textContainer)

    let index = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
    return index
  }
}

extension NSAttributedString {
  func rangesOf(subString: String) -> [NSRange] {
    var nsRanges: [NSRange] = []
    let ranges = string.ranges(of: subString, options: .caseInsensitive, locale: nil)

    for range in ranges {
      nsRanges.append(range.nsRange)
    }

    return nsRanges
  }
}

extension String {
  func ranges(of substring: String, options: CompareOptions = [], locale: Locale? = nil) -> [Range<Index>] {
    var ranges: [Range<Index>] = []
    while let range = self.range(of: substring, options: options, range: (ranges.last?.upperBound ?? self.startIndex) ..< self.endIndex, locale: locale) {
      ranges.append(range)
    }
    return ranges
  }
}

回答by Naloiko Eugene

Swift 3. I've developed an extension:

Swift 3. 我开发了一个扩展:

 extension UILabel {
        ///Find the index of character (in the attributedText) at point
        func indexOfAttributedTextCharacterAtPoint(point: CGPoint) -> Int {
            assert(self.attributedText != nil, "This method is developed for attributed string")
            let textStorage = NSTextStorage(attributedString: self.attributedText!)
            let layoutManager = NSLayoutManager()
            textStorage.addLayoutManager(layoutManager)
            let textContainer = NSTextContainer(size: self.frame.size)
            textContainer.lineFragmentPadding = 0
            textContainer.maximumNumberOfLines = self.numberOfLines
            textContainer.lineBreakMode = self.lineBreakMode
            layoutManager.addTextContainer(textContainer)

            let index = layoutManager.characterIndex(for: point, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
            return index
        } 
    }

And now I can check if the tapped character is in range:

现在我可以检查点击的字符是否在范围内:

        let range = SOME_RANGE
        let tapLocation = gesture.location(in: MY_TEXT_LABEL)
        let index = textLbl.indexOfAttributedTextCharacterAtPoint(point: tapLocation)

        if index > range.location && index < range.location + range.length {
         //YES, THE TAPPED CHARACTER IS IN RANGE
        }

回答by Lance Samaria

This is a real easy alternative for anyone who is willing to use a textView. I realize this question is about a UILabel but if you read the comments on some of the answers they don't work for some people and some of them are very code heavy which isn't very good for beginners. You can do this in 11 simple steps if your willing to swap out a UILabel for a UITextView.

对于任何愿意使用 textView 的人来说,这是一个非常简单的替代方案。我意识到这个问题是关于 UILabel 的,但是如果您阅读对某些答案的评论,它们对某些人不起作用,并且其中一些代码非常繁重,这对初学者来说不是很好。如果您愿意将 UILabel 换成 UITextView,则可以通过 11 个简单的步骤完成此操作。

You can use NSMutableAttributedStringand a UITextView. The UITextView has a delegate method func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {. Once you set the part of the string you want to make tappable the delegate method will activate it.

您可以使用NSMutableAttributedString和一个UITextView. UITextView 有一个委托方法func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {。一旦你设置了你想要点击的字符串部分,委托方法就会激活它。

The 11 steps are listed below in the comments above each piece of code.

下面在每段代码上方的注释中列出了 11 个步骤。

// 1st **BE SURE TO INCLUDE** UITextViewDelegate to the view controller's class
class VewController: UIViewController, UITextViewDelegate {

    // 2nd use a programmatic textView or use the textView from your storyboard
    let yourTextView: UITextView = {
        let textView = UITextView()
        textView.textAlignment = .center
        textView.isEditable = false
        textView.showsVerticalScrollIndicator = false
        return textView
    }()

   override func viewDidLoad() {
        super.viewDidLoad()

        // 3rd in viewDidLoad set the textView's delegate
        yourTextView.delegate = self

        // 4th create the first piece of the string you don't want to be tappable
        let regularText = NSMutableAttributedString(string: "any text ", attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 17), NSAttributedStringKey.foregroundColor: UIColor.black])

        // 5th create the second part of the string that you do want to be tappable. I used a blue color just so it can stand out.
        let tappableText = NSMutableAttributedString(string: "READ MORE")
        tappableText.addAttribute(NSAttributedString.Key.font, value: UIFont.systemFont(ofSize: 17), range: NSMakeRange(0, tappableText.length))
        tappableText.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.blue, range: NSMakeRange(0, tappableText.length))

        // 6th this ISN'T NECESSARY but this is how you add an underline to the tappable part. I also used a blue color so it can match the tappableText and used the value of 1 for the height. The length of the underline is based on the tappableText's length using NSMakeRange(0, tappableText.length)
        tappableText.addAttribute(NSAttributedString.Key.underlineStyle, value: 1, range: NSMakeRange(0, tappableText.length))
        tappableText.addAttribute(NSAttributedString.Key.underlineColor, value: UIColor.blue, range: NSMakeRange(0, tappableText.length))

        // 7th this is the important part that connects the tappable link to the delegate method in step 11
        // use NSAttributedString.Key.link and the value "makeMeTappable" to link the NSAttributedString.Key.link to the method. FYI "makeMeTappable" is a name I choose for clarity, you can use anything like "anythingYouCanThinkOf"
        tappableText.addAttribute(NSAttributedString.Key.link, value: "makeMeTappable", range: NSMakeRange(0, tappableText.length))

        // 8th *** important append the tappableText to the regularText ***
        regularText.append(tappableText)

        // 9th set the regularText to the textView's attributedText property
        yourTextView.attributedText = regularText 
   }

   // 10th add the textView's delegate method that activates urls. Make sure to return false for the tappable part
   func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {

        // 11th use the value from the 7th step to trigger the url inside this method
        if URL.absoluteString == "makeMeTappable"{

            // in this situation I'm using the tappableText to present a view controller but it can be used for whatever you trying to do
            let someVC = SomeController()
            let navVC = UINavigationController(rootViewController: someVC)
            present(navVC, animated: true, completion: nil)

            return false // return false for this to work
        }

        return true
    }
}

回答by matt

Your text kit stack is faulty. You forgot to add the text container to the layout manager! Therefore there is no text to lay out, and the layout manager cannot report any glyph rect. Therefore that glyph rect is NSRectZero, which is why you can never report a tap within it.

您的文本套件堆栈有问题。您忘记将文本容器添加到布局管理器!因此没有要布局的文本,并且布局管理器无法报告任何字形矩形。因此,字形矩形是 NSRectZero,这就是为什么您永远不能报告其中的点击。

Another problem is that you are calling characterRangeForGlyphRangewhen you should be calling glyphRangeForCharacterRange, and you don't seem to know how to use the result (in fact, you throw away the result).

另一个问题是,您characterRangeForGlyphRange在应该调用的时候调用了glyphRangeForCharacterRange,而您似乎不知道如何使用结果(实际上,您将结果扔掉了)。

Here is working code that shows just the part about using the text stack. I start with a string "Hello to you". I will show how to learn where the rect for "to" is:

这是工作代码,仅显示有关使用文本堆栈的部分。我从一个字符串“Hello to you”开始。我将展示如何学习“to”的矩形在哪里:

let s = "Hello to you"
let ts = NSTextStorage(
    attributedString: NSAttributedString(string:s))
let lm = NSLayoutManager()
ts.addLayoutManager(lm)
let tc = NSTextContainer(size: CGSizeMake(4000,400))
lm.addTextContainer(tc) // ****
tc.lineFragmentPadding = 0
let toRange = (s as NSString).rangeOfString("to")
let gr = lm.glyphRangeForCharacterRange(
    toRange, actualCharacterRange: nil) // ****
let glyphRect = lm.boundingRectForGlyphRange(
    gr, inTextContainer: tc)

The result is {x 30.68 y 0 w 10.008 h 13.8}. Now we can proceed to test whether a tap is in that rect. Go Ye And Do Likewise.

结果是{x 30.68 y 0 w 10.008 h 13.8}。现在我们可以继续测试该矩形中是否有一个水龙头。去吧,照样做。

回答by rahul

To enable multiline tappable & don't want to subclass the UILabel then:

要启用多行可点击 & 不想子类 UILabel 然后:

  • Write Extension function for UITapGestureRecognizer
  • 为 UITapGestureRecognizer 编写扩展函数
extension UITapGestureRecognizer {

   func didTapAttributedTextInLabel(label: UILabel, inRange targetRange: NSRange) -> Bool {
       guard let attributedText = label.attributedText else { return false }

       let mutableStr = NSMutableAttributedString.init(attributedString: attributedText)
       mutableStr.addAttributes([NSAttributedString.Key.font : label.font!], range: NSRange.init(location: 0, length: attributedText.length))

       // Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
       let layoutManager = NSLayoutManager()
       let textContainer = NSTextContainer(size: CGSize.zero)
       let textStorage = NSTextStorage(attributedString: mutableStr)

       // Configure layoutManager and textStorage
       layoutManager.addTextContainer(textContainer)
       textStorage.addLayoutManager(layoutManager)

       // Configure textContainer
       textContainer.lineFragmentPadding = 0.0
       textContainer.lineBreakMode = label.lineBreakMode
       textContainer.maximumNumberOfLines = label.numberOfLines
       let labelSize = label.bounds.size
       textContainer.size = labelSize

       // Find the tapped character location and compare it to the specified range
       let locationOfTouchInLabel = self.location(in: label)
       let textBoundingBox = layoutManager.usedRect(for: textContainer)
       let textContainerOffset = CGPoint(x: (labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
                                         y: (labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
       let locationOfTouchInTextContainer = CGPoint(x: locationOfTouchInLabel.x - textContainerOffset.x,
                                                    y: locationOfTouchInLabel.y - textContainerOffset.y);
       let indexOfCharacter = layoutManager.characterIndex(for: locationOfTouchInTextContainer, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
       return NSLocationInRange(indexOfCharacter, targetRange)
   }

}
  • Configure your UILable
  • 配置你的 UILable
label.text = "For any type of query please call us on +9186XXX-XXXXX or mail us at [email protected]"
label.isUserInteractionEnabled = true
label.lineBreakMode = .byWordWrapping
let tapGesture = UITapGestureRecognizer.init(target: self, action: #selector(tappedOnLabel(_:)))
tapGesture.numberOfTouchesRequired = 1
label.addGestureRecognizer(tapGesture)
  • Add the gesture recogniser selector function:
  • 添加手势识别器选择器功能:
@objc func tappedOnLabel(_ gesture: UITapGestureRecognizer) {
    guard let text = label.text else { return }
    let numberRange = (text as NSString).range(of: "+9186XXX-XXXXX")
    let emailRange = (text as NSString).range(of: "[email protected]")    
    if gesture.didTapAttributedTextInLabel(label: self.label, inRange: numberRange) {
        print("number tapped")
    } else if gesture.didTapAttributedTextInLabel(label: self.label, inRange: emailRange) {
        print("Email tapped")
    }
}