Error when adding a string to a NSMutableAttributedString - ios

I have this function for adding a bullet to a textView
let bullet: String = " ● "
func setAttributedValueForBullets(bullet: String, positionWhereTheCursorIs: Int?) {
var textRange = selectedRange
let selectedText = NSMutableAttributedString(attributedString: attributedText)
if let line = positionWhereTheCursorIs {
textRange.location = line
}
selectedText.mutableString.replaceCharacters(in: textRange, with: bullet)
let paragraphStyle = createParagraphAttribute()
selectedText.addAttributes([NSParagraphStyleAttributeName: paragraphStyle], range: NSMakeRange(textRange.location, bullet.length))
self.attributedText = selectedText
self.selectedRange = textRange
}
and it works when inserting a bullet to a paragraph with just one line like this
but when I add it to a paragraph with more than one line this happen
I want it to look like in the first image, without that space in the bullet and the begining of text
I have also tried to use
selectedText.insert(bullet, at: textRange.location)
instead of
selectedText.addAttributes([NSParagraphStyleAttributeName: paragraphStyle], range: NSMakeRange(textRange.location, bullet.length))

This is essentially happening because of the space between the bullet point and the long description. If the description is too long, it will, by normal behavior, go on to it's own line, wrapping the rest of the string.
The easiest fix for this, is to use a non-breaking unicode space character (u00A0):
let bullet = "●\u{00A0}"
let string = "thisisaveryveryveryveryveryveryveryveryveryverylongstring"
textView.text = bullet + string
This will result in the expected behavior, where the very long string will not break into it's own line, as it will be connected to the preceding bullet point:
Also, if the space between the bullet and the string is too small, simply string together multiple non-breaking spaces:
let bullet = "●\u{00A0}\u{00A0}"

func attributeString() -> NSAttributedString{
let attribute = NSMutableAttributedString(string: "●DESCRIPTION\n", attributes: [NSFontAttributeName : UIFont.systemFont(ofSize: 14)])
let style = NSMutableParagraphStyle()
style.lineSpacing = 10
let range = NSMakeRange(0, attribute.string.characters.count)
attribute.addAttribute(NSParagraphStyleAttributeName, value: style, range: range)
return attribute
}

Related

Not able to give separate color for multiple words

I'm having a certain sentence. I have to give black color to four words in that sentence.
This is how I have attempted that...
In viewDidLoad,
rangeArray = ["Knowledge","Events","Community","Offers"]
for text in rangeArray {
let range = (bottomTextLabel.text! as NSString).range(of: text)
let attribute = NSMutableAttributedString.init(string: bottomTextLabel.text!)
attribute.addAttribute(NSAttributedString.Key.foregroundColor, value: UIColor.black , range: range)
self.bottomTextLabel.attributedText = attribute
}
But with this code, instead of having black color for all 4 words, I get only the word 'Offers' in black color. What am I doing wrong...?
In your code, you are updating the self.bottomTextLabel.attributedText for each run of the for loop.
Instead of that you must
create an NSMutableAttributedString using the sentence,
add all the relevant attributes as per your rangeArray and
then at last set that attrStr it as attributedText of bottomTextLabel.
Here is what I mean to say,
if let sentence = bottomTextLabel.text {
let rangeArray = ["Knowledge","Events","Community","Offers"]
let attrStr = NSMutableAttributedString(string: sentence)
rangeArray.forEach {
let range = (sentence as NSString).range(of: $0)
attrStr.addAttribute(.foregroundColor, value: UIColor.black, range: range)
}
self.bottomTextLabel.attributedText = attrStr
}

How to not have an "expanded" textview?

