I am trying to extract the word that was tapped in a UILabel using tap gesture recognizer. I am able to get the character that was tapped, as well as the range of the character, but I am not able to extract the full word. Is there a way to get the full string of the word that was tapped instead of the character?
if let label = descriptionLabel, tapGestureRecognizer != nil {
if label.attributedText == nil {
return
}
let storage = NSTextStorage(attributedString: label.attributedText ?? NSAttributedString())
let textContainer = NSTextContainer(size: label.bounds.size)
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainer)
storage.addLayoutManager(layoutManager)
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = (label.lineBreakMode)
textContainer.maximumNumberOfLines = label.numberOfLines
let location: CGPoint = tapGestureRecognizer!.location(in: label)
let characterIndex: Int = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
if characterIndex < storage.length {
print("Character Index: \(Int(characterIndex))")
let range = NSRange(location: characterIndex, length: 1)
let substring: String? = (label.attributedText?.string as NSString?)?.substring(with: range)
//prints out one character, how do I get the full word of this character
print(substring)
}
}
Much appreciated
I don't like this way, but it works. Place the following after the print(substring) statement. Why doesn't UIKit have a method to extract the full string at a Range? Ridiculous.
let afterWords: [Any] = (afterString.components(separatedBy: " "))
let beforeWords: [Any] = (beforeString.components(separatedBy: ", "))
var attachmentString = ""
if let stringBeforeLocation = beforeWords.last as? String, let stringAfterLocation = afterWords.first as? String {
attachmentString = stringBeforeLocation
if stringAfterLocation.count > 0 {
let choppedString = (stringAfterLocation as NSString).substring(from: 1)
attachmentString = attachmentString + choppedString
print("FULL STRING TAPPED \(attachmentString)")
}
}
Related
I want to remove the bottom padding of a UILabel with attributedText inside a UIStackview.
I found this solution How to remove the extra padding below an one line UILabel. This works with normal text but not with attributed text.
let textLabel = UILabel()
textLabel.translatesAutoresizingMaskIntoConstraints = false
textLabel.text = "What is a chemical property and how can you observe it?"
textLabel.numberOfLines = 0
textLabel.lineBreakMode = .byWordWrapping
textLabel.backgroundColor = .lightGray
mainStackView.addArrangedSubview(textLabel)
let textLabel2 = UILabel()
textLabel2.translatesAutoresizingMaskIntoConstraints = false
let html = "<html lang=\"en\"><head><meta charset=\"UTF-8\"></head><body><div style=\"font-size:36;\"><p>What is a <em>chemical property</em> and how can you observe it?</p></div></body></html>"
let data = Data(html.utf8)
if let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
let a = NSMutableAttributedString.init(attributedString: attributedString)
let range = (a.string as NSString).range(of: a.string)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .left
paragraphStyle.firstLineHeadIndent = 0.0
let attributes: [NSAttributedString.Key: Any] = [
.foregroundColor: UIColor.black,
.paragraphStyle: paragraphStyle
]
a.addAttributes(attributes, range: range)
textLabel2.attributedText = a
}
textLabel2.numberOfLines = 0
textLabel2.lineBreakMode = .byWordWrapping
textLabel2.backgroundColor = .yellow
mainStackView.addArrangedSubview(textLabel2)
let textLabel3 = UILabel()
textLabel3.translatesAutoresizingMaskIntoConstraints = false
textLabel3.text = "What is a chemical property and how can you observe it?"
textLabel3.numberOfLines = 0
textLabel3.lineBreakMode = .byWordWrapping
textLabel3.backgroundColor = .lightGray
mainStackView.addArrangedSubview(textLabel3)
A working sample project with this code can be found here: https://github.com/Quobject/testUIlabelInStackviewpadding
The "bottom spacing" is not "spacing" ... your converted <p>...</p> html block adds a newline character at the end of the text.
You can use this extension (found here):
extension NSMutableAttributedString {
func trimmedAttributedString() -> NSAttributedString {
let invertedSet = CharacterSet.whitespacesAndNewlines.inverted
let startRange = string.rangeOfCharacter(from: invertedSet)
let endRange = string.rangeOfCharacter(from: invertedSet, options: .backwards)
guard let startLocation = startRange?.upperBound, let endLocation = endRange?.lowerBound else {
return NSAttributedString(string: string)
}
let location = string.distance(from: string.startIndex, to: startLocation) - 1
let length = string.distance(from: startLocation, to: endLocation) + 2
let range = NSRange(location: location, length: length)
return attributedSubstring(from: range)
}
}
and change this line:
textLabel2.attributedText = a
to:
textLabel2.attributedText = a.trimmedAttributedString()
Result (applying that change to your GitHub repo):
I'm using a UITextView which contains multiple colors. I'm using NSMutableAttributedString for this. I'm fetching the words by tapping on the word inside the TextView in console. Now I want to fetch the color of the attributed string along with the word. I've found no resource in Swift. Everything I found was in Objective-C. Here is my UITapGestureRecognizer handler function.
#objc func myMethodToHandleTap(sender: UITapGestureRecognizer) {
let textView = sender.view as! UITextView
let location: CGPoint = sender.location(in: textView)
let position: CGPoint = CGPoint(x: location.x, y: location.y)
let tapPosition: UITextPosition? = textView.closestPosition(to: position)
if tapPosition != nil {
let textRange: UITextRange? = textView.tokenizer.rangeEnclosingPosition(tapPosition!, with: UITextGranularity.word, inDirection: UITextDirection(rawValue: 1))
if textRange != nil && textColor == UIColor.red
{
let tappedWord: String? = textView.text(in: textRange!)
print("Color : RED , tapped word : ", tappedWord!)
}
else if textRange != nil && textColor == UIColor.green
{
let tappedWord: String? = textView.text(in: textRange!)
print("Color : GREEN , tapped word : ", tappedWord!)
}
else {
print("empty space")
}
}
}
If you adding word color programmatic than check tap word range and match with you applied range then you can easily find the color of the tapped word.
textColor where you assign a value before compare?
This will help you
Thank you.
You can get the color this way:
if
let tapPosition = tapPosition,
let characterIndex = textView.offset(from: textView.beginningOfDocument, to: tapPosition)
let textColor = textView.attributedText.attribute(.foregroundColor, at: characterIndex, effectiveRange: nil) as? UIColor
{
//Do what you want with text color
}
I have following string "#[Hema](hema_ramburuth), #[Ilesh P](ilesh.panchal), #[Lewis Murphy](lewis) how are you?". I want to display this screen like this "Hema, Ilesh P, Lewis Murphy how are you?" also I want to identify the screen for the click event.
I have used the ActiveLabel repo for the click.
Hey I have had encountered a similar requirement. So this is how I have handled.
I have created an extension for String
extension String {
/// Returns range of text in the string
func getRange(OfText text: String) -> NSRange {
let nsRepresentation = self as NSString
return nsRepresentation.range(of: text)
}
}
In your View Controller,
var tapPrivacyGesture = UITapGestureRecognizer()
#IBOutlet weak var yourLabel: UILabel!
var displayText = String()
func matchesForRegexInText(regex: String, text: String, firstBracket: String, lastBracket: String) -> [String] {
do {
let regex = try NSRegularExpression(pattern: regex, options: [])
let nsString = text as NSString
let results = regex.matches(
in: text,
options: [],
range: NSRange(location: 0, length: nsString.length))
return results.map { nsString.substring(with: $0.range) }.map { $0.replacingOccurrences(of: firstBracket, with: "") }.map { $0.replacingOccurrences(of: lastBracket, with: "") }
} catch let error as NSError {
print("invalid regex: \(error.localizedDescription)")
return []
}
}
var givenString = "#[Hema](hema_ramburuth), #[Ilesh P](ilesh.panchal), #[Lewis Murphy](lewis) how are you?"
let nameStrings = matchesForRegexInText(regex: "\\[(.*?)\\]", text: givenString, firstBracket: "[", lastBracket: "]")
let removeForUIStrings = matchesForRegexInText(regex: "\\((.*?)\\)", text: givenString, firstBracket: "(", lastBracket: ")")
removeForUIStrings.forEach {
givenString = givenString.replacingOccurrences(of: "(\($0))", with: "")
}
nameStrings.forEach {
givenString = givenString.replacingOccurrences(of: "[\($0)]", with: $0)
}
givenString = givenString.replacingOccurrences(of: "#", with: "")
print(givenString)
displayText = givenString
tapPrivacyGesture.addTarget(self, action: #selector(self.handlePolicyTap(tap:)))
yourLabel.addGestureRecognizer(tapPrivacyGesture)
yourLabel.isUserInteractionEnabled = true
func handlePolicyTap(tap: UITapGestureRecognizer) {
let storage = NSTextStorage(attributedString: yourLabel.attributedText ?? NSAttributedString())
let layoutManager = NSLayoutManager()
storage.addLayoutManager(layoutManager)
let textContainer = NSTextContainer(size: CGSize(width: yourLabel.frame.size.width, height: yourLabel.frame.size.height+100))
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = (yourLabel.lineBreakMode)
textContainer.maximumNumberOfLines = yourLabel.numberOfLines
layoutManager.addTextContainer(textContainer)
let location: CGPoint = tap.location(in: yourLabel)
let characterIndex: Int = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
guard
characterIndex < storage.length,
let question = currentQuestion else {
return
}
nameStrings.forEach {
let range = displayText.getRange(OfText: $0)
if range.contains(characterIndex) {
/// Perform actions on click of this string
}
}
}
As from your question, just hard-code parsing done below.
let fullString = "#[Hema](hema_ramburuth), #[Ilesh P](ilesh.panchal), #[Lewis Murphy](lewis) how are you?"
let allarray = fullString.split(separator: ",")
let messageArray = allarray.last
let message = messageArray?.split(separator: ")")
let correctMessage = message?.last
var allNames : String = ""
for namesString in allarray {
if allNames.count > 0 {
allNames += ", "
}
let name = String(namesString)
allNames += name.slice(from: "#[", to: "]") ?? ""
}
if allNames.count > 0 {
allNames += correctMessage ?? ""
}
print("Name and Message --- > \(allNames)")
Slicing string using String extension
extension String {
func slice(from: String, to: String) -> String? {
return (range(of: from)?.upperBound).flatMap { substringFrom in
(range(of: to, range: substringFrom..<endIndex)?.lowerBound).map { substringTo in
substring(with: substringFrom..<substringTo)
}
}
}
}
I've printed output as below:
Name and Message --- > Hema, Ilesh P, Lewis Murphy how are you?
I want to get ride of the white spaces in front and at the end of my NSAttributedString(Trimming it). I can't simply convert it to string and do trimming because there are images(attachments) in it.
How can i do it?
Create extension of NSAttributedString as below.
extension NSAttributedString {
public func attributedStringByTrimmingCharacterSet(charSet: CharacterSet) -> NSAttributedString {
let modifiedString = NSMutableAttributedString(attributedString: self)
modifiedString.trimCharactersInSet(charSet: charSet)
return NSAttributedString(attributedString: modifiedString)
}
}
extension NSMutableAttributedString {
public func trimCharactersInSet(charSet: CharacterSet) {
var range = (string as NSString).rangeOfCharacter(from: charSet as CharacterSet)
// Trim leading characters from character set.
while range.length != 0 && range.location == 0 {
replaceCharacters(in: range, with: "")
range = (string as NSString).rangeOfCharacter(from: charSet)
}
// Trim trailing characters from character set.
range = (string as NSString).rangeOfCharacter(from: charSet, options: .backwards)
while range.length != 0 && NSMaxRange(range) == length {
replaceCharacters(in: range, with: "")
range = (string as NSString).rangeOfCharacter(from: charSet, options: .backwards)
}
}
}
and use in viewController where you want to use. like this
let attstring = NSAttributedString(string: "this is test message. Please wait. ")
let result = attstring.attributedStringByTrimmingCharacterSet(NSCharacterSet.whitespaceAndNewlineCharacterSet())
This works even with emoji in the text
extension NSAttributedString {
/** Will Trim space and new line from start and end of the text */
public func trimWhiteSpace() -> NSAttributedString {
let invertedSet = CharacterSet.whitespacesAndNewlines.inverted
let startRange = string.utf16.description.rangeOfCharacter(from: invertedSet)
let endRange = string.utf16.description.rangeOfCharacter(from: invertedSet, options: .backwards)
guard let startLocation = startRange?.upperBound, let endLocation = endRange?.lowerBound else {
return NSAttributedString(string: string)
}
let location = string.utf16.distance(from: string.startIndex, to: startLocation) - 1
let length = string.utf16.distance(from: startLocation, to: endLocation) + 2
let range = NSRange(location: location, length: length)
return attributedSubstring(from: range)
}
}
USAGE
let attributeString = NSAttributedString(string: "\n\n\n Hi π π©βπ©βπ§π©βπ©βπ¦βπ¦π©βπ©βπ§βπ§π¨βπ¨βπ¦π©βπ¦π¨βπ¨βπ§βπ§π¨βπ¨βπ¦βπ¦π¨βπ¨βπ§βπ¦π©βπ§βπ¦π©βπ¦βπ¦π©βπ§βπ§π¨βπ¦ buddy. ")
let result = attributeString.trimWhiteSpace().string // Hi π π©βπ©βπ§π©βπ©βπ¦βπ¦π©βπ©βπ§βπ§π¨βπ¨βπ¦π©βπ¦π¨βπ¨βπ§βπ§π¨βπ¨βπ¦βπ¦π¨βπ¨βπ§βπ¦π©βπ§βπ¦π©βπ¦βπ¦π©βπ§βπ§π¨βπ¦ buddy.
Swift 4 and above
extension NSMutableAttributedString {
func trimmedAttributedString() -> NSAttributedString {
let invertedSet = CharacterSet.whitespacesAndNewlines.inverted
let startRange = string.rangeOfCharacter(from: invertedSet)
let endRange = string.rangeOfCharacter(from: invertedSet, options: .backwards)
guard let startLocation = startRange?.upperBound, let endLocation = endRange?.lowerBound else {
return NSAttributedString(string: string)
}
let location = string.distance(from: string.startIndex, to: startLocation) - 1
let length = string.distance(from: startLocation, to: endLocation) + 2
let range = NSRange(location: location, length: length)
return attributedSubstring(from: range)
}
}
use:
let string = "This is string with some space in the end. "
let attributedText = NSMutableAttributedString(string: string).trimmedAttributedString()
It turns out that Unicode strings are hard hahaha! The other solutions posted here are a great starting point, but they crashed for me when using non-latin strings.
Whenever using indexes or ranges in Swift Strings, we need to use String.Index instead of plain Int. Creating an NSRange from a Range<String.Index> has to be done with NSRange(swiftRange, in: String).
That being said, this code builds on the other answers, but makes it unicode-proof:
public extension NSMutableAttributedString {
/// Trims new lines and whitespaces off the beginning and the end of attributed strings
func trimmedAttributedString() -> NSAttributedString {
let invertedSet = CharacterSet.whitespacesAndNewlines.inverted
let startRange = string.rangeOfCharacter(from: invertedSet)
let endRange = string.rangeOfCharacter(from: invertedSet, options: .backwards)
guard let startLocation = startRange?.lowerBound, let endLocation = endRange?.lowerBound else {
return NSAttributedString(string: string)
}
let trimmedRange = startLocation...endLocation
return attributedSubstring(from: NSRange(trimmedRange, in: string))
}
}
I made a swift 3 implementation, just in case anyone is interested:
/**
Trim an attributed string. Can for example be used to remove all leading and trailing spaces and line breaks.
*/
public func attributedStringByTrimmingCharactersInSet(set: CharacterSet) -> NSAttributedString {
let invertedSet = set.inverted
let rangeFromStart = string.rangeOfCharacter(from: invertedSet)
let rangeFromEnd = string.rangeOfCharacter(from: invertedSet, options: .backwards)
if let startLocation = rangeFromStart?.upperBound, let endLocation = rangeFromEnd?.lowerBound {
let location = string.distance(from: string.startIndex, to: startLocation) - 1
let length = string.distance(from: startLocation, to: endLocation) + 2
let newRange = NSRange(location: location, length: length)
return self.attributedSubstring(from: newRange)
} else {
return NSAttributedString()
}
}
Swift 3.2 Version:
extension NSAttributedString {
public func trimmingCharacters(in characterSet: CharacterSet) -> NSAttributedString {
let modifiedString = NSMutableAttributedString(attributedString: self)
modifiedString.trimCharacters(in: characterSet)
return NSAttributedString(attributedString: modifiedString)
}
}
extension NSMutableAttributedString {
public func trimCharacters(in characterSet: CharacterSet) {
var range = (string as NSString).rangeOfCharacter(from: characterSet)
// Trim leading characters from character set.
while range.length != 0 && range.location == 0 {
replaceCharacters(in: range, with: "")
range = (string as NSString).rangeOfCharacter(from: characterSet)
}
// Trim trailing characters from character set.
range = (string as NSString).rangeOfCharacter(from: characterSet, options: .backwards)
while range.length != 0 && NSMaxRange(range) == length {
replaceCharacters(in: range, with: "")
range = (string as NSString).rangeOfCharacter(from: characterSet, options: .backwards)
}
}
}
Following code will work for your requirement.
var attString: NSAttributedString = NSAttributedString(string: " this is att string")
let trimmedString = attString.string.stringByTrimmingCharactersInSet(NSCharacterSet.whitespaceCharacterSet())
I have a string (for example: "This is some text with specific http://goo.gl/45hz web adress in it").
I need to find all URLs within the string if any and then convert somehow the string so the address will be tapable (safari will open if user taps on it) and i need to display the whole string with URL in label idealy (if possible).
Does anybody know how can be this achieved?
In order to find all URLS in a string
NSError *error = NULL;
NSString *string = #"This is some text with specific http://goo.gl/45hz web adress in it";
NSDataDetector *detector = [NSDataDetector dataDetectorWithTypes:NSTextCheckingTypeLink error:&error];
NSArray *matches = [detector matchesInString:string
options:0
range:NSMakeRange(0, [string length])];
for (NSTextCheckingResult *match in matches) {
if ([match resultType] == NSTextCheckingTypeLink) {
NSURL *url = [match URL];
NSLog(#"url...%#", url);
}
}
You can use NSRange to find the http:// first and and from that location, discard the text before http://, finally you can separate the remaining string using spaces, take first part of the remaining string which will contain URL.
NSString *givenStr = #"This is some text with specific http://goo.gl/45hz web address in it";
NSRange range = [givenStr rangeOfString:#"http://" options:NSCaseInsensitiveSearch];
if (range.location != NSNotFound) {
NSString *urlString = [givenStr substringFromIndex:range.location];
// urlString="http://goo.gl/45hz web address in it"
NSArray *urlStrArray = [urlString componentsSeparatedByString:#" "];
NSURL *finalURL=[NSURL URLWithString:[urlStrArray objectAtIndex:0]];
// [urlStrArray objectAtIndex:0]="http://goog.gl/45hz"
}
For making URL clickable as said by #calampunay you should use UITextView instead of UILabel because UILabel presents only plain text.
You can use a UITextView instead of UILabel. Set editable to NO and then modify the property #property(nonatomic) UIDataDetectorTypes dataDetectorTypes to detect URLs.
#In swift 5 you will try like this way to find URL
let input = "here is your String"
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector.matches(in: input, options: [], range:NSRange(location: 0, length: input.utf16.count))
for match in matches {
guard let range = Range(match.range, in: input) else { continue }
let url = input[range]
print(url)
}
Use TTTAttributedLabel if this is coming from a UI label - https://github.com/mattt/TTTAttributedLabel
Works great for us.
I have implemented same feature in swift 3. Below is the complete code to detect url in string and make it tapable :
//put these lines of code inside your function
//add Tap Gesture
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapLabel))
myLabel?.addGestureRecognizer(tapGesture)
let input = βtap on url http://www.google.com, to go to google searchβ
myLabel?.attributedText = getAttributedString(input: input);
/*** functions to perform complete task ***/
//function to get attributed string with url
func getAttributedString(input : String) -> NSMutableAttributedString {
let matches = getURLRange(input: input)
let attributedString = NSMutableAttributedString(string:input)
for match in matches {
let url = (input as NSString).substring(with: match.range)
print(url)
let linkText = NSMutableAttributedString(string:url, attributes:[NSForegroundColorAttributeName : UIColor.blue] as [String : Any])
attributedString.replaceCharacters(in: match.range, with: linkText)
}
return attributedString
}
//function to get urls range
func getURLRange(input : String) -> [NSTextCheckingResult] {
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector.matches(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count))
return matches
}
// function to check if URL is taped
func tapLabel(gesture: UITapGestureRecognizer) {
let matches = getURLRange(input: (myLabel?.text)!)
let tapLocation = gesture.location(in: myLabel)
let indexOfCharacter = didTapAttributedTextInLabel(label: myLabel!, tapLocation: tapLocation)
for match in matches {
if NSLocationInRange(indexOfCharacter, match.range) {
//open selected URL
let mainText = myLabel?.text as NSString?
let urlToOpen = URL(string: (mainText?.substring(with: match.range))!)
UIApplication.shared.open(urlToOpen!)
break;
}else {
print("Tapped none")
}
}
}
//function to get index Of selected Character in string
func didTapAttributedTextInLabel(label: UILabel, tapLocation: CGPoint) -> Int {
//here myLabel is the object of UILabel
//added this from #warly's answer
//set font of attributedText
let attributedText = NSMutableAttributedString(attributedString: myLabel!.attributedText!)
attributedText.addAttributes([NSFontAttributeName: myLabel!.font], range: NSMakeRange(0, (myLabel!.attributedText?.string.characters.count)!))
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize(width: (myLabel?.frame.width)!, height: (myLabel?.frame.height)!+100))
let textStorage = NSTextStorage(attributedString: attributedText)
// Configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
// Configure textContainer
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = myLabel!.lineBreakMode
textContainer.maximumNumberOfLines = myLabel!.numberOfLines
let labelSize = myLabel!.bounds.size
textContainer.size = labelSize
// get the index of character where user tapped
let indexOfCharacter = layoutManager.characterIndex(for: tapLocation, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return indexOfCharacter
}
It is tested and working fine inside my app.
Swift 4 implementation of Ankur's answer:
let messageLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.font = UIFont.systemFont(ofSize: 14, weight: UIFont.Weight.regular)
label.isUserInteractionEnabled = true
return label
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.addSubview(messageLabel)
// setup messageLabel constraints
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tapLabel))
messageLabel.addGestureRecognizer(tapGesture)
}
//function to get attributed string with url
func getAttributedString(input : String) -> NSMutableAttributedString {
let matches = getURLRange(input: input)
let attributedString = NSMutableAttributedString(string:input)
for match in matches {
let url = (input as NSString).substring(with: match.range)
let linkText = NSMutableAttributedString(string:url, attributes:[NSAttributedString.Key(rawValue: NSAttributedString.Key.foregroundColor.rawValue) : UIColor.appleBlue()])
attributedString.replaceCharacters(in: match.range, with: linkText)
}
return attributedString
}
func getURLRange(input : String) -> [NSTextCheckingResult] {
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector.matches(in: input, options: [], range: NSRange(location: 0, length: input.utf16.count))
return matches
}
//function to get index Of selected Character in string
func didTapAttributedTextInLabel(label: UILabel, tapLocation: CGPoint) -> Int {
//here myLabel is the object of UILabel
//added this from #warly's answer
//set font of attributedText
let attributedText = NSMutableAttributedString(attributedString: messageLabel.attributedText!)
attributedText.addAttributes([NSAttributedString.Key.font: messageLabel.font], range: NSMakeRange(0, (messageLabel.attributedText?.string.count)!))
// Create instances of NSLayoutManager, NSTextContainer and NSTextStorage
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: CGSize(width: messageLabel.frame.width, height: messageLabel.frame.height+100))
let textStorage = NSTextStorage(attributedString: attributedText)
// Configure layoutManager and textStorage
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
// Configure textContainer
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = messageLabel.lineBreakMode
textContainer.maximumNumberOfLines = messageLabel.numberOfLines
let labelSize = messageLabel.bounds.size
textContainer.size = labelSize
// get the index of character where user tapped
let indexOfCharacter = layoutManager.characterIndex(for: tapLocation, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return indexOfCharacter
}
// function to check if URL is taped
#objc func tapLabel(gesture: UITapGestureRecognizer) {
let matches = getURLRange(input: (messageLabel.text)!)
let tapLocation = gesture.location(in: messageLabel)
let indexOfCharacter = didTapAttributedTextInLabel(label: messageLabel, tapLocation: tapLocation)
for match in matches {
if NSLocationInRange(indexOfCharacter, match.range) {
//open selected URL
let mainText = messageLabel.text as NSString?
if let urlToOpen = URL(string: (mainText?.substring(with: match.range))!) {
UIApplication.shared.open(urlToOpen)
} else {
print("We have error with URL")
}
break
} else {
print("Tapped none")
}
}
}
Swift 5 extension
extension String {
func extractUrl() -> URL? {
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector.matches(in: self, options: [], range: NSRange(location: 0, length: self.utf16.count))
for match in matches {
guard let range = Range(match.range, in: self) else { continue }
let url = self[range]
return URL(string: String(url))
}
return nil
}
}