Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Tap on a part of text of UILabel

I have a problem that boundingRectForGlyphRange always returns CGRect.zero "0.0, 0.0, 0.0, 0.0".

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")         }     } } 
like image 987
Ashley Avatar asked Mar 16 '16 17:03

Ashley


People also ask

How do I make a UILabel clickable?

To make UILabel clickable you will need to enable user interaction for it. To enable user interaction for UILabel simply set the isUserInteractionEnabled property to true.

How do I add tap gestures in swift 5?

Adding a Tap Gesture Recognizer in Interface Builder You don't need to switch between the code editor and Interface Builder. Open Main. storyboard and drag a tap gesture recognizer from the Object Library and drop it onto the view we added earlier. The tap gesture recognizer appears in the Document Outline on the left.


2 Answers

#swift 4.2 Please find the solution here for getting specific text action of Label.

enter image description here

  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.

  1. Add the tapLabel 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") } } 
  2. 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)     } } 

Make sure to do:

lblTerms.isUserInteractionEnabled = true 
like image 98
Ilesh P Avatar answered Sep 27 '22 16:09

Ilesh P


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     } } 
like image 28
Beninho85 Avatar answered Sep 27 '22 16:09

Beninho85