Skip to content

Instantly share code, notes, and snippets.

@hamdan
Last active November 15, 2023 21:26
Show Gist options
  • Star 24 You must be signed in to star a gist
  • Fork 7 You must be signed in to fork a gist
  • Save hamdan/e8c98db7bcdcf4cdaa2d41be248823ec to your computer and use it in GitHub Desktop.
Save hamdan/e8c98db7bcdcf4cdaa2d41be248823ec to your computer and use it in GitHub Desktop.
Create Multiple Tappable Links in a UILabel
extension UITapGestureRecognizer {
func didTapAttributedTextInLabel(label: UILabel, targetText: String) -> Bool {
guard let attributedString = label.attributedText, let lblText = label.text else { return false }
let targetRange = (lblText as NSString).range(of: targetText)
//IMPORTANT label correct font for NSTextStorage needed
let mutableAttribString = NSMutableAttributedString(attributedString: attributedString)
mutableAttribString.addAttributes(
[NSAttributedString.Key.font: label.font ?? UIFont.smallSystemFontSize],
range: NSRange(location: 0, length: attributedString.length)
)
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize.zero)
let textStorage = NSTextStorage(attributedString: mutableAttribString)
// 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)
}
}
//For example adding uilabel then setup
let lblPrivacyTerm = UILabel()
func setupMultipleTapLabel() {
lblPrivacyTerm.text = "By signing up you agree to our Terms of service and Privacy policy ending"
let text = (lblPrivacyTerm.text)!
let underlineAttriString = NSMutableAttributedString(string: text)
let termsRange = (text as NSString).range(of: "Terms of service")
let privacyRange = (text as NSString).range(of: "Privacy policy")
underlineAttriString.addAttribute(.foregroundColor, value: UIColor.blue, range: termsRange)
underlineAttriString.addAttribute(NSAttributedString.Key.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: privacyRange)
lblPrivacyTerm.attributedText = underlineAttriString
let tapAction = UITapGestureRecognizer(target: self, action: #selector(self.tapLabel(gesture:)))
lblPrivacyTerm.isUserInteractionEnabled = true
lblPrivacyTerm.addGestureRecognizer(tapAction)
}
@IBAction func tapLabel(gesture: UITapGestureRecognizer) {
if gesture.didTapAttributedTextInLabel(label: lblPrivacyTerm, targetText: "Terms of service") {
print("Terms of service")
} else if gesture.didTapAttributedTextInLabel(label: lblPrivacyTerm, targetText: "Privacy policy") {
print("Privacy policy")
} else {
print("Tapped none")
}
}
@vinamelody
Copy link

For some unknown reason, if the label has a center alignment, the locationOfTouchInLabel messed up

@mrussu
Copy link

mrussu commented Apr 23, 2019

switch label.textAlignment {
case .left, .natural, .justified:
alignmentOffset = 0.0
case .center:
alignmentOffset = 0.5
case .right:
alignmentOffset = 1.0
}
let bounds = label.bounds
let xOffset = ((bounds.size.width - textBoundingBox.size.width) * alignmentOffset) - textBoundingBox.origin.x
let yOffset = ((bounds.size.height - textBoundingBox.size.height) * alignmentOffset) - textBoundingBox.origin.y
let textContainerOffset = CGPoint(x: locationOfTouchInLabel.x - xOffset, y: locationOfTouchInLabel.y - yOffset)

@hien352911
Copy link

it is incorrectly

@pawanline
Copy link

Its not working when text goes into multiline .It takes click to last second word.

@gustaveracosta
Copy link

Does not work with multiple links multiple lines unfortunately

@hamdan
Copy link
Author

hamdan commented Mar 13, 2020

unfortunately, its stop working in swift 4 and above

@sumitGupta10993
Copy link

not working it detect before it

@amir08
Copy link

amir08 commented Apr 16, 2020

Any update on multiple lines?

@hamdan
Copy link
Author

hamdan commented Apr 16, 2020

Any update on multiple lines?

Its working check revisions 5

@cmdr-surender-singh
Copy link

its not working

@akrant06
Copy link

akrant06 commented Nov 16, 2021

did any one find solution of multiline label? I tried latest version but somehow it did not work on swift 5.

@akrant06
Copy link

Setting

textContainer.lineBreakMode = .byCharWrapping

worked for me.

@akrant06
Copy link

its not working

Setting

textContainer.lineBreakMode = .byCharWrapping

worked for me.

@tientm-2843
Copy link

this code only work for charWrapping or wordWraping line break mode of Label. In default, line break mode of label is truncate tail so the result when label have multi line went wrong

@AlibekYusupov
Copy link

have this code in obj-c ?

@arthtilva
Copy link

Not working for arabic text. any solution ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment