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
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:
Any help would be greatly appreciated.
Please find the solution here for getting specific text action
of Label
.
1) Label declaration
@IBOutlet weak var lblTerms: UILabel!
2) Set attributed text to the label
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
@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 UITapGestureRecognizer
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 = 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! :-)