Create tap-able “links” in the NSAttributedString of a UILabel?

前端 未结 30 3106
半阙折子戏
半阙折子戏 2020-11-22 02:07

I have been searching this for hours but I\'ve failed. I probably don\'t even know what I should be looking for.

Many applications have text and in this text are web

30条回答
  •  眼角桃花
    2020-11-22 03:06

    I am extending @NAlexN original detailed solution, with @zekel excellent extension of UITapGestureRecognizer, and providing in Swift.

    Extending UITapGestureRecognizer

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

    Usage

    Setup UIGestureRecognizer to send actions to tapLabel:, and you can detect if the target ranges is being tapped on in myLabel.

    @IBAction func tapLabel(gesture: UITapGestureRecognizer) {
        if gesture.didTapAttributedTextInLabel(myLabel, inRange: targetRange1) {
            print("Tapped targetRange1")
        } else if gesture.didTapAttributedTextInLabel(myLabel, inRange: targetRange2) {
            print("Tapped targetRange2")
        } else {
            print("Tapped none")
        }
    }
    

    IMPORTANT: The UILabel line break mode must be set to wrap by word/char. Somehow, NSTextContainer will assume that the text is single line only if the line break mode is otherwise.

提交回复
热议问题