I am encountering a small problem that is that my label text is expanded as you can see on the picture below:
I would like to know how to get normal text please.
If you know how to do that, please let me know.
Here is the code :
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
appDelegate.updateUserActivityOnApp(isFirstConnection: false)
//print(self.view.frame.size.height - (self.extraPhoto1.frame.size.height + self.extraPhoto1.frame.origin.y))
if self.view.frame.size.height - (self.extraPhoto1.frame.size.height + self.extraPhoto1.frame.origin.y) >= 140 {
descriptionTopSpaceConstraint.constant = 20
self.view.layoutIfNeeded()
}
profilePicture.image = nil
descriptionTxtView.isEditable = false
descriptionTxtView.text! = ""
extraPhoto1.image = nil
extraPhoto2.image = nil
extraPhoto3.image = nil
usernameCountryLbl.text! = "Loading..."
self.navigationItem.setHidesBackButton(true, animated:true)
var attrStr = NSMutableAttributedString(string: "this is some very long test string for justification. this is some very long test string for justification. this is some very long test string for justification. this is some very long test string for justification")
var paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = NSTextAlignment.justified
paragraphStyle.hyphenationFactor = 1
attrStr.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, attrStr.length))
attrStr.addAttribute(NSAttributedString.Key.kern, value: -0.1, range: NSMakeRange(0, attrStr.length))
self.descriptionTxtView.attributedText = attrStr // label is the UILabel where you want the text to be displayed
self.descriptionTxtView.sizeToFit()
}
Thank you very much
textAligment.natural as #Rob mentioned is one of the options in case you don't care about your text being justified. Otherwise the only way is to use NSMutableAttributedString and hyphenationFactor. In case you need it to be justified you can do something like this:
var attrStr = NSMutableAttributedString(string: "this is some very long test string for justification. this is some very long test string for justification. this is some very long test string for justification. this is some very long test string for justification")
var paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = NSTextAlignment.justified
paragraphStyle.hyphenationFactor = 1
attrStr.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, attrStr.length))
attrStr.addAttribute(NSAttributedString.Key.kern, value: -0.1, range: NSMakeRange(0, attrStr.length))
label.attributedText = attrStr // label is the UILabel where you want the text to be displayed
label.sizeToFit()
label.numberOfLines = 0
Set your text view's textAlignment to .natural instead of .justified.

Bulleted list in Swift - iOS

