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
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/
It's about to extend UITapGestureRegonizer and detect if the tap is in the range of the string when triggered.
Here is the updated Swift 4 version of this extension:
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
}
}