I'm working with a TextView and I wanted to create two different links within a text where the user accepts the terms and conditions and the privacy policy.
Also I need each link to open a different UIViewController.
Can anyone help me with an example to understand how to achieve this?
I need to understand how to create two Hyper links and how to open them in two different ViewControllers
Thank you all for any help you can give me
For example ... I would like to get a TextView similar to this
You can use the following UITextView delegate Method and Attributed string Tested on swift 5.1 :
let attributedString = NSMutableAttributedString(string: "By continueing you agree terms and conditions and the privacy policy")
attributedString.addAttribute(.link, value: "terms://termsofCondition", range: (attributedString.string as NSString).range(of: "terms and conditions"))
attributedString.addAttribute(.link, value: "privacy://privacypolicy", range: (attributedString.string as NSString).range(of: "privacy policy"))
textView.linkTextAttributes = [ NSAttributedString.Key.foregroundColor: UIColor.blue]
textView.attributedText = attributedString
textView.delegate = self
textView.isSelectable = true
textView.isEditable = false
textView.delaysContentTouches = false
textView.isScrollEnabled = false
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if URL.scheme == "terms" {
//push view controller 1
return false
} else if URL.scheme == "privacy"{
// pushViewcontroller 2
return false
}
return true
// let the system open this URL
}
The UITextView call this function if the user taps or longPresses the URL link. Implementation of this method is optional. By default, the UITextview opens those applications which are responsible for handling the URL type and pass them the URL. You can use this method to trigger an alternative action
Set your textView properties like this.
textView.attributedText = "By Continuing, you aggree to terms <a href='http://termsandservicelink'>Terms Of Services</a> and <a href='https://privacypolicylink'>Privacy Policy</a>".convertHtml()
textView.isEditable = false
textView.dataDetectorTypes = [.link]
textView.linkTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.blue, NSAttributedString.Key.underlineColor: UIColor.clear]
You can handle tap event on your link in this delegate.
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
//Check your url whether it is privacy policy or terms and do accordigly
return true
}
Here is String extension.
extension String{
func convertHtml() -> NSAttributedString{
guard let data = data(using: .utf8) else { return NSAttributedString() }
do{
return try NSAttributedString(data: data, options: [NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html, NSAttributedString.DocumentReadingOptionKey.characterEncoding: String.Encoding.utf8.rawValue], documentAttributes: nil)
}catch{
return NSAttributedString()
}
}
}
This result is reached using NSAttributedString, Using NSAttributedString, we can style the text,
myLabel.text = "By signing up you agree to our Terms & Conditions and Privacy Policy"
let text = (myLabel.text)!
let underlineAttriString = NSMutableAttributedString(string: text)
let range1 = (text as NSString).rangeOfString("Terms & Conditions")
underlineAttriString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: range1)
let range2 = (text as NSString).rangeOfString("Privacy Policy")
underlineAttriString.addAttribute(NSUnderlineStyleAttributeName, value: NSUnderlineStyle.StyleSingle.rawValue, range: range2)
myLabel.attributedText = underlineAttriString
Extend UITapGestureRecognizer to provide a convenient function to detect if a certain range (NSRange) is tapped on in a UILabel.
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.locationInView(label)
let textBoundingBox = layoutManager.usedRectForTextContainer(textContainer)
let textContainerOffset = CGPointMake((labelSize.width - textBoundingBox.size.width) * 0.5 - textBoundingBox.origin.x,
(labelSize.height - textBoundingBox.size.height) * 0.5 - textBoundingBox.origin.y);
let locationOfTouchInTextContainer = CGPointMake(locationOfTouchInLabel.x - textContainerOffset.x,
locationOfTouchInLabel.y - textContainerOffset.y);
let indexOfCharacter = layoutManager.characterIndexForPoint(locationOfTouchInTextContainer, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return NSLocationInRange(indexOfCharacter, targetRange)
}
}
UITapGestureRecognizer send action to tapLabel:, and detect using the extension method didTapAttributedTextInLabel:inRange:.
#IBAction func tapLabel(gesture: UITapGestureRecognizer) {
let text = (myLabel.text)!
let termsRange = (text as NSString).rangeOfString("Terms & Conditions")
let privacyRange = (text as NSString).rangeOfString("Privacy Policy")
if gesture.didTapAttributedTextInLabel(myLabel, inRange: termsRange) {
print("Tapped terms")
} else if gesture.didTapAttributedTextInLabel(myLabel, inRange: privacyRange)
{
print("Tapped privacy")
} else {
print("Tapped none")
}
}
Related
I am using UITextView to show following text:
txtTest.userInteractionEnabled = true;
txtTest.selectable = true
txtTest.dataDetectorTypes = .Link;
txtTest.text = "<p>لتيتنظربهاإلىالأمورتؤثربنجاحكفيالعملفعلًاأتعرفالقولالمأثورالقديملاتنظرللنصفالفارغمنالكأسوالطريقةالتيتنظربهاإلىالأمورتؤثربنجاحكفيالعملفعلًا</p>رابط خارجي external link"
UITextView link is not tappable on text رابط خارجي external link. Tappable area goes somewhere else in the UITextView. I just figured out it by tapping random locations on UITextView
Don't know is it the bug of UITextView or something is missing from my side. If anyone experienced the same issue and found any solution?
You will have to make your UIViewController confirm to UITextViewDelegate protocol and implement textView(_:shouldInteractWith:in:interaction:). your standard UITextView setup should look something like this, don't forget the delegate and dataDetectorTypes.
txtTest.delegate = self
txtTest.isUserInteractionEnabled = true // default: true
txtTest.isEditable = false // default: true
txtTest.isSelectable = true // default: true
txtTest.dataDetectorTypes = [.link]
UITextViewDelegate method shouldInteractWithURL:
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
print("Link Selected!")
return true
}
and instead of using anchor tag use attributedText to detect link in your selected text in swift way.
let targetLink = "https://google.com"
let yourAttributes = [NSForegroundColorAttributeName: UIColor.black, NSFontAttributeName: UIFont.systemFont(ofSize: 15)]
let yourOtherAttributes = [NSForegroundColorAttributeName: UIColor.red, NSFontAttributeName: UIFont.systemFont(ofSize: 25)]
let partOne = NSMutableAttributedString(string: "لتيتنظربهاإلىالأمورتؤثربنجاحكفيالعملفعلًاأتعرفالقولالمأثورالقديملاتنظرللنصفالفارغمنالكأسوالطريقةالتيتنظربهاإلىالأمورتؤثربنجاحكفيالعملفعلً ", attributes: yourAttributes)
let partTwo = NSMutableAttributedString(string: " رابط خارجي external link", attributes: yourOtherAttributes)
let text = " رابط خارجي external link"
let str = NSString(string: text)
let theRange = str.range(of: text)
partTwo.addAttribute(NSLinkAttributeName, value: targetLink, range: theRange)
let combination = NSMutableAttributedString()
combination.append(partOne)
combination.append(partTwo)
txtTest.attributedText = combination
if you want to use HTML then you still have to convert it to NSAttributedString. This function will convert all the HTML tags into NSAttributedString.
extension String{
func convertHtml() -> NSAttributedString{
guard let data = data(using: .utf8) else { return NSAttributedString() }
do{
return try NSAttributedString(data: data, options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSCharacterEncodingDocumentAttribute: String.Encoding.utf8.rawValue], documentAttributes: nil)
}catch{
return NSAttributedString()
}
}
}
then you can use it like so.
let stringValue = "<p>لتيتنظربهاإلىالأمورتؤثربنجاحكفيالعملفعلًاأتعرفالقولالمأثورالقديملاتنظرللنصفالفارغمنالكأسوالطريقةالتيتنظربهاإلىالأمورتؤثربنجاحكفيالعملفعلًا</p>رابط خارجي external link"
txtTest.attributedText = stringValue.convertHtml()
I have added a uitextview which is initially non editable. I added a tap gesture which enable the editing to true. In the tap gesture selector I get the word that is being tapped. I have tried a lot many solution but none worked for me as a complete solution. Every solution worked if the textview is not scrolled. But if I scroll the textview the exact word is not retrieved. Here is my code for getting the tapped word:
#objc func handleTap(_ sender: UITapGestureRecognizer) {
notesTextView.isEditable = true
notesTextView.textColor = UIColor.white
if let textView = sender.view as? UITextView {
var pointOfTap = sender.location(in: textView)
print("x:\(pointOfTap.x) , y:\(pointOfTap.y)")
let contentOffsetY = textView.contentOffset.y
pointOfTap.y += contentOffsetY
print("x:\(pointOfTap.x) , y:\(pointOfTap.y)")
word(atPosition: pointOfTap)
}
func word(atPosition: CGPoint) -> String? {
if let tapPosition = notesTextView.closestPosition(to: atPosition) {
if let textRange = notesTextView.tokenizer.rangeEnclosingPosition(tapPosition , with: .word, inDirection: 1) {
let tappedWord = notesTextView.text(in: textRange)
print("Word: \(tappedWord)" ?? "")
return tappedWord
}
return nil
}
return nil
}
EDITED:
Here is the demo project with the problem.
https://github.com/amrit42087/TextViewDemo
The best and easiest way in Swift 4
METHOD 1:
Step 1: Add Tap Gesture on the textview
let tap = UITapGestureRecognizer(target: self, action: #selector(tapResponse(recognizer:)))
textViewTC.addGestureRecognizer(tap)
Step 2: Implement Tap Gesture
#objc func tapResponse(recognizer: UITapGestureRecognizer) {
let location: CGPoint = recognizer.location(in: textViewTC)
let position: CGPoint = CGPoint(x: location.x, y: location.y)
let tapPosition: UITextPosition = textViewTC.closestPosition(to: position)!
guard let textRange: UITextRange = textViewTC.tokenizer.rangeEnclosingPosition(tapPosition, with: UITextGranularity.word, inDirection: 1) else {return}
let tappedWord: String = textViewTC.text(in: textRange) ?? ""
print("tapped word ->", tappedWord)
}
And yes thats it. Go for it.
METHOD 2:
The alternate way is that you can enable links for textview and then set the same as an attribute. Here is an example
var foundRange = attributedString.mutableString.range(of: "Terms of Use") //mention the parts of the attributed text you want to tap and get an custom action
attributedString.addAttribute(NSAttributedStringKey.link, value: termsAndConditionsURL, range: foundRange)
set this attribute text to Textview and textView.delegate = self
Now you just need to handle the response in
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
Hope it helps you. All the best.
You don't need to add the content offset of the text view. When you convert location into a scrollview it will already take its content offset into account.
Removing:
let contentOffsetY = textView.contentOffset.y
pointOfTap.y += contentOffsetY
should work.
Please Try This
//add UITextViewDelegate
let termsAndConditionsURL = "someText"
let privacyURL = "SampleText"
#IBOutlet weak var terms: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
self.terms.delegate = self
// Adding Attributed text to TextView
let str = "By registering, you agree to the Terms and the User Privacy Statement."
let attributedString = NSMutableAttributedString(string: str)
var foundRange = attributedString.mutableString.range(of: "Terms")
attributedString.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.blue, range: foundRange)
attributedString.addAttribute(NSAttributedStringKey.underlineStyle , value: NSUnderlineStyle.styleSingle.rawValue, range: foundRange)
attributedString.addAttribute(NSAttributedStringKey.link, value: termsAndConditionsURL, range: foundRange)
foundRange = attributedString.mutableString.range(of: "User Privacy Statement")
attributedString.addAttribute(NSAttributedStringKey.foregroundColor, value: UIColor.blue, range: foundRange)
attributedString.addAttribute(NSAttributedStringKey.underlineStyle , value: NSUnderlineStyle.styleSingle.rawValue, range: foundRange)
attributedString.addAttribute(NSAttributedStringKey.link, value: privacyURL, range: foundRange)
terms.attributedText = attributedString
// Do any additional setup after loading the view.
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool
{
if (URL.absoluteString == termsAndConditionsURL)
{
// Perform Terms Action here
} else if (URL.absoluteString == privacyURL)
{
// Perform Terms Action here
}
return false
}
I am developing a social app in which I need show post and sharing etc.
I also need to show tag friend.
When post is show on list UI is also similar to Facebook post. Please check below screen shot.
As you can see Clicking Post title is as attributed single line, Where I need to open User Profile when user click on his name that is Person Name Abc and want to open Page when user click on Buzzfeed india,
As we need to use a UILabel to display content like this I am not able to get click on particular word. I have already tried https://github.com/null09264/FRHyperLabel but when there is multiple line Then clicking on word is not perfect.
You can use TTTAttributedLabel to make a particular part or words tappable in a UILabel.
For example
label.text = #"Fork me on GitHub!"; // Repository URL will be automatically detected and linked
NSRange range = [label.text rangeOfString:#"me"];
[label addLinkToURL:[NSURL URLWithString:#"meUrl"] withRange:range];
Here me becomes tappable and on tapping that word delegate method didSelectLinkWithURL gets called and you can check for that link inside that function
(void)attributedLabel:(TTTAttributedLabel *)label didSelectLinkWithURL:(NSURL *)url {
NSLog(#"link %#", [url absoluteString]);
if ([url absoluteString] == #"meUrl") {
//implement your logic here
}
NSLog(#"whole label %#", label);
}
Try using this:
Add Extension
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)
}
}
Then Add UITapGestureRecognizer on your UILable. Please make sure UILabel.isUserInteractionEnabled is true
override func viewDidLoad() {
super.viewDidLoad()
initialSetup()
}
func initialSetup(){
let tap = UITapGestureRecognizer(target: self, action: #selector(self.didTap(_:)))
lblTitle.addGestureRecognizer(tap)
lblTitle.isUserInteractionEnabled = true
lblTitle.numberOfLines = 0
let personname = "Person Name Abc"
let buzzIndia = "Buzzfeed india"
let str = "\(personname) shared \(buzzIndia)'s video"
let attributedString = NSMutableAttributedString(string:str)
let range = (str as NSString).range(of: personname)
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.black, range: range)
if let font = UIFont(name: "Helvetica Bold", size: 14) {
attributedString.addAttribute(NSFontAttributeName, value: font, range: range)
}
let rangeBuzz = (str as NSString).range(of: buzzIndia)
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.black, range: rangeBuzz)
if let font = UIFont(name: "Helvetica Bold", size: 14) {
attributedString.addAttribute(NSFontAttributeName, value: font, range: rangeBuzz)
}
lblTitle.attributedText = attributedString
}
func didTap(_ gesture: UITapGestureRecognizer) {
let text = (lblTitle.text)!
let name = (text as NSString).range(of: "Person Name Abc")
let buzzfeed = (text as NSString).range(of: "Buzzfeed india")
if gesture.didTapAttributedTextInLabel(label: lblTitle, inRange: name) {
//Repective "Person Name Abc" action
print("Person Name Abc")
}else if gesture.didTapAttributedTextInLabel(label: lblTitle, inRange: buzzfeed) {
//Repective "Buzzfeed india" action
print("Buzzfeed india")
}else {
print("other tapped")
}
}
Reference: http://samwize.com/2016/03/04/how-to-create-multiple-tappable-links-in-a-uilabel/
I am trying to display an attributed string in a UITextview with clickable links. I've created a simple test project to see where I'm going wrong and still can't figure it out. I've tried enabling user interaction and setting the shouldInteractWithURLs delegate method, but it's still not working. Here's my code (for a view controller that only contains a textview)
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let string = "Google"
let linkString = NSMutableAttributedString(string: string)
linkString.addAttribute(NSLinkAttributeName, value: NSURL(string: "https://www.google.com")!, range: NSMakeRange(0, string.characters.count))
linkString.addAttribute(NSFontAttributeName, value: UIFont(name: "HelveticaNeue", size: 25.0)!, range: NSMakeRange(0, string.characters.count))
textView.attributedText = linkString
textView.delegate = self
textView.selectable = true
textView.userInteractionEnabled = true
}
And here are the delegate methods I've implemented:
func textViewShouldBeginEditing(textView: UITextView) -> Bool {
return false
}
func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool {
return true
}
This still isn't working. I've searched on this topic and nothing has helped yet. Thanks so much in advance.
Just select the UITextView in your storyboard and go to "Show Attributes inspector" and select selectable and links. As the image below shows. Make sure Editable is unchecked.
For swift3.0
override func viewDidLoad() {
super.viewDidLoad()
let linkAttributes = [
NSLinkAttributeName: NSURL(string: "http://stalwartitsolution.co.in/luminutri_flow/terms-condition")!
] as [String : Any]
let attributedString = NSMutableAttributedString(string: "Please tick box to confirm you agree to our Terms & Conditions, Privacy Policy, Disclaimer. ")
attributedString.setAttributes(linkAttributes, range: NSMakeRange(44, 18))
attributedString.addAttribute(NSUnderlineStyleAttributeName, value: NSNumber(value: 1), range: NSMakeRange(44, 18))
textview.delegate = self
textview.attributedText = attributedString
textview.linkTextAttributes = [NSForegroundColorAttributeName: UIColor.red]
textview.textColor = UIColor.white
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
return true
}
Swift 3 iOS 10: Here's Clickable extended UITextView that detect websites inside the textview automatically as long as the link start with www. for example: www.exmaple.com if it exist anywhere in the text will be clickable. Here's the class:
import Foundation
import UIKit
public class ClickableTextView:UITextView{
var tap:UITapGestureRecognizer!
override public init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
print("init")
setup()
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
func setup(){
// Add tap gesture recognizer to Text View
tap = UITapGestureRecognizer(target: self, action: #selector(self.myMethodToHandleTap(sender:)))
// tap.delegate = self
self.addGestureRecognizer(tap)
}
func myMethodToHandleTap(sender: UITapGestureRecognizer){
let myTextView = sender.view as! UITextView
let layoutManager = myTextView.layoutManager
// location of tap in myTextView coordinates and taking the inset into account
var location = sender.location(in: myTextView)
location.x -= myTextView.textContainerInset.left;
location.y -= myTextView.textContainerInset.top;
// character index at tap location
let characterIndex = layoutManager.characterIndex(for: location, in: myTextView.textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
// if index is valid then do something.
if characterIndex < myTextView.textStorage.length {
let orgString = myTextView.attributedText.string
//Find the WWW
var didFind = false
var count:Int = characterIndex
while(count > 2 && didFind == false){
let myRange = NSRange(location: count-1, length: 2)
let substring = (orgString as NSString).substring(with: myRange)
// print(substring,count)
if substring == " w" || (substring == "w." && count == 3){
didFind = true
// print("Did find",count)
var count2 = count
while(count2 < orgString.characters.count){
let myRange = NSRange(location: count2 - 1, length: 2)
let substring = (orgString as NSString).substring(with: myRange)
// print("Did 2",count2,substring)
count2 += 1
//If it was at the end of textView
if count2 == orgString.characters.count {
let length = orgString.characters.count - count
let myRange = NSRange(location: count, length: length)
let substring = (orgString as NSString).substring(with: myRange)
openLink(link: substring)
print("It's a Link",substring)
return
}
//If it's in the middle
if substring.hasSuffix(" "){
let length = count2 - count
let myRange = NSRange(location: count, length: length)
let substring = (orgString as NSString).substring(with: myRange)
openLink(link: substring)
print("It's a Link",substring)
return
}
}
return
}
if substring.hasPrefix(" "){
print("Not a link")
return
}
count -= 1
}
}
}
func openLink(link:String){
if let checkURL = URL(string: "http://\(link.replacingOccurrences(of: " ", with: ""))") {
if UIApplication.shared.canOpenURL(checkURL) {
UIApplication.shared.open(checkURL, options: [:], completionHandler: nil)
print("url successfully opened")
}
} else {
print("invalid url")
}
}
public override func didMoveToWindow() {
if self.window == nil{
self.removeGestureRecognizer(tap)
print("ClickableTextView View removed from")
}
}
}
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
}
}