I need to add bulleted text to textView in iOS app. I am looking at this link and this one and following their ideas. This is my code:
let paragraph = NSMutableParagraphStyle()
paragraph.firstLineHeadIndent = 15
paragraph.headIndent = 15
attributes = [
NSAttributedStringKey.paragraphStyle: paragraph
]
attributedString = NSAttributedString(string: "\u{2022} Some text some text some text some text some text some text", attributes: attributes)
finalText.append(attributedString)
What I need is to get the text indented with the start of the text above. Like it is in the picture:
What I get is the text indented with the starting point of the bullet.
Remove paragraph.firstLineHeadIndent = 15 from code...
let paragraph = NSMutableParagraphStyle()
paragraph.headIndent = 15
attributes = [
NSAttributedStringKey.paragraphStyle: paragraph
]
attributedString = NSAttributedString(string: "\u{2022} Some text some text some text some text some text some text", attributes: attributes)
finalText.append(attributedString)
Please refer my sample code and screenshot
let style = NSMutableParagraphStyle()
style.alignment = .left
style.headIndent = 20
let title = NSMutableAttributedString(string: "\u{2022} I need to add bulleted text to textView in iOS app. I am looking at this link and this one and following their ideas. This is my code:", attributes: [NSAttributedStringKey.paragraphStyle: style,NSAttributedStringKey.foregroundColor:UIColor.blue])
let titleStr = NSMutableAttributedString(string: "\n\n\u{2022} I need to add bulleted text to textView in iOS app. I am looking at this link and this one and following their ideas. This is my code:", attributes: [NSAttributedStringKey.paragraphStyle: style,NSAttributedStringKey.foregroundColor:UIColor.blue])
title.append(titleStr)
titleLabel.attributedText = title
I faced same problem with textView i used custom indent & it working fine-
#IBOutlet var bulletTextView: UITextView!
override func viewDidLoad() {
let bullet1 = "This is a small string,This is a small string,This is a small string,This is a small string,This is a small string,This is a small string,This is a small string"
let bullet2 = "This is more of medium string with a few more words etc."
let bullet3 = "Well this is certainly a longer string, with many more words than either of the previuos two strings"
strings = [bullet1, bullet2, bullet3]
let fullAttributedString = NSMutableAttributedString()
for string: String in strings {
let attributesDictionary:[NSAttributedStringKey:Any] = [NSAttributedStringKey.font : bulletTextView.font,NSAttributedStringKey.foregroundColor : UIColor.red]
let bulletPoint: String = "\u{2022}"
//let formattedString: String = "\(bulletPoint) \(string)\n"
let attributedString = NSMutableAttributedString(string: bulletPoint, attributes: attributesDictionary)
attributedString.append(NSAttributedString(string: " \(string) \n"))
let indent:CGFloat = 15
let paragraphStyle = createParagraphAttribute(tabStopLocation: indent, defaultTabInterval: indent, firstLineHeadIndent: indent - 10, headIndent: indent)
attributedString.addAttributes([NSAttributedStringKey.paragraphStyle: paragraphStyle], range: NSMakeRange(0, attributedString.length))
fullAttributedString.append(attributedString)
}
bulletTextView.attributedText = fullAttributedString
}
func createParagraphAttribute(tabStopLocation:CGFloat, defaultTabInterval:CGFloat, firstLineHeadIndent:CGFloat, headIndent:CGFloat) -> NSParagraphStyle {
let paragraphStyle: NSMutableParagraphStyle = NSParagraphStyle.default.mutableCopy() as! NSMutableParagraphStyle
let options:[NSTextTab.OptionKey:Any] = [:]
paragraphStyle.tabStops = [NSTextTab(textAlignment: .left, location: tabStopLocation, options: options)]
paragraphStyle.defaultTabInterval = defaultTabInterval
paragraphStyle.firstLineHeadIndent = firstLineHeadIndent
paragraphStyle.headIndent = headIndent
return paragraphStyle
}
Output:-
Set paragraph.firstLineHeadIndent to zero. This indents only lines starting with the second one. Currently, you are indenting all lines…
let paragraph = NSMutableParagraphStyle()
// paragraph.firstLineHeadIndent = 15
paragraph.headIndent = 15
To have the headIndent resize with dynamic fonts I'm using this:
private func updateUI() {
let bullet: NSString = "• "
var attributes = [NSAttributedString.Key: Any]()
let paragraph = NSMutableParagraphStyle()
leStackView.subviews.compactMap({ $0 as? UILabel }).forEach {
attributes[.font] = $0.font
paragraph.headIndent = bullet.size(withAttributes: attributes).width
attributes[.paragraphStyle] = paragraph
let text = $0.text ?? ""
$0.attributedText = NSAttributedString(string: text, attributes: attributes)
}
}
The labels for each bullet point are set up in the storyboard with plain text (including the bullet) and dynamic fonts.
I really appreciate the contributions to this thread plus https://bendodson.com/weblog/2018/08/09/bulleted-lists-with-uilabel/
Simple solution:
extension Sequence where Self.Element == String {
func toBulletList(_ bulletIndicator: String = "•",
itemSeparator: String = "\n",
spaceCount: Int = 2) -> String {
let bullet = bulletIndicator + String(repeating: " ", count: spaceCount)
let list = self
.map { bullet + $0 }
.reduce("", { $0 + ($0.isEmpty ? $0 : itemSeparator) + $1 })
return list
}
}
usage:
let items: [String] = [
"one",
"two",
"three"
]
let list = items.toBulletList()
po list ->
• one
• two
• three
I had the same problem, and I finally realized that Label didn't support it. If you want to use bullet-list in the same rows you should use text view

join array items into one string for a label in swift 3

In my app I have an array coming back from an API and currently it is creating a new label for each item and stacking them one on top of another. I would like it to create one label where the array items are in string separated by a bullet. Here is my current working code:
lblLeft.text = ""
if let expertiseCount = helper.expertise {
for i in 0..<expertiseCount.count {
if i >= 0 {
print(expertiseCount[i].name!)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 10
let attrString = NSMutableAttributedString(string: lblLeft.text! + "\(expertiseCount[i].name ?? "")\n")
attrString.addAttribute(NSParagraphStyleAttributeName, value:paragraphStyle, range: NSMakeRange(0, attrString.length))
lblLeft.attributedText = attrString
}
}
}
currently looks like the image on the left, I want it to look like the image on the right.
You don't need to loop over your expertiseCount array. Arrays of strings in Swift have a special method joined(separator:), which should do exactly what you need:
let joinedExpertise = expertiseCount.joined(" • ")
Use a special bullet point character, •, as a separator.
Taking in account that your Expertise class .name is what you need to concat, you can use map and after that as #Andrii answer suggest use join and finally adjusting the paragraphStyle.lineBreakMode, replace your code by this one
if let expertiseCount = helper.expertise {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 10
paragraphStyle.lineBreakMode = .byWordWrapping
let finalString = expertiseCount.map({$0.name}).joined(separator: " • ")
let finalAttributedString = NSMutableAttributedString(string: finalString, attributes: [NSParagraphStyleAttributeName:paragraphStyle])
lblLeft.attributedText = finalAttributedString
}
Hope this helps
I'd change your code like this:
if let expertise = helper.expertise {
let expertises = expertise.joined(" • ") // join the strings with bullet point char
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = 10
paragraphStyle.lineBreakMode = .byWordWrapping // line break by word wrappnig
let attrString = NSMutableAttributedString(string: expertises)
attrString.addAttribute(NSParagraphStyleAttributeName, value: paragraphStyle, range: 0..<attrString.characters.count) // not sure i this range will work, change to fit your needs
lblLeft.attributedText = attrString
}
This code wasn't tested but you got the idea...
Swift 5
if you want to set a single string to a label in loop, you can follow this simple method.
for hash in objSinglePost?.hash_tags ?? [Hashtags].init() {
self.lblHashtags.text = (self.lblHashtags.text ?? "") + " #" + (hash.hash_tags ?? "")
}

Adding color to a word in string using NSMutableAttributedString

I am trying to add color to 2 words in a string. This is the code I am using:
var HighScore:Int = 0
var CurrentScore:Int = 0
let stringOne = "You have managed to score \(CurrentScore). Current record is \(self.HighScore). Play again and give it another try!"
let stringTwo = "\(CurrentScore)"
let stringThree = "\(HighScore)"
let range1 = (stringOne as NSString).range(of: stringTwo)
let range2 = (stringOne as NSString).range(of: stringThree)
let attributedText = NSMutableAttributedString.init(string: stringOne)
attributedText.addAttribute(NSForegroundColorAttributeName, value: UIColor.init(netHex: 0x00b4ff) , range: range1)
attributedText.addAttribute(NSForegroundColorAttributeName, value: UIColor.init(netHex: 0x00b4ff) , range: range2)
gameOverDescriptionLabel.attributedText = attributedText
The problem I have is that if CurrentScore and HighScore is the same(ex: 2 & 2) the color on the range2 still stays white, but if they are not equal(2 & 1 or 1 & 2) both gets the color I have choosen.
Any suggestions?
Add this to the top or bottom of your .swift file:
extension NSMutableAttributedString {
func bold(_ text:String) -> NSMutableAttributedString {
let attrs:[String:AnyObject] = [NSForegroundColorAttributeName: UIColor.init(netHex: 0x00b4ff)]
let boldString = NSMutableAttributedString(string:"\(text)", attributes:attrs)
self.append(boldString)
return self
}
func normal(_ text:String)->NSMutableAttributedString {
let normal = NSAttributedString(string: text)
self.append(normal)
return self
}
}
To code below is the usage, you can edit it how you'd like, but I have made it so you can easy just copy&paste it to your project:
let formattedString = NSMutableAttributedString()
formattedString
.normal("You have managed to score ")
.bold("\(CurrentScore)")
.normal(". Current record is ")
.bold("\(HighScore)")
.normal(". Play again and give it another try!")
gameOverDescriptionLabel.attributedText = formattedString
If the current score and high score are the same string, searching for the latter finds the former (because the search starts at the beginning).
There are lots of other, better ways to do this.
Perform the second search in a range starting after the result of the first search (range(of:) has a range: parameter, but you are not using it)
Instead of looking for the range of the high score, search for the surrounding boilerplate ("You have managed to score" and so on) and figure out where the numbers must be.
Use NSScanner or regular expressions to find the numerical expressions embedded in the string.
My favorite: mark each of the numbers with an "invisible" attribute and search for that attribute so that you can find the numbers reliably (example here).
A solution without searching for the range would be to create 2 separate NSMutableAttributedString for current score and high score, and then append everything together.
let currentScoreString = NSMutableAttributedString(...)
let highscoreString = NSMutableAttributedString(...)
let finalString = NSMutableAttributedString(string: "You have managed to score ").append(currentScoreString)....
//MARK: forgroundColor
open var foregroundColor: UIColor? {
didSet {
textAttributes[NSForegroundColorAttributeName] = foregroundColor
self.attributedText = NSAttributedString(string: self.text, attributes: textAttributes)
}
}
//MARK: backgroundColor
open var textBackgroundColor: UIColor? {
didSet {
textAttributes[NSBackgroundColorAttributeName] = textBackgroundColor
self.attributedText = NSAttributedString(string: self.text, attributes: textAttributes)
}
}

Resources