Swift:点击UILabel的一部分文本

我有一个问题,“boundingRectForGlyphRange”总是返回CGRect.zero“0.0,0.0,0.0,0.0”。 “boundingRectForGlyphRange”不起作用。 例如,我正在编写触摸UILabelfunction的一部分文本。 我的文本有第一部分是“任何文本”,第二部分是“读更多”。 当我点击“阅读更多”时,我只需要点击识别器就可以工作。 如果我触及UILabel的任何一点,“CGRectContainsPoint”总是返回true,然后调用action

在这里我的代码:

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

这只是一个演示来testing我想要做什么:

在这里输入图像说明

任何帮助将不胜感激。

您的文本工具包堆栈有问题。 您忘了将文本容器添加到布局pipe理器! 因此没有文字布局,布局pipe理器不能报告任何字形矩形。 因此,该字形rect是NSRectZero,这就是为什么你永远不能报告其中的一个水龙头。

另一个问题是,当你应该调用glyphRangeForCharacterRange时,你正在调用characterRangeForGlyphRange ,而你似乎不知道如何使用结果(事实上,你扔掉了结果)。

这是工作代码,只显示有关使用文本堆栈的部分。 我从string“Hello to you”开始。 我将展示如何学习“to”的rect在哪里:

 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) 

结果是{x 30.68 y 0 w 10.008 h 13.8} 。 现在我们可以继续testing是否有一个水龙头。 去吧,做同样的事情。

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

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

  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 } 

在与这种东西有几个问题后,使用了很多不同的馆藏等…我发现了一个有趣的解决scheme: http ://samwize.com/2016/03/04/how-to-create-multiple-tappable -links-IN-A-的UILabel /

它将扩展UITapGestureRegonizer,并检测触发时是否在string范围内。

这里是这个扩展的更新的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) } } 

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

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

一旦你有了这个扩展,你可以添加一个轻击手势到你的标签:

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

这是处理水龙头的function:

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