Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
391 views
in Technique[技术] by (71.8m points)

ios - Swift : 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". "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:

enter image description here

Any help would be greatly appreciated.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

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

Good luck! :-)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...