AttributedText in TextView while typing [duplicate] - ios

This question already has answers here:
Replace UITextViews text with attributed string
(4 answers)
Closed 6 years ago.
I have a textView, and I am trying to give it an attributed text. I tried achieving it inside shouldChangeTextInRange, but it crashes for range out of index.
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
if myTextView {
textView.attributedText = addAttributedText(1, text: text, fontsize: 13)
let newText = (textView.text as NSString).stringByReplacingCharactersInRange(range, withString: text)
let numberOfChars = newText.characters.count
return numberOfChars < 20
}
return true
}
func addAttributedText(spacing:CGFloat, text:String, fontsize: CGFloat) -> NSMutableAttributedString {
let attributedString = NSMutableAttributedString(string: text, attributes: [NSFontAttributeName:UIFont(
name: "Font",
size: fontsize)!])
attributedString.addAttribute(NSKernAttributeName, value: spacing, range: NSMakeRange(0, text.characters.count))
return attributedString
}
I tried adding attributedString with empty text to textView in viewDidLoad, but that doesn't help. That's why I thought it would be appropriate to do it on shouldChangeTextInRange
(Please note that my addAttributedText method works perfectly for other textviews)
If I use this, in one character type-in, it writes 2x and crashes. What is the right way of handling that kind of converting textView's text to attributed text that is being typed.

Here is the code that I tried to convert from the link above, it might have bugs, but I hope it will be able to help you.
func formatTextInTextView(textView: UITextView)
{
textView.scrollEnabled = false
var selectedRange: NSRange = textView.selectedRange
var text: String = textView.text!
// This will give me an attributedString with the base text-style
var attributedString: NSMutableAttributedString = NSMutableAttributedString(string: text)
var error: NSError? = nil
var regex: NSRegularExpression = NSRegularExpression.regularExpressionWithPattern("#(\\w+)", options: 0, error: error!)
var matches: [AnyObject] = regex.matchesInString(text, options: 0, range: NSMakeRange(0, text.length))
for match: NSTextCheckingResult in matches {
var matchRange: NSRange = match.rangeAtIndex(0)
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: matchRange)
}
textView.attributedText = attributedString
textView.selectedRange = selectedRange
textView.scrollEnabled = true
}
EDIT: didn't see that in the original post there was a Swift answer, here is the link: stackoverflow.com/a/35842523/1226963

Related

Keep hyperlink to specific word in UITextView using attributedString

I'm trying to implement an editor that can handle hashtag while typing.
extension UITextView {
func resolveHashTags() {
if self.text.isEmpty {
let emptyString = NSMutableAttributedString(string: " ", attributes: [NSAttributedString.Key.foregroundColor: UIColor.black,
NSAttributedString.Key.font: self.font!])
self.attributedText = emptyString
self.textColor = .black
self.text = ""
return
}
let cursorRange = selectedRange
let nsText = NSString(string: self.text)
let words = nsText.components(separatedBy: CharacterSet(charactersIn: "##ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_").inverted).filter({!$0.isEmpty})
self.textColor = .black
let attrString = NSMutableAttributedString()
attrString.setAttributedString(self.attributedText)
attrString.addAttributes([NSAttributedString.Key.foregroundColor : UIColor.black], range: nsText.range(of: self.text))
var anchor: Int = 0
for word in words {
// found a word that is prepended by a hashtag!
// homework for you: implement #mentions here too.
let matchRange:NSRange = nsText.range(of: word as String, range: NSRange(location: anchor, length: nsText.length - anchor))
anchor = matchRange.location + matchRange.length
if word.hasPrefix("#") {
// a range is the character position, followed by how many characters are in the word.
// we need this because we staple the "href" to this range.
// drop the hashtag
let stringifiedWord = word.dropFirst()
if let firstChar = stringifiedWord.unicodeScalars.first, NSCharacterSet.decimalDigits.contains(firstChar) {
// hashtag contains a number, like "#1"
// so don't make it clickable
} else {
// set a link for when the user clicks on this word.
// it's not enough to use the word "hash", but you need the url scheme syntax "hash://"
// note: since it's a URL now, the color is set to the project's tint color
attrString.addAttribute(NSAttributedString.Key.link, value: "hash:\(stringifiedWord)", range: matchRange)
}
} else if !word.hasPrefix("#") {
}
}
self.attributedText = attrString
self.selectedRange = cursorRange
}
}
So this is the extension I'm using to create a hyperlink in UITextView. Called in func textViewDidChange(_ textView: UITextView)
So while typing if any word starts with #. It'll turn in hyperlinks and will change color to blue. After typing the intended word if you press space it goes back to black text. This is expected behavior.
But if you clear text and move your course back to hashtag word like this
it keeps extending hyperlink to the next word too.
any solution to keep hyperlinks to that word only. Anything typed after hashtag should be normal text
I finally figured it out.
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
var shouldReturn = true
let selectedRange = textView.selectedRange
let attributedText = NSMutableAttributedString(attributedString: textView.attributedText)
if !text.isEmpty && text != " " {
var userAttributes = [(NSAttributedString.Key, Any, NSRange)]()
attributedText.enumerateAttribute(.link, in: _NSRange(location: 0, length: textView.text.count), options: .longestEffectiveRangeNotRequired) { (value, range, stop) in
if let url = value as? String, url.hasPrefix("user:") {
userAttributes.append((.link, value!, range))
}
}
if let userLink = userAttributes.first(where: {$0.2.contains(range.location - 1)}) {
attributedText.replaceCharacters(in: range, with: NSAttributedString(string: text, attributes: [NSAttributedString.Key.link : userLink.1, NSAttributedString.Key.font : textView.font as Any]))
textView.attributedText = attributedText
shouldReturn = false
} else {
attributedText.replaceCharacters(in: range, with: NSAttributedString(string: text, attributes: [NSAttributedString.Key.font : textView.font as Any]))
textView.attributedText = attributedText
textDidChange?(textView)
shouldReturn = false
}
textView.selectedRange = _NSRange(location: selectedRange.location + text.count, length: 0)
textViewDidChange(textView)
}
return shouldReturn
}
This way I have the control to update the link in between the word and it doesn't extend afterward to a new word.

Not able to get underline for url and detect space after the url

I have written this code to detect a url...i.e. if I typed a url into my textview then it should detect that url and show a message. This code is written in textview shouldChangeTextIn range....
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if textView == contributeTextView {
let detector = try! NSDataDetector(types: NSTextCheckingResult.CheckingType.link.rawValue)
let matches = detector.matches(in: textView.text, options: [], range: NSRange(location: 0, length: textView.text.utf16.count))
for match in matches {
guard let range = Range(match.range, in: textView.text) else { continue }
url = textView.text[range]
print("THIS IS THE URL:-> \(url)")
}
return true
}
//CODE TO GET URL AS UNDERLINED
.......
return true
}
This code is working fine. But if It is a url, then I also want an underline to be shown below that url to indicate it’s a url. For that I have written the below code...
//CODE TO GET URL AS UNDERLINED
let attributedString = NSMutableAttributedString(string: textView.text)
let range = (textView.text as NSString).range(of: "\(url)")
attributedString.addAttribute(.link, value: "\(url)", range: range)
textView.attributedText = attributedString
But it’s not working. What has gone wrong...?
Also if I write the entire url and press space, where can I know that space has been pressed...?
The reason your code is not working because the value for addAttribute is wrong. Here is the correct snippet.
let attributedString = NSMutableAttributedString(string: textView.text)
let range = (textView.text as NSString).range(of: "\(url)")
attributedString.addAttribute(.link, value: 1, range: range)
attributedString.addAttribute(.underlineStyle, value: 1, range: range)
attributedString.addAttribute(.underlineColor, value: UIColor.blue, range: range)
textView.attributedText = attributedString

[NSConcreteTextStorage attribute:atIndex:effectiveRange:]: Range or index out of bounds error in NSMutableAttributedString

I am trying to create an attributed string in which there is a Link appended at the end of the string :
func addMoreAndLessFunctionality (textView:UITextView){
if textView.text.characters.count >= 120
{
let lengthOfString = 255
var abc : String = (somelongStringInitiallyAvailable as NSString).substringWithRange(NSRange(location: 0, length: lengthOfString))
abc += " ...More"
textView.text = abc
let attribs = [NSForegroundColorAttributeName: StyleKit.appDescriptionColor, NSFontAttributeName: StyleKit.appDescriptionFont]
let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: abc, attributes: attribs)
attributedString.addAttribute(NSLinkAttributeName, value: " ...More", range: NSRange(location:255, length: 8))
textView.attributedText = attributedString
textView.textContainer.maximumNumberOfLines = 3;
}
}
What I am trying to achieve here is that if characters in text view's text more than 255, it should show the "...More" link in text view which is tapable, on tap I am already able get the delegate "shouldInteractWithUrl" called, where I am increasing the no of lines in text view, and also change the text of link to "...Less". On tap of Less I again call this same method so that it can truncate again. :
func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool
{
print("textview should interact with URl")
let textTapped = textView.text[textView.text.startIndex.advancedBy(characterRange.location)..<textView.text.endIndex]
if textTapped == " ...More"{
var abc : String = (self.contentDetailItemManaged?.whatsnewDesc)!
abc += " ...Less"
textView.textContainer.maximumNumberOfLines = 0
let attribs = [NSForegroundColorAttributeName: StyleKit.appDescriptionColor, NSFontAttributeName: StyleKit.appDescriptionFont]
let attributedString: NSMutableAttributedString = NSMutableAttributedString(string: abc, attributes: attribs)
attributedString.addAttribute(NSLinkAttributeName, value: " ...Less", range: NSRange(location:abc.characters.count-8 , length: 8))
textView.attributedText = attributedString
}
else if textTapped == " ...Less"{
textView.attributedText = nil
textView.text = somelongStringInitiallyAvailable
self.addMoreAndLessFunctionality(textView)
}
return true
}
Now the problem, when the 1st method is called for the first time (it is called after I have set the textview's text), it works fine, but after clicking on "...More", textview expands normally, "...More" changes to " ...Less". And when " ...Less" is tapped, It crashes and exception occurs :
[NSConcreteTextStorage attribute:atIndex:effectiveRange:]: Range or index out of bounds'
any help would be greatly appreciated. (Also string "...More" has a space in beginning, so total 8 chars, i may have missed this space while typing above code)
Thanks :)
return false in func textView(textView: UITextView, shouldInteractWithURL URL: NSURL, inRange characterRange: NSRange) -> Bool

Trying to color all occurrences of a textview string

I'm trying to color all occurrences (not just the first) of a textview string however when I get to the looping I get a cannot invoke rangeOfString argument.
I checked the rangeOfString:options:range documentation on Swift 2 and it looked pretty similar. I'm not really sure what I'm doing wrong.
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
func textView(textView: UITextView){
if textView == self.textView {
let nsString = textView.text as NSString
let stringLength = textView.text.characters.count
var range = NSRange(location: 0, length: textView.text.characters.count)
let searchString = textView.text
let text = NSMutableAttributedString(string: textView.text)
text.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: NSMakeRange(0, stringLength))
textView.attributedText = text
while(range.location != NSNotFound) {
range = (textView.text as NSString).rangeOfString(searchString, options: NSStringCompareOptions, range: range)
text.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: nsString.rangeOfString("hello"))
}
}
}
}
It looks like you try to highlight user input from search bar. Here how I do so:
func highlightedText(text: NSString, inText: NSString, var withColor: UIColor?) -> NSMutableAttributedString {
var attributedString = NSMutableAttributedString(string:inText as String)
let range = (inText.lowercaseString as NSString).rangeOfString(text.lowercaseString as String)
if withColor == nil {
if let color = globalTint() {
withColor = color
} else {
withColor = UIColor.redColor()
}
}
attributedString.addAttribute(NSForegroundColorAttributeName, value: withColor! , range: range)
return attributedString
}
...
textView.attributedText = highlightedText("string", inText: titleText, color: nil)

NSAttributed string not working in swift

I am trying to use attributed string to customize a label but getting weird errors in swift.
func redBlackSubstring(substring: String) {
self.font = UIFont(name: "HelveticaNeue", size: 12.0)
var theRange: Range<String.Index>! = self.text?.rangeOfString(substring)
var attributedString = NSMutableAttributedString(string: self.text!)
let attribute = [NSForegroundColorAttributeName as NSString: UIColor.blackColor()]
attributedString.setAttributes(attribute, range: self.text?.rangeOfString(substring))
self.attributedText = attributedString
}
I have also tried using the below code
func redBlackSubstring(substring: String) {
self.font = UIFont(name: "HelveticaNeue", size: 12.0)
var theRange: Range<String.Index>! = self.text?.rangeOfString(substring)
var attributedString = NSMutableAttributedString(string: self.text!)
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor(), range: self.text?.rangeOfString(substring))
self.attributedText = attributedString
}
In both the cases, getting weird errors "Can not invoke 'setAttributes' with an argument list of type '([NSString : ..."
I have tried most of the solutions available on stack overflow and many other tutorials but, all of them resulting in such errors.
The main culprit is Range. Use NSRange instead of Range. One more thing to note here is, simply converting self.text to NSString will give you error for forced unwrapping.
Thus, use "self.text! as NSString" instead.
func redBlackSubstring(substring: String) {
self.font = UIFont(name: "HelveticaNeue", size: 12.0)!
var range: NSRange = (self.text! as NSString).rangeOfString(substring)
var attributedString = NSMutableAttributedString(string: self.text)
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.blackColor(), range: range)
self.attributedText = attributedString
}
Your problem is that your passing a swift Range where a NSRange is expected.
The solution to get a valid NSRange from your string is to convert it to NSString first. See NSAttributedString takes an NSRange while I'm using a Swift String that uses Range.
So something like this should work:
let nsText = self.text as NSString
let theRange = nsText.rangeOfString(substring) // this is a NSRange, not Range
// ... snip ...
attributedString.setAttributes(attribute, range: theRange)
Try using NSRange instead of Range:
func redBlackSubstring(substring: String) {
self.font = UIFont(name: "HelveticaNeue", size: 12.0)!
var range: NSRange = (self.text as NSString).rangeOfString(substring)
var attributedString = NSMutableAttributedString(string: self.text)
attributedString.addAttribute(NSForegroundColorAttributeName, value: UIColor.blackColor(), range: range)
self.attributedText = attributedString
}

Resources