Reputation: 83
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?
Upvotes: 6
Views: 1895
Reputation: 209
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.
Upvotes: 1
Reputation: 1570
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.
Upvotes: 4