Swift : tap on a part of text of UILabel

前端 未结 7 879
北海茫月
北海茫月 2020-12-02 21:36

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

相关标签:
7条回答
  • 2020-12-02 22:26

    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
        }
    }
    
    0 讨论(0)
提交回复
热议问题