I am using two NSMutableAttributedString and making one NSMutableAttributedString from that two. I want to limit the different number of max lines for both attributedString. I searched a lot but nothing found working and a good option.
let linkTitleAttributed = NSMutableAttributedString(string: message.getLinkTitle() ?? "" , attributes: [NSFontAttributeName: UIFont.systemFontOfSize(19.0)])
//linkTitleAttributed should be maximum 2 lines.
let linkDescAttributed = NSAttributedString(string: message.getLinkDescription() ?? "", attributes: [NSFontAttributeName: UIFont.systemFontOfSize(15.0)])
//linkDescAttributed should be maximum 5 lines.
let finalAttributed = NSMutableAttributedString()
final.append(linkTitleAttributed)
final.append(linkDescAttributed)
If the text is more then given a number of lines then it should be ending with 'Lorem Ipsum is simply dummy text...'
One solution is in my mind(set text to textview independenty and get visible range ) but I am looking for a better one.
Thanks.
You have to process the strings for your title and description and then create a new
NSAttributedString. Something like:
func limitNumLines(_ msg: String, max: Int) -> String{
if max <= 0 { return "" }
let lines = msg.components(separatedBy: "\n")
var output = ""
for i in 0..<max {
output += lines[i] + "\n"
}
return output
}
let titleMessage = "LINE1\nLINE2\nLINE3\nLINE4"
let descMessage = "line1\nline2\nline3\nline4\nline5\nline6"
//linkTitleAttributed (that is, message) should be maximum 2 lines.
let title = limitNumLines(titleMessage, max: 2)
let linkTitleAttributed = NSMutableAttributedString(string: title , attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 19.0)])
//linkDescAttributed should be maximum 5 lines.
let desc = limitNumLines(descMessage, max: 5)
let linkDescAttributed = NSAttributedString(string: desc , attributes: [NSAttributedString.Key.font: UIFont.systemFont(ofSize: 15.0)])
let finalAttributed = NSMutableAttributedString()
finalAttributed.append(linkTitleAttributed)
finalAttributed.append(linkDescAttributed)
Related
I want to have one string with different paragraphs styles. The goal is to customize the paragraph/line spacing for different parts of the string. I researched and found this answer but since I added multiple new line characters, not sure how to implement.
Design
This is my goal in terms of layout:
Code
This is the code I have which makes it look like the left image above. Please see the comments Not working in the code. Notice how the spacing is set for the main string, but the other strings can't then set their own custom spacing:
struct BookModel: Codable {
let main: String
let detail: String
}
func createAttributedString(for model: BookModel) -> NSMutableAttributedString {
let fullString = NSMutableAttributedString()
let mainString = NSMutableAttributedString(string: model.main)
let mainStringParagraphStyle = NSMutableParagraphStyle()
mainStringParagraphStyle.alignment = .center
mainStringParagraphStyle.lineSpacing = 10
mainStringParagraphStyle.paragraphSpacing = 30
let mainStringAttributes: [NSAttributedString.Key: Any] = [.paragraphStyle: mainStringParagraphStyle]
let spacingAfterQuote = NSMutableAttributedString(string: "\n")
let lineImageAttachment = NSTextAttachment(image: #imageLiteral(resourceName: "line-image"))
let lineImageString = NSMutableAttributedString(attachment: lineImageAttachment)
let lineParagraphStyle = NSMutableParagraphStyle()
lineParagraphStyle.alignment = .left
lineParagraphStyle.lineSpacing = 0 // Not working - instead of 0 it is 30 from `mainStringParagraphStyle`
lineParagraphStyle.paragraphSpacing = 0 // Not working - instead of 0 it is 30 from `mainStringParagraphStyle`
let lineAttributes: [NSAttributedString.Key: Any] = [.paragraphStyle: lineParagraphStyle]
let spacingAfterSeparator = NSMutableAttributedString(string: "\n")
let spacingAfterSeparatorParagraphStyle = NSMutableParagraphStyle()
spacingAfterSeparatorParagraphStyle.alignment = .left
spacingAfterSeparatorParagraphStyle.lineSpacing = 0 // Not working - instead of 0 it is 30 from `mainStringParagraphStyle`
spacingAfterSeparatorParagraphStyle.paragraphSpacing = 5 // Not working - instead of 5 it is 30 from `mainStringParagraphStyle`
let spacingAfterSeparatorAttributes: [NSAttributedString.Key: Any] = [.paragraphStyle: spacingAfterSeparatorParagraphStyle]
let detailString = NSMutableAttributedString(string: model.detail)
let detailStringAttributes: [NSAttributedString.Key: Any] = [.font: UIFont.systemFont(ofSize: 20)]
fullString.append(mainString)
fullString.append(spacingAfterQuote)
fullString.append(lineImageString)
fullString.append(spacingAfterSeparator)
fullString.append(detailString)
fullString.addAttributes(mainStringAttributes, range: fullString.mutableString.range(of: model.main))
fullString.addAttributes(lineAttributes, range: fullString.mutableString.range(of: lineImageString.string))
fullString.addAttributes(spacingAfterSeparatorAttributes, range: fullString.mutableString.range(of: spacingAfterSeparator.string))
fullString.addAttributes(detailStringAttributes, range: fullString.mutableString.range(of: model.detail))
return fullString
}
Any thoughts on how to achieve the image on the right?
Question Update 1
The code below is working! There is only one slight problem. When I add lineSpacing, there is extra space at the end of the last line in main string. Notice that I have this set to zero: mainStringParagraphStyle.paragraphSpacing = 0, but there is still space at the end because mainStringParagraphStyle.lineSpacing = 60.
The reason I ask this is to have more fine grain control of spacing. For example, have a perfect number between the line image and main string. Any thoughts on this?
I put code and picture below:
Code:
func createAttributedString(for model: BookModel) -> NSMutableAttributedString {
let fullString = NSMutableAttributedString()
let mainStringParagraphStyle = NSMutableParagraphStyle()
mainStringParagraphStyle.alignment = .center
mainStringParagraphStyle.paragraphSpacing = 0 // The space after the end of the paragraph
mainStringParagraphStyle.lineSpacing = 60 // NOTE: This controls the spacing after the last line instead of just `paragraphSpacing`
let mainString = NSAttributedString(string: "\(model.main)\n",
attributes: [.paragraphStyle: mainStringParagraphStyle, .font: UIFont.systemFont(ofSize: 24)])
let lineImageStringParagraphStyle = NSMutableParagraphStyle()
lineImageStringParagraphStyle.alignment = .center
let lineImageAttachment = NSTextAttachment(image: #imageLiteral(resourceName: "line-view"))
let lineImageString = NSMutableAttributedString(attachment: lineImageAttachment)
lineImageString.addAttribute(.paragraphStyle, value: lineImageStringParagraphStyle, range: NSRange(location: 0, length: lineImageString.length))
let detailStringParagraphStyle = NSMutableParagraphStyle()
detailStringParagraphStyle.alignment = .center
detailStringParagraphStyle.paragraphSpacingBefore = 5 // The distance between the paragraph’s top and the beginning of its text content
detailStringParagraphStyle.lineSpacing = 0
let detailString = NSAttributedString(string: "\n\(model.detail)",
attributes: [.paragraphStyle: detailStringParagraphStyle, .font: UIFont.systemFont(ofSize: 12)])
fullString.append(mainString)
fullString.append(lineImageString)
fullString.append(detailString)
return fullString
}
Updated answer:
Here's a new example. I set the spacing at the top and at the bottom of the paragraph with the image. This allows line breaks to be used in model.main and model.detail if needed. Also, instead of lineSpacing, I used lineHeightMultiple. This parameter affects the indentation between lines without affecting the last line:
func createAttributedString(for model: BookModel) -> NSAttributedString {
let fullString = NSMutableAttributedString()
let mainStringParagraphStyle = NSMutableParagraphStyle()
mainStringParagraphStyle.alignment = .center
mainStringParagraphStyle.lineHeightMultiple = 2 // Note that this is a multiplier, not a value in points
let mainString = NSAttributedString(string: "\(model.main)\n", attributes: [.paragraphStyle: mainStringParagraphStyle, .font: UIFont.systemFont(ofSize: 24)])
let lineImageStringParagraphStyle = NSMutableParagraphStyle()
lineImageStringParagraphStyle.alignment = .center
lineImageStringParagraphStyle.paragraphSpacingBefore = 10 // The space before image
lineImageStringParagraphStyle.paragraphSpacing = 20 // The space after image
let lineImageAttachment = NSTextAttachment(image: #imageLiteral(resourceName: "line-image"))
let lineImageString = NSMutableAttributedString(attachment: lineImageAttachment)
lineImageString.addAttribute(.paragraphStyle, value: lineImageStringParagraphStyle, range: NSRange(location: 0, length: lineImageString.length))
let detailStringParagraphStyle = NSMutableParagraphStyle()
detailStringParagraphStyle.alignment = .center
let detailString = NSAttributedString(string: "\n\(model.detail)", attributes: [.paragraphStyle: detailStringParagraphStyle, .font: UIFont.systemFont(ofSize: 12)])
fullString.append(mainString)
fullString.append(lineImageString)
fullString.append(detailString)
return fullString
}
Also have a look at my library StringEx. It allows you to create a NSAttributedString from the template and apply styles without having to write a ton of code:
import StringEx
...
func createAttributedString(for model: BookModel) -> NSAttributedString {
let pattern = "<main />\n<image />\n<detail />"
let ex = pattern.ex
ex[.tag("main")]
.insert(model.main)
.style([
.aligment(.center),
.lineHeightMultiple(2),
.font(.systemFont(ofSize: 24))
])
let lineImageAttachment = NSTextAttachment(image: #imageLiteral(resourceName: "line-image"))
let lineImageString = NSAttributedString(attachment: lineImageAttachment)
ex[.tag("image")]
.insert(lineImageString)
.style([
.aligment(.center),
.paragraphSpacingBefore(10),
.paragraphSpacing(20)
])
ex[.tag("detail")]
.insert(model.detail)
.style([
.aligment(.center),
.font(.systemFont(ofSize: 12))
])
return ex.attributedString
}
Old answer:
I think you can just set the spacing at the end of the first paragraph (main string) and the spacing at the beginning of the last paragraph (detail string):
func createAttributedString(for model: BookModel) -> NSMutableAttributedString {
let fullString = NSMutableAttributedString()
let mainStringParagraphStyle = NSMutableParagraphStyle()
mainStringParagraphStyle.alignment = .center
mainStringParagraphStyle.paragraphSpacing = 30 // The space after the end of the paragraph
let mainString = NSAttributedString(string: "\(model.main)\n", attributes: [.paragraphStyle: mainStringParagraphStyle])
let lineImageStringParagraphStyle = NSMutableParagraphStyle()
lineImageStringParagraphStyle.alignment = .center
let lineImageAttachment = NSTextAttachment(image: #imageLiteral(resourceName: "line-image"))
let lineImageString = NSMutableAttributedString(attachment: lineImageAttachment)
lineImageString.addAttribute(.paragraphStyle, value: lineImageStringParagraphStyle, range: NSRange(location: 0, length: lineImageString.length))
let detailStringParagraphStyle = NSMutableParagraphStyle()
detailStringParagraphStyle.alignment = .center
detailStringParagraphStyle.paragraphSpacingBefore = 5 // The distance between the paragraph’s top and the beginning of its text content
let detailString = NSAttributedString(string: "\n\(model.detail)", attributes: [.paragraphStyle: detailStringParagraphStyle])
fullString.append(mainString)
fullString.append(lineImageString)
fullString.append(detailString)
return fullString
}
I have a label where I wish to show some words on line 1 and some words on line 2. I was able to achieve it by putting "\n" between the strings. Words on line 2 is an attributed string with background color, however the background color starts on line one itself.
Is there a way, I can get background color just on line 2?
Below is the code:
let attributedString = NSMutableAttributedString(string: "Line 1 Text" ?? "")
let attributesForNonSelectedRow = [NSAttributedString.Key.font:UIFont.systemFont(ofSize: 14, weight: UIFont.Weight.semibold),NSAttributedString.Key.foregroundColor:UIColor(rgb: 0x707070),NSAttributedString.Key.backgroundColor:UIColor(rgb: 0xE5E5E5)]
let myTitle = NSAttributedString(string: " \n Line 2 Text", attributes: attributesForNonSelectedRow)
attributedString.append(myTitle)
searchTitleLabel.lineBreakMode = .byWordWrapping
searchTitleLabel.numberOfLines = 0
searchTitleLabel.attributedText = attributedString
Simple solution: - Just add /n in the line one string - it will solve the issue
let attributedString = NSMutableAttributedString(string: "Line 1 Text\n" ?? "")
let attributesForNonSelectedRow = [NSAttributedString.Key.font:UIFont.systemFont(ofSize: 14, weight: UIFont.Weight.semibold),NSAttributedString.Key.foregroundColor:UIColor(rgb: 0x707070),NSAttributedString.Key.backgroundColor:UIColor(rgb: 0xE5E5E5)]
let myTitle = NSAttributedString(string: "Line 2 Text", attributes: attributesForNonSelectedRow)
attributedString.append(myTitle)
searchTitleLabel.lineBreakMode = .byWordWrapping
searchTitleLabel.numberOfLines = 0
searchTitleLabel.attributedText = attributedString
Solution via Constraints:
Why don't you just try to set the constraints for the label by providing Leading, Top and Height constraints, you can achieve it..
I wrote this question. All I had to do was put "\n" at the back of line 1 instead of in the front of line 2 and that fixed the background color issue.
let attributedString = NSMutableAttributedString(string: "Line 1 Text \n" ?? "")
let attributesForNonSelectedRow = [NSAttributedString.Key.font:UIFont.systemFont(ofSize: 14, weight: UIFont.Weight.semibold),NSAttributedString.Key.foregroundColor:UIColor(rgb: 0x707070),NSAttributedString.Key.backgroundColor:UIColor(rgb: 0xE5E5E5)]
let myTitle = NSAttributedString(string: "Line 2 Text", attributes: attributesForNonSelectedRow)
attributedString.append(myTitle)
searchTitleLabel.lineBreakMode = .byWordWrapping
searchTitleLabel.numberOfLines = 0
searchTitleLabel.attributedText = attributedString
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
I am trying to display a score with some text. The score is displayed in the middle of a sentence, and I want the font to be bigger for the score than the rest of the text.
My code is as follows:
let fontSizeAttribute = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 43)]
let myString = String(describing: Int(finalScore!.rounded(toPlaces: 0)))
let attributedString = NSAttributedString(string: myString, attributes: fontSizeAttribute)
scoreLabel.text = "Your score is \(attributedString)%, which is much higher than most people."
I can't see anything wrong with this implementation, but when I run it, it says, "Your score is 9{ NSFont = "UITCFont: 0x7f815...
I feel like I'm doing something stupid, but can't figure out what it is. Any help would be appreciated!
Please check :
let fontSizeAttribute = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 43)]
let myString = String(describing: Int(finalScore!.rounded(toPlaces: 0)))
let partOne = NSMutableAttributedString(string: "Your ")
let partTwo = NSMutableAttributedString(string: myString, attributes: fontSizeAttribute)
let partThree = NSMutableAttributedString(string: "%, which is much higher than most people.")
let attributedString = NSMutableAttributedString()
attributedString.append(partOne)
attributedString.append(partTwo)
attributedString.append(partThree)
scoreLabel.attributedText = attributedString
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)
}
}