How to add target to NSAttributedString - ios

I have a UITextView that has some text, and then should have a clickable string at the end of the paragraph, but I'm not sure how to approach this. I wanted to use attributed strings, but I cannot add a target to the attributed string to make it clickable. How can I accomplish this?

You can use UITextViewDelegate to detect iuser interation.
#IBOutlet weak var mapTextView: UITextView!
func setupTextView() {
let mapAttributedText = NSMutableAttributedString(string: "Click here to get directions")
mapAttributedText.addAttribute(NSAttributedString.Key.link, value: "customValue", range: mapAttributedText.mutableString.range(of: "Click here"))
let linkArr = [NSAttributedString.Key.foregroundColor: UIColor.blue,NSAttributedString.Key.underlineStyle: NSUnderlineStyle.single.rawValue,NSAttributedString.Key.underlineColor: UIColor.blue] as [NSAttributedString.Key : Any]
mapTextView.linkTextAttributes = linkArr
mapTextView.attributedText = mapAttributedText
mapTextView.delegate = self
mapTextView.isEditable = false
}
extension YourVCName: UITextViewDelegate {
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if URL.absoluteString == "customValue" {
// Do something
}
return false
}
}

Related

UITextView avoid text selection, but keep tappable links

I have a pop-up window, that shows terms of service & privacy policy for users, how to disable text selection but keep links tappable. screenshot
If I set:
SOLVED: By adding textViewDidChangeSelection method in UIViewDelegate and setting
textView.isSelectable = false
textView.isUserInteractionEnabled = true
textView.isSelectable = true
Links become uninteractable.
How do I keep the link tappable and text not selectable?
My whole class:
// HyperLinkTextView.swift
import SwiftUI
struct Hyperlink {
var word: String
var url: NSURL
}
struct HyperLinkTextView: UIViewRepresentable {
private var text: String
private var links: [Hyperlink]
let textView = UITextView()
init(text: String, links: [Hyperlink]) {
self.text = text
self.links = links
}
func makeUIView(context: Self.Context) -> UITextView {
let attributedString = NSMutableAttributedString(string: text)
links.forEach { hyperlink in
let linkAttributes = [NSAttributedString.Key.link: hyperlink.url]
var nsRange = NSMakeRange(0, 0)
if let range = text.range(of: hyperlink.word) {
nsRange = NSRange(range, in: text)
}
attributedString.setAttributes(linkAttributes, range: nsRange)
attributedString.addAttribute(NSAttributedString.Key.underlineStyle, value: NSNumber(value: 1), range: nsRange)
}
textView.isEditable = false
textView.delegate = context.coordinator
textView.attributedText = attributedString
textView.linkTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor(Theme.colorClickables)]
textView.isUserInteractionEnabled = true
textView.isSelectable = true
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.font = UIFont(name: "ArialMT", size: 18)
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
public class Coordinator: NSObject, UITextViewDelegate, NSLayoutManagerDelegate {
weak var textView: UITextView?
public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction, replacementText text: String) -> Bool {
return true
}
func textViewDidChangeSelection(_ textView: UITextView) {
if textView.selectedTextRange != nil {
textView.delegate = nil
textView.selectedTextRange = nil
textView.delegate = self
}
}
}
}
Add this UITextViewDelegate textViewDidChangeSelection and comment out isEditable and isSelectable:
func textViewDidChangeSelection(_ textView: UITextView) {
if textView.selectedTextRange != nil {
textView.delegate = nil
textView.selectedTextRange = nil
textView.delegate = self
}
}

UITextView with NSMutableAttributedString with Link SwiftUI

I have this Custom UITextView that uses "ShouldInteractWith" method:
class StudyText: UITextView, UITextViewDelegate {
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
print(URL)
return false
}
}
struct ClickableText: UIViewRepresentable {
#Binding var text: NSMutableAttributedString
func makeUIView(context: Context) -> StudyText {
let view = StudyText()
view.dataDetectorTypes = .all
view.isEditable = false
view.isSelectable = true
view.delegate = view
view.isUserInteractionEnabled = true
return view
}
func updateUIView(_ uiView: StudyText, context: Context) {
uiView.attributedText = text
}
}
I have this extension to set a link to the attributed text:
extension NSMutableAttributedString {
func apply(link: String, subString: String) {
if let range = self.string.range(of: subString) {
self.apply(link: link, onRange: NSRange(range, in: self.string))
}
}
private func apply(link: String, onRange: NSRange) {
self.addAttributes([NSAttributedString.Key.link: link], range: onRange)
}
}
And I created this layout:
struct contentView: View {
#State text: NSMutableAttributedString = NSMutableAttributedString(string: "")
var body: some View {
VStack {
ClickableText(text: self.$text)
}
.onAppear{
let myText = "Click Me!"
let attributedString = NSMutableAttributedString(string: myText)
attributedString.apply(link: "Soeme random link", subString: myText)
self.text = attributedString
}
}
}
When I click on the text view it doesn't print anything to the console and sometimes it crashes.
How can I fix this?
It must be provided valid URL, like
let attributedString = NSMutableAttributedString(string: myText)
attributedString.apply(link: "https://www.google.com", subString: myText)

tapgesture for textview string in swift

I have a signup page with privacy and term&condition. I am using textview controller for show content when I was clicked any of them show another view controller, I tried this code but it's not working,
let signUpTermsAndPrivacyString = NSMutableAttributedString(string: "I have read and understood PayGyft Terms of Usage and Privacy Policy",attributes: [NSAttributedStringKey.font: UIFont(name: "Helvetica", size: 15.0)!,NSAttributedStringKey(rawValue: NSAttributedStringKey.foregroundColor.rawValue): UIColor.darkGray])
let termsString = signUpTermsAndPrivacyString.mutableString.range(of: "Terms of Usage")
print("termsSTring",termsString)
signUpTermsAndPrivacyString.addAttribute(.link, value: "http://gregoryadunbar.com", range: termsString)
let privacyString = signUpTermsAndPrivacyString.mutableString.range(of: "Privacy Policy")
signUpTermsAndPrivacyString.addAttribute(.link, value:"http://gregoryadunbar.com", range: privacyString)
termsPrivacyPolicyTextView.attributedText = signUpTermsAndPrivacyString
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
if (URL.absoluteString == termsURLString) {
print("termsURLString")
} else if (URL.absoluteString == privacyURLString) {
print("privacyURLString")
}
return false
}
You need to add myTextView.linkTextAttributes
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
let attributes = [NSAttributedStringKey.foregroundColor : UIColor.init(rgb: 0x646464),
NSAttributedStringKey.font : AppFont.getFont(fontType: .regular, ofSize: 16),
NSAttributedStringKey.paragraphStyle : paragraphStyle]
let attributedText = NSMutableAttributedString(string: descriptonText,
attributes: attributes)
if let range = attributedText.string.range(of: "terms & conditions") {
let nsRange = NSRange(range, in: attributedText.string)
attributedText.addAttributes([NSAttributedStringKey.link : "link://T&C"], range: nsRange)
}
let linkColor = UIColor.init(red: 47, green: 117, blue: 83)
let linkAttrs: [String: Any] = [NSAttributedStringKey.foregroundColor.rawValue : linkColor,
NSAttributedStringKey.underlineColor.rawValue : linkColor,
NSAttributedStringKey.underlineStyle.rawValue : NSUnderlineStyle.styleSingle.rawValue]
descriptionTextView.linkTextAttributes = linkAttrs
let textView = UITextView(frame: descriptionTextView.bounds)
textView.attributedText = attributedText
textView.layoutIfNeeded()
textView.sizeToFit()
let size = textView.sizeThatFits(CGSize(width: textView.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
descriptionTextViewHeight.constant = size.height
descriptionTextView.attributedText = attributedText
And in delegate:
func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
return false
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
return false
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
debugPrint(URL.absoluteString)
if URL.absoluteString.contains("link://") {
// my func call
dismissAnimated()
}
return false
}
Also set isEditable to false and isSelectable to true. Subclass textView and return false in canPerformAction. In awakeFromNib set textDragInteraction?.isEnabled = false

How to make the alert have the correct word?

I have text in a TextView, there are some words in the text, in the case when the user clicks on it, an alert should appear with a word, which for example can mean a word translation into some language.
I tried to use a dictionary, where the key is the word in the text, and the word in the alert is a value. But it does not work. There is an alert with the wrong word.
import UIKit
class ViewController: UIViewController , UITextViewDelegate {
private let kURLString = "https://www.mywebsite.com"
let dictionary = ["website" : "Johny" , "visit" : "Bilbo"]
var keyOne : String?
var valueOne : String?
#IBOutlet weak var text: UITextView! {
didSet{
text.delegate = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
let originalText = "Please visit the website for more information."
let attributedOriginalText = NSMutableAttributedString(string: originalText)
for (key , value) in dictionary {
keyOne = key
valueOne = value
let linkRange = attributedOriginalText.mutableString.range(of: keyOne!)
attributedOriginalText.addAttribute(.link, value: kURLString, range: linkRange)
}
text.attributedText = attributedOriginalText
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
if (URL.absoluteString == kURLString) {
alert(value: valueOne!)
}
return false
}
func alert (value : String) {
let alert = UIAlertController (title: nil, message: value, preferredStyle: .alert)
let restartAction = UIAlertAction(title: "Ок", style: .default , handler : { (UIAlertAction) in
self.viewDidLoad()
})
alert.addAction(restartAction)
present(alert, animated: true, completion: nil)
}
}
You can do this by creating an extension for reusable next time as follow code below:
extension UITextView{
func textRangeFromNSRange(range:NSRange)->String{
let myNSString = self.text as NSString
return myNSString.substring(with: range)
}
}
Usage:
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
if (URL.absoluteString == kURLString) {
alert(value: textView.textRangeFromNSRange(range: characterRange))
}
return false
}
If you want to get value from your dictionary you can do like below:
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
if (URL.absoluteString == kURLString) {
alert(value: dictionary[textView.textRangeFromNSRange(range: characterRange)]!)
}
return false
}
Noted: Make sure links in textViews are selectable but not editable.
Links in text views are interactive only if the text view is selectable but noneditable.
set editable false & selectable true:
text.isEditable = false
text.isSelectable = true
To get selected text from textview which is key in your case use below function:
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
if let key = textView.text.substring(with: characterRange){
if let value = dictionary[String(key)]{
print("text :",value)
alert(value: value)
}
}
return false
}
Using extension for string to get Substring with range:
extension String {
func substring(with nsrange: NSRange) -> Substring? {
guard let range = Range(nsrange, in: self) else { return nil }
return self[range]
}
}

shouldInteractWith url textview delegate not called in ios9 but working in ios10?

shouldInteractWith url textview delegate not called in ios9 but i have implemented ios9 delegate method also. Code is below. If someone knows whats the issue fix then please let me know. thanks in advance for any help
import UIKit
class UrNestTermsView: UIView {
// Outlets
#IBOutlet weak var contentView: UIView!
#IBOutlet weak var termsTextView: UITextView!
#IBOutlet weak var termsUrNestCheckBox: UrNestCheckBox!
#IBOutlet weak var subscribeUrNestCheckBox: UrNestCheckBox!
#IBOutlet weak var subscribeTextView: UITextView!
fileprivate let fontOfTerms = UIFont.systemFont(with: 10)
fileprivate let labelTextColor = UIColor(colorLiteralRed: 102/255, green: 110/255, blue: 111/255, alpha: 1)
override func awakeFromNib() {
super.awakeFromNib()
self.setUp()
}
}
extension UrNestTermsView: UITextViewDelegate {
func setUp() {
self.subscribeTextView.textColor = labelTextColor
self.subscribeTextView.font = fontOfTerms
self.setUpTextView()
}
func setUpTextView() {
// You must set the formatting of the link manually
let linkAttributeTermsOfService = [
NSLinkAttributeName: NSURL(string: "https://www.apple.com")!,
NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue,
NSForegroundColorAttributeName: labelTextColor
] as [String : Any]
let linkAttributePolicy = [
NSLinkAttributeName: NSURL(string: "https://www.google.com")!,
NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue,
NSForegroundColorAttributeName: labelTextColor
] as [String : Any]
let linkAttributePaymentsTermsOfService = [
NSLinkAttributeName: NSURL(string: "https://www.amazon.com")!,
NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue,
NSForegroundColorAttributeName: labelTextColor
] as [String : Any]
let linkAttributeGuestRefundPolicy = [
NSLinkAttributeName: NSURL(string: "https://www.yahoo.com")!,
NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue,
NSForegroundColorAttributeName: labelTextColor
] as [String : Any]
let linkAttributeHostGuaranteeTerms = [
NSLinkAttributeName: NSURL(string: "https://www.assembla.com")!,
NSUnderlineStyleAttributeName: NSUnderlineStyle.styleSingle.rawValue,
NSForegroundColorAttributeName: labelTextColor
] as [String : Any]
let attributedString = NSMutableAttributedString(string: kTermsAndPolicy)
// Set the 'click here' substring to be the link
attributedString.setAttributes(linkAttributeTermsOfService, range: NSMakeRange(33, 16))
attributedString.setAttributes(linkAttributePolicy, range: NSMakeRange(51, 6))
attributedString.setAttributes(linkAttributePaymentsTermsOfService, range: NSMakeRange(59, 25))
attributedString.setAttributes(linkAttributeGuestRefundPolicy, range: NSMakeRange(102, 19))
attributedString.setAttributes(linkAttributeHostGuaranteeTerms, range: NSMakeRange(127, 20))
attributedString.addAttribute(NSFontAttributeName, value: fontOfTerms, range: NSMakeRange(0, attributedString.length-1))
attributedString.addAttribute(NSForegroundColorAttributeName, value: labelTextColor, range: NSMakeRange(0, attributedString.length-1))
self.termsTextView.delegate = self
self.termsTextView.attributedText = attributedString
let attributedStringOfSubscribe = NSMutableAttributedString(string: kSubscribe)
attributedStringOfSubscribe.addAttribute(NSFontAttributeName, value: fontOfTerms, range: NSMakeRange(0, attributedStringOfSubscribe.length))
attributedStringOfSubscribe.addAttribute(NSForegroundColorAttributeName, value: labelTextColor, range: NSMakeRange(0, attributedStringOfSubscribe.length))
self.subscribeTextView.delegate = self
self.subscribeTextView.attributedText = attributedStringOfSubscribe
}
//For iOS 7...9
#available(iOS, deprecated: 10.0)
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
return true
}
#available(iOS 10.0, *)
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
return true
}
}
you must check Selectable in storyboard or do it in code :
textView.isSelectable = true
try this:
#available(iOS, deprecated: 10.0)
func textView(_ textView: UITextView, shouldInteractWith url: URL, in characterRange: NSRange) -> Bool {
return true
}
//For iOS 10
#available(iOS 10.0, *)
func textView(_ textView: UITextView, shouldInteractWith url: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
return true
}

Resources