iOS: insert attributed string at cursor for UITextView? - ios

UITextView lets you insert plain text at the cursor with the insertText function. Is there a clean way to do so with attributed text?
Is the only approach to split the attributedText property into two parts -- pre-cursor and post-cursor -- then append the new attributed string to the pre-cursor attributed text, followed by appending the post-cursor attributed text?

Insert into a mutable copy of the text view’s attributed text by calling https://developer.apple.com/documentation/foundation/nsmutableattributedstring/1414947-insert.

As advised by #matt, here's a Swift 4.x function:
fileprivate func insertAtTextViewCursor(attributedString: NSAttributedString) {
// Exit if no selected text range
guard let selectedRange = textView.selectedTextRange else {
return
}
// If here, insert <attributedString> at cursor
let cursorIndex = textView.offset(from: textView.beginningOfDocument, to: selectedRange.start)
let mutableAttributedText = NSMutableAttributedString(attributedString: textView.attributedText)
mutableAttributedText.insert(attributedString, at: cursorIndex)
textView.attributedText = mutableAttributedText
}

Related

Get selected text in a UITextView

I have a UITextView, and I want to allow the user to highlight a portion of text and copy it with a button instead of using the default Apple method. The problem is that I can't get the text within the selected range.
Here's what I have:
#IBAction func copyButton(_ sender: Any) {
let selectedRange: UITextRange? = textView.selectedTextRange
selectedText = textView.textInRange(selectedRange)
UIPasteboard.general.string = selectedText
}
But I'm getting
UITextView has no member textInRange
and I'm not sure what I should be using instead.
What is happening is that UITextView method textInRange have been renamed to text(in: Range) since Swift 3. Btw you forgot to add the let keyword in your sentence:
if let range = textView.selectedTextRange {
UIPasteboard.general.string = textView.text(in: range)
}

How to make bold a part of NSMutableString?

I have this code in my hands:
if let text = trimText?.mutableCopy() as? NSMutableString {
text.insertString("\(prefix) ", atIndex: 0)
textStorage.replaceCharactersInRange(range, withString: text as String)
}
When I try to change my text as:
text = attributedTextFunc(text)
where
func attributedTextFunc(str: NSString) -> NSAttributedString {
var attributedString = NSMutableAttributedString(string: str as String, attributes: [NSFontAttributeName:UIFont.systemFontOfSize(15.0)])
let boldFontAttribute = [NSFontAttributeName: UIFont.boldSystemFontOfSize(15.0)]
attributedString.addAttributes(boldFontAttribute, range: str.rangeOfString("More"))
return attributedString
}
and I get this error:
Cannot assign value of type 'NSAttributedString' to type 'NSMutableString'
How can I make it bold?
You cannot assing NSAttributedString to text. It's two different types.
String is not subclassed from NSAttributedString.
You should set:
attributedText = attributedTextFunc(text)
Then if you want to present it on UILabel
label.attributedText = attributedText
UPDATE
Struct String doesn't know anything about UIKit and Bold styles.
NSAttributedString knows about UIKit and contains any text styles you want
UPDATE 2
In your case
ReadMoreTextView.attributedTrimText = attributedText
You cannot reassign text because:
text is constant (let)
text is NSMutableString, but attributedTextFunc return NSAttributedString
You have to store result of attributedTextFunc in variable as a NSAttributeString and set attributeText of UILabel instead of text
if let text = trimText?.mutableCopy() as? NSMutableString {
// ...
let attributeText = attributedTextFunc(text)
someLabel.attributeText = attributeText
}
Use this code and pass your normal String and Bold String (which is needed to be bold).
func attributeStrings(first: String, second : String) -> NSMutableAttributedString{
let myNormalAttributedTitle = NSAttributedString(string: first,
attributes: [NSFontAttributeName : UIFont.boldSystemFontOfSize(15)])
let myAttributedTitle = NSAttributedString(string: second,
attributes: [NSForegroundColorAttributeName : UIColor.blackColor()])
let result = NSMutableAttributedString()
result.appendAttributedString(myNormalAttributedTitle)
result.appendAttributedString(myAttributedTitle)
return result
}
and assign the return value of this function to
someLabel.attributeText = attributeStrings("My Name is", second : "Himanshu")
It is because, your text is type of NSMutableString and your function attributedTextFunc is type of NSString.
That is the problem so just change it from NSString to NSMutableString.

how can i get the fonts attributed

i've a single UIButton to set the text bold and unbold in UITextview
let range = mainTextView.selectedRange
let string = NSMutableAttributedString(attributedString: mainTextView.attributedText)
let attributes = [NSFontAttributeName: UIFont.boldSystemFontOfSize(12)]
string.addAttributes(attributes, range: mainTextView.selectedRange)
mainTextView.attributedText = string
My question is how to unbold the selected text if it is already bold? how to check if the selectedRange is already bold or not?

How to make UITextView detect hashtags?

I know that the UITextView default can detect URL, but how can i make it detect hashtags(#)?
It doesn't needs to detect hashtags while typing, but then viewDidLoad the text is set in the UITextView, so i want to detect hashtags as a color or something.
I have been using ActiveLabel, but that is only for UILabel, and i need the scroll function that the UITextView has.
This should work for you
Create a new swift file with any name(UITextViewHashtagExtension.swift)
Insert this code below:
import UIKit
extension UITextView {
func resolveHashTags(){
// turn string in to NSString
let nsText:NSString = self.text
// this needs to be an array of NSString. String does not work.
let words:[NSString] = nsText.componentsSeparatedByString(" ")
// you can't set the font size in the storyboard anymore, since it gets overridden here.
let attrs = [
NSFontAttributeName : UIFont.systemFontOfSize(17.0)
]
// you can staple URLs onto attributed strings
let attrString = NSMutableAttributedString(string: nsText as String, attributes:attrs)
// tag each word if it has a hashtag
for word in words {
// found a word that is prepended by a hashtag!
// homework for you: implement #mentions here too.
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.
let matchRange:NSRange = nsText.rangeOfString(word as String)
// convert the word from NSString to String
// this allows us to call "dropFirst" to remove the hashtag
var stringifiedWord:String = word as String
// drop the hashtag
stringifiedWord = String(stringifiedWord.characters.dropFirst())
// check to see if the hashtag has numbers.
// ribl is "#1" shouldn't be considered a hashtag.
let digits = NSCharacterSet.decimalDigitCharacterSet()
if let numbersExist = stringifiedWord.rangeOfCharacterFromSet(digits) {
// 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(NSLinkAttributeName, value: "hash:\(stringifiedWord)", range: matchRange)
}
}
}
// we're used to textView.text
// but here we use textView.attributedText
// again, this will also wipe out any fonts and colors from the storyboard,
// so remember to re-add them in the attrs dictionary above
self.attributedText = attrString
}
}
To use this you can do something like this:
self.textView.text = "This is an #example test"
self.textView.resolveHashTags()
Updated for Swift 4.0:
extension UITextView {
func resolveHashTags() {
// turn string in to NSString
let nsText = NSString(string: self.text)
// this needs to be an array of NSString. String does not work.
let words = nsText.components(separatedBy: CharacterSet(charactersIn: "#ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_").inverted)
// you can staple URLs onto attributed strings
let attrString = NSMutableAttributedString()
attrString.setAttributedString(self.attributedText)
// tag each word if it has a hashtag
for word in words {
if word.count < 3 {
continue
}
// found a word that is prepended by a hashtag!
// homework for you: implement #mentions here too.
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.
let matchRange:NSRange = nsText.range(of: word as String)
// 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(NSAttributedStringKey.link, value: "hash:\(stringifiedWord)", range: matchRange)
}
}
}
// we're used to textView.text
// but here we use textView.attributedText
// again, this will also wipe out any fonts and colors from the storyboard,
// so remember to re-add them in the attrs dictionary above
self.attributedText = attrString
}
}
One option would be to use an NSAttributedString, something like this...
func convertHashtags(text:String) -> NSAttributedString {
let attrString = NSMutableAttributedString(string: text)
attrString.beginEditing()
// match all hashtags
do {
// Find all the hashtags in our string
let regex = try NSRegularExpression(pattern: "(?:\\s|^)(#(?:[a-zA-Z].*?|\\d+[a-zA-Z]+.*?))\\b", options: NSRegularExpressionOptions.AnchorsMatchLines)
let results = regex.matchesInString(text,
options: NSMatchingOptions.WithoutAnchoringBounds, range: NSMakeRange(0, text.characters.count))
let array = results.map { (text as NSString).substringWithRange($0.range) }
for hashtag in array {
// get range of the hashtag in the main string
let range = (attrString.string as NSString).rangeOfString(hashtag)
// add a colour to the hashtag
attrString.addAttribute(NSForegroundColorAttributeName, value: UIColor.redColor() , range: range)
}
attrString.endEditing()
}
catch {
attrString.endEditing()
}
return attrString
}
Then assign your attributedText like this...
let myText = "some text with a #hashtag in side of it #itsnoteasy"
self.textView.attributedText = convertHashtags(myText)
For swift 4.0
extension UITextView {
func resolveHashTags() {
// turn string in to NSString
let nsText = NSString(string: self.text)
// this needs to be an array of NSString. String does not work.
let words = nsText.components(separatedBy: CharacterSet(charactersIn: "#ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_").inverted)
// you can staple URLs onto attributed strings
let attrString = NSMutableAttributedString()
attrString.setAttributedString(self.attributedText)
// tag each word if it has a hashtag
for word in words {
if word.count < 3 {
continue
}
// found a word that is prepended by a hashtag!
// homework for you: implement #mentions here too.
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.
let matchRange:NSRange = nsText.range(of: word as String)
// 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(NSAttributedStringKey.link, value: "hash:\(stringifiedWord)", range: matchRange)
}
}
}
// we're used to textView.text
// but here we use textView.attributedText
// again, this will also wipe out any fonts and colors from the storyboard,
// so remember to re-add them in the attrs dictionary above
self.attributedText = attrString
}
}
For Swift 3 +
extension UITextView {
func convertHashtags(text:String) -> NSAttributedString {
let attr = [
NSFontAttributeName : UIFont.systemFont(ofSize: 17.0),
NSForegroundColorAttributeName : clr_golden,
NSLinkAttributeName : "https://Laitkor.com"
] as [String : Any]
let attrString = NSMutableAttributedString(string: text)
attrString.beginEditing()
// match all hashtags
do {
// Find all the hashtags in our string
let regex = try NSRegularExpression(pattern: "(?:\\s|^)(#(?:[a-zA-Z].*?|\\d+[a-zA-Z]+.*?))\\b", options: NSRegularExpression.Options.anchorsMatchLines)
let results = regex.matches(in: text,
options: NSRegularExpression.MatchingOptions.withoutAnchoringBounds, range: NSMakeRange(0, text.characters.count))
let array = results.map { (text as NSString).substring(with: $0.range) }
for hashtag in array {
// get range of the hashtag in the main string
let range = (attrString.string as NSString).range(of: hashtag)
// add a colour to the hashtag
//attrString.addAttribute(NSForegroundColorAttributeName, value: clr_golden , range: range)
attrString.addAttributes(attr, range: range)
}
attrString.endEditing()
}
catch {
attrString.endEditing()
}
return attrString
}
}
Add UITextViewDelegate in your class and Use like this
self.tv_yourTextView.delegate = self
self.tv_yourTextView.attributedText = tv_yourTextView.convertHashtags(text: "This is an #museer test")
delegate funtion
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
print("hastag Selected!")
return true
}
-> Modified #Wez Answer
when to make mention or hashtag or any word, yo can make it by:
func convertRegex(text:String, regex: String) -> NSAttributedString {
let attr:[NSAttributedString.Key:Any] = [
NSAttributedString.Key.font : UIFont.systemFont(ofSize: 17.0),
NSAttributedString.Key.foregroundColor : UIColor.green,
] as [NSAttributedString.Key : Any]
let attrString = NSMutableAttributedString(string: text)
attrString.beginEditing()
do {
// Find all the specific word in our string
let regex = try NSRegularExpression(pattern: "\\s\(regex)\\b" , options: NSRegularExpression.Options.anchorsMatchLines)
let results = regex.matches(in: text,
options: NSRegularExpression.MatchingOptions.withoutAnchoringBounds, range: NSMakeRange(0, text.count))
let array = results.map { (text as NSString).substring(with: $0.range) }
for word in array {
let range = (attrString.string as NSString).range(of: word)
attrString.addAttributes(attr, range: range)
}
attrString.endEditing()
}
catch {
attrString.endEditing()
}
return attrString
}

Can't get attributed string to work in Swift

I'm trying to set some attributes of a string in code, but can't get NSAttributedString to work. This is the function, that's supposed to change the string:
func getAttributedString(string: String) -> NSAttributedString
{
var attrString = NSMutableAttributedString(string: string)
var attrs = [NSFontAttributeName : UIFont.boldSystemFontOfSize(18.0)]
attrString.setAttributes(attrs, range: NSMakeRange(0, attrString.length))
return attrString
}
And this is how I use it:
if (self.product.packageDimensions != nil) {
self.descriptionLabel.text =
self.descriptionLabel.text + self.getAttributedString("Package dimensions:").string +
"\n\(self.product.packageDimensions) \n"
}
But the font stays the same. What am I doing wrong ?
You make 2 errors in your code.
setAttributes needs a Dictionary, not an Array
when you use the string attribute, you will only get a String, all attributes are lost.
To add or change attributes to a attributedString it has to be mutable. You only get a NSMutableString from the attributedText attribute. If you want to change it create a mutable version from it and change it. Then you may set the attributedText to the new mutable version.
If you can give the attributed string as an argument, I will give you an example that works:
func setFontFor(attrString: NSAttributedString) -> NSMutableAttributedString {
var mutableAttrString: NSMutableAttributedString = NSMutableAttributedString(attributedString: attrString)
let headerStart: Int = 0
let headerEnd: Int = 13
mutableAttrString.addAttribute(NSFontAttributeName, value: UIFont.boldSystemFontOfSize(18.0), range: NSMakeRange(headerStart, headerEnd))
return mutableAttrString
}
Usage:
myLabel.attributedText = setFontFor(myLabel.attributedText)
As you can see I used the attributedText property of the UILabel class, it also works for UITextView and others. If you have another label, you can create a new NSAttributedString with the initializer NSAttributedString(normalString) as you already used in the question code.
if (self.product.packageDimensions != nil) {
self.descriptionLabel.attributedText =
self.descriptionLabel.attributedText + self.getAttributedString("Package dimensions:").string +
"\n\(self.product.packageDimensions) \n"
}
You should use the attributedText method

Resources