Recognise an image tap in attributed String displayed in a label
Asked Answered
V

2

6

I have an attributed string as below to be displayed in a label.

let someText = NSMutableAttributedString(string: "This is a sample text with")
let imageAttachment = NSTextAttachment()
imageAttachment.image = UIImage(named: "icon") 

let imageString = NSAttributedString(attachment: imageAttachment)
someText.append(imageString)
someText.append(NSAttributedString(string: "attached")
somelabel.attributedText = someText

The label displays This is a sample text with 'image' attached

How to recognize a tap on the image(not on the text) to perform an action?

Veal answered 28/1, 2020 at 5:57 Comment(0)
T
4
  • create a new NSAttributedStringKey that you'll use to identify the image attachment.
  • Then create an NSTextAttachment with the image, wrap it in a NSMutableAttributedString and add the custom attribute to it.
  • Finally add the wrapper to the full NSAttributedString and attach a UITapGestureRecognizer.

  • Then when in the selector on the UITapGestureRecognizer simply look for that custom tag.

Code for most bit :

extension NSAttributedStringKey {
static let imagePath = NSAttributedStringKey(rawValue: "imagePath")
}

when to setup the text display

let fullString = NSMutableAttributedString()    
let imageAttachment = NSTextAttachment()
imageAttachment.image = image

let imageAttributedString: NSMutableAttributedString = NSAttributedString(attachment: imageAttachment).mutableCopy() as! NSMutableAttributedString

let customAttribute = [ NSAttributedStringKey.imagePath: imagePath ]
imageAttributedString.addAttributes(customAttribute, range: NSRange(location: 0, length: imageAttributedString.length))

fullString.append(imageAttributedString)

then in the function called by the tap action:

    @objc func onImageTap(_ sender: UITapGestureRecognizer) {
      let textView = sender.view as! UITextView
      let layoutManager = textView.layoutManager

      // location of tap in textView coordinates
      var location = sender.location(in: textView)
      location.x -= textView.textContainerInset.left;
      location.y -= textView.textContainerInset.top;

      // character index at tap location
      let characterIndex = layoutManager.characterIndex(for: location, in: textView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)

      // if index is valid 
      if characterIndex < textView.textStorage.length {

        // check if the tap location has the custom attribute
        let attributeValue = textView.attributedText.attribute(NSAttributedStringKey.imagePath, at: characterIndex, effectiveRange: nil) as? String
        if let value = attributeValue {
            print("You tapped on \(NSAttributedStringKey.imagePath) and the value is: \(value)")
        }

    }

}

From there you know the tap was in the image and you have the coordinates inside the image frame, so you can use that combination to figure out where in the image was tapped.

Terrapin answered 28/1, 2020 at 6:16 Comment(3)
it works fine in a case of text view but not in a label...tap is not getting detected exactly where attachment isZymotic
@jayantrawat were you able to fix the code for label?Boucicault
Any one fixed for label?Pressmark
S
0

You can implement image tap functionality in an attributed string within a UILabel using the following below code.

Step 1 : The following function is used to convert a string into an attributed string, applying various properties such as color change, font adjustments, and the addition of images. It returns the resulting attributed string.

func createAttributedStringWithImage(message: String, subText1: String) -> NSAttributedString {

    let mainAttributes: [NSAttributedString.Key: Any] = [
        .foregroundColor: Constants.primaryTextColor ?? UIColor.black,   // Change color as needed
        .font: UIFont(name: "Poppins-Regular", size: 14.0) ?? UIFont.boldSystemFont(ofSize: 14)  // Change font as needed
    ]

    let subTextAttributes: [NSAttributedString.Key: Any] = [
        .foregroundColor: Constants.primaryTextColor ?? UIColor.black,    // Change color as needed
        .font: UIFont(name: "Poppins-Bold", size: 14.0) ?? UIFont.boldSystemFont(ofSize: 14)  // Change font as needed
    ]

    let attributedString = NSMutableAttributedString(string: message, attributes: mainAttributes)
    let spaceString = NSMutableAttributedString(string: " ", attributes: mainAttributes)
    
    attributedString.append(NSAttributedString(string: subText1))

    var combinedText = message + subText1 + " "
    
    if let range = combinedText.range(of: subText1) {
        let nsRange = NSRange(range, in: combinedText)
        attributedString.addAttributes(subTextAttributes, range: nsRange)
    }
    
    // Add an image attachment after subText1
    let imageAttachment = NSTextAttachment()
    imageAttachment.image = UIImage(named: "ic_edit") // Replace with the name of your image
    
    let imageAttributedString = NSAttributedString(attachment: imageAttachment)
    // Line to add space before image
    attributedString.append(spaceString)
    
    attributedString.append(imageAttributedString)
    
    return attributedString
}

Step 2: Now, we need to assign the attributed string to a UILabel and add a tap gesture recognizer to the UILabel. Use the following code to achieve this:

 let message = "We have sent the code verification to your email "
 let email = "[email protected]"
 let resultAttributedString = createAttributedStringWithImage(message: message, subText1: email)
 self.messageLabel?.attributedText = resultAttributedString
 self.messageLabel?.isUserInteractionEnabled = true
        
 let tapGesture = UITapGestureRecognizer.init(target: self, action: #selector(imageTapped(gesture:)))
 self.messageLabel?.addGestureRecognizer(tapGesture)

Step 3: When a user taps on the UILabel after adding the tap gesture, the following function will be called. Below is the implementation of the imageTapped function where we write code to determine if the user has tapped on an image or text.

 @objc private func imageTapped(gesture: UITapGestureRecognizer) {
    guard let label = gesture.view as? UILabel else {
        return
    }
    
    guard let attributedText = label.attributedText else {
        return // nothing to do
    }
    
    let location = gesture.location(in: label)
    let textStorage = NSTextStorage(attributedString: attributedText)
    let textContainer = NSTextContainer(size: label.bounds.size)
    let layoutManager = NSLayoutManager()
    layoutManager.addTextContainer(textContainer)
    textStorage.addLayoutManager(layoutManager)
    
    textContainer.lineFragmentPadding = 0.0
    textContainer.lineBreakMode = label.lineBreakMode
    textContainer.maximumNumberOfLines = label.numberOfLines
    
    let characterIndex = layoutManager.characterIndex(for: location,in:textContainer,fractionOfDistanceBetweenInsertionPoints: nil)
    
    if characterIndex < textStorage.length {
       
        guard NSTextAttachment.character == (textStorage.string as NSString).character(at: characterIndex) else {
            return
        }
        guard let attachment = textStorage.attribute(.attachment, at: characterIndex, effectiveRange: nil) as? NSTextAttachment else {
            return
        }
        
        if (attachment.image != nil){
            // You have clicked on image and write your code here you want to achieve on image tapped.
        }
        
    }
    
}

Final Output: Below are the output of the above-mentioned steps, and your label will look like this.

enter image description here

Sterilization answered 3/1 at 9:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.