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")
}
}
}
Related
I'm developing an App, which has this feature: when user press a shortcut button, the UITextView is entered into the corresponding symbol (like "**", "__" or """"). Then the cursor turn back to the position between the pair of symbols, and user input what the want.
When user has done, he can press the "enter" button to jump out of the pair of symbol and input sth else.
But if the user change the cursor manually, for example he move the cursor out of the symbols.He press enter then display a normal line feed.
I have mostly done this function. But I don't know how to track if the cursor is still between the pair of symbols.
#objc func shortcutFunc(sender: CustomBtn, forEvent: UIEvent) {
insertFromCursor(sender: sender, forEvent: forEvent)
retreat = sender.retreat!
var selectedView: CustomTextView?
if bodyViewUnderEditing {
selectedView = textField.bodyView
} else if titleViewUnderEditing {
selectedView = textField.titleView
}
if let selectedView = selectedView {
let location = selectedView.selectedRange.location
selectedView.selectedRange = NSRange(location: location - sender.retreat!, length: 0)
isShortcutBtnInputing = true
}
}
#objc func insertFromCursor(sender: CustomBtn, forEvent event: UIEvent) {
var selectedView: CustomTextView?
if bodyViewUnderEditing {
selectedView = textField.bodyView
} else if titleViewUnderEditing {
selectedView = textField.titleView
}
if let selectedView = selectedView {
let range = selectedView.selectedRange
let start = selectedView.position(from: selectedView.beginningOfDocument, offset: range.location)!
let end = selectedView.position(from: start, offset: range.length)!
let textRange = selectedView.textRange(from: start, to: end)!
selectedView.replace(textRange, withText: sender.argument!)
}
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if textView == textField.bodyView {
if text == "\n", isShortcutBtnInputing, bodyViewUnderEditing {
let location = textField.bodyView.selectedRange.location
textField.bodyView.selectedRange = NSRange(location: location + retreat, length: 0)
isShortcutBtnInputing = false
return false
}
return true
} else if textView == textField.titleView {
if text == "\n" {
if isShortcutBtnInputing, titleViewUnderEditing {
let location = textField.titleView.selectedRange.location
textField.titleView.selectedRange = NSRange(location: location + retreat, length: 0)
isShortcutBtnInputing = false
} else {
textField.bodyView.becomeFirstResponder()
}
return false
}
return true
}
return true
}
I'm having a custom NSLayoutManager with these two methods overwritten:
override func drawGlyphs(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint) {
super.drawGlyphs(forGlyphRange: glyphsToShow, at: origin)
let characterRange = self.characterRange(forGlyphRange: glyphsToShow, actualGlyphRange: nil)
textStorage?.enumerateAttribute(.blur, in: characterRange, options: .longestEffectiveRangeNotRequired, using: { (value, subrange, _) in
guard let key = value as? String, !key.isEmpty else { return }
let blurGlyphRange = glyphRange(forCharacterRange: subrange, actualCharacterRange: nil)
drawBlur(forGlyphRange: blurGlyphRange)
textStorage?.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.clear], range: blurGlyphRange)
})
}
private func drawBlur(forGlyphRange tokenGlypeRange: NSRange) {
guard let textContainer = textContainer(forGlyphAt: tokenGlypeRange.location, effectiveRange: nil) else { return }
let withinRange = NSRange(location: NSNotFound, length: 0)
enumerateEnclosingRects(forGlyphRange: tokenGlypeRange, withinSelectedGlyphRange: withinRange, in: textContainer) { (rect, _) in
let blurRect = rect.offsetBy(dx: self.textContainerOriginOffset.width, dy: self.textContainerOriginOffset.height)
UIColor.red.setFill()
UIBezierPath(roundedRect: blurRect, cornerRadius: 4).fill()
}
Everything works fine except when I set the UITextView isScrollingEnabled on false
I enter an endless loop caused by the textStorage enumerateAttribute method in drawGlyphs.
I don't understand why this happens and also I don't know how to prevent this.
Someone who knows more about this?
EDIT
If I remove the textStorage addAttributes with the foregroundColor then it works. So that's causing the loop for some reason.
I found the problem why it comes in and endless loop. The textstorage updates/add the attribute and then notifies the layoutmanager again.
Solution is to create your own textstorage like this:
class CustomTextStorage: NSTextStorage {
private let backingStore = NSMutableAttributedString()
override var string: String {
return backingStore.string
}
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func attributes(at location: Int, effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key: Any] {
return backingStore.attributes(at: location, effectiveRange: range)
}
override func replaceCharacters(in range: NSRange, with str: String) {
beginEditing()
backingStore.replaceCharacters(in: range, with:str)
edited(.editedCharacters, range: range, changeInLength: (str as NSString).length - range.length)
endEditing()
}
override func setAttributes(_ attrs: [NSAttributedString.Key: Any]?, range: NSRange) {
beginEditing()
backingStore.setAttributes(attrs, range: range)
if let attrs = attrs, let _ = attrs[.blur] {
backingStore.addAttribute(.foregroundColor, value: UIColor.clear, range: range)
}
edited(.editedAttributes, range: range, changeInLength: 0)
endEditing()
}
}
and remove the line under the draw method in the layoutmanager:
textStorage?.addAttributes([NSAttributedString.Key.foregroundColor: UIColor.clear], range: blurGlyphRange)
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")
}
}
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 have NSAttributed string with links in it and I want to load it inside UILabel. I works fine, however all links are blue Color.
let string = NSMutableAttributedString(attributedString: attributedText)
string.addAttributes([NSForegroundColorAttributeName:linkColor], range: linkRange)
self.attributedText = string
No change to foreground color, setting all other attributes work, like strikethrough style. Just link always stays blue.
NSAttributed string is generated from HTML if that makes any difference.
Ended up doing
class AttributedTextLabel:UILabel {
var attributedString:NSAttributedString?{
didSet{
guard let attributedString = attributedString else {
return
}
let mutableAttributedString = NSMutableAttributedString(attributedString: attributedString)
mutableAttributedString.enumerateAttribute(NSLinkAttributeName, inRange: NSRange(location: 0, length: attributedString.length), options: NSAttributedStringEnumerationOptions.Reverse) {[weak self] (attribute, range, other) in
if let url = attribute as? NSURL {
mutableAttributedString.removeAttribute(NSLinkAttributeName, range: range)
self?.links.append(Link(url: url, range: range))
}
}
self.attributedText = mutableAttributedString
}
}
struct Link {
var url:NSURL
var range:NSRange
}
var links:[Link] = []
var edgeInsets:UIEdgeInsets = UIEdgeInsetsZero
private var textContentSize:CGSize {
let textContainerWidth = frame.width - edgeInsets.left - edgeInsets.right
let textContainerHeight = frame.height - edgeInsets.top - edgeInsets.bottom
return CGSizeMake(textContainerWidth, textContainerHeight)
}
func characterIndexAtPoint(point:CGPoint) -> Int? {
guard let attributedText = attributedText else {
return nil
}
let layoutManager = NSLayoutManager()
let textContainer = NSTextContainer(size: textContentSize)
textContainer.lineFragmentPadding = 0.0
textContainer.lineBreakMode = self.lineBreakMode
textContainer.maximumNumberOfLines = self.numberOfLines
layoutManager.addTextContainer(textContainer)
let storage = NSTextStorage(attributedString: attributedText)
storage.addLayoutManager(layoutManager)
let adjustedPoint = CGPointMake(point.x-edgeInsets.left, point.y-edgeInsets.top)
let characterIndex = layoutManager.characterIndexForPoint(point, inTextContainer: textContainer, fractionOfDistanceBetweenInsertionPoints: nil)
return characterIndex
}
override func drawTextInRect(rect: CGRect) {
return super.drawTextInRect(UIEdgeInsetsInsetRect(rect, edgeInsets))
}
private var selectedRange:NSRange?
private var highligtedLink:Link? {
didSet{
let string = self.attributedText as! NSMutableAttributedString
if let oldValue = oldValue {
if let selectedLinkColor = NativeTextKit.TextAttributes.selectedLinkColor.value {
string.addAttributes([
NSForegroundColorAttributeName:selectedLinkColor
], range: oldValue.range)
}
}
if let highligtedLink = highligtedLink {
if let selectedLinkColor = NativeTextKit.TextAttributes.selectedLinkColor.value {
string.addAttributes([
NSForegroundColorAttributeName:selectedLinkColor
], range: highligtedLink.range)
}
}
self.attributedText = string
}
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let touch = touches.first else {
return
}
let char = characterIndexAtPoint(touch.locationInView(self))
let string = self.attributedText as! NSMutableAttributedString
highligtedLink = linkForTouch(touch)
string.addAttributes([
NSForegroundColorAttributeName:UIColor.brownColor()
], range: NSMakeRange(char!, 1))
attributedText = string
}
func linkForTouch(touch:UITouch)->Link? {
guard let attributedText = attributedText else {
return nil
}
guard let characterIndex = characterIndexAtPoint(touch.locationInView(self)) else {
return nil
}
return links.filter({NSLocationInRange(characterIndex, $0.range)}).first
}
override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
highligtedLink = nil
}
override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
guard let touch = touches.first else {
return
}
if let highligtedLink = highligtedLink, let lastTouchedLink = linkForTouch(touch) where highligtedLink.url == lastTouchedLink.url {
urlInteractionHandler?(textView: UITextView(), url:lastTouchedLink.url)
}
}
/// Executed on link interaction
var urlInteractionHandler:URLInteractionHandler?
}
Does the job, took a while to figure out. Because UILabel has its own link formatting ended up
Remove all links from attributed string once string is set
Add links and ranges to array
After link is selected use NSTextContainer to figure out what index the character was
Find range that character belongs to
Return link