How to get first two lines text from UILabel - ios

I want to get first two lines text from UIlabel.I have searched a lot but not find any solution.Please tell me how to get first two lines text.

Use this extension check if your label's number of line granter then 2 or whatever you target then apply this:
extension UILabel {
func addTrailing(with trailingText: String, moreText: String, moreTextFont: UIFont, moreTextColor: UIColor) {
let readMoreText: String = trailingText + moreText
let lengthForVisibleString: Int = self.vissibleTextLength()
let mutableString: String = self.text!
let trimmedString: String? = (mutableString as NSString).replacingCharacters(in: NSRange(location: lengthForVisibleString, length: ((self.text?.characters.count)! - lengthForVisibleString)), with: "")
let readMoreLength: Int = (readMoreText.characters.count)
let trimmedForReadMore: String = (trimmedString! as NSString).replacingCharacters(in: NSRange(location: ((trimmedString?.characters.count ?? 0) - readMoreLength), length: readMoreLength), with: "") + trailingText
let answerAttributed = NSMutableAttributedString(string: trimmedForReadMore, attributes: [NSFontAttributeName: self.font])
let readMoreAttributed = NSMutableAttributedString(string: moreText, attributes: [NSFontAttributeName: moreTextFont, NSForegroundColorAttributeName: moreTextColor])
answerAttributed.append(readMoreAttributed)
self.attributedText = answerAttributed
}
func vissibleTextLength() -> Int {
let font: UIFont = self.font
let mode: NSLineBreakMode = self.lineBreakMode
let labelWidth: CGFloat = self.frame.size.width
let labelHeight: CGFloat = self.frame.size.height
let sizeConstraint = CGSize(width: labelWidth, height: CGFloat.greatestFiniteMagnitude)
let attributes: [AnyHashable: Any] = [NSFontAttributeName: font]
let attributedText = NSAttributedString(string: self.text!, attributes: attributes as? [String : Any])
let boundingRect: CGRect = attributedText.boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, context: nil)
if boundingRect.size.height > labelHeight {
var index: Int = 0
var prev: Int = 0
let characterSet = CharacterSet.whitespacesAndNewlines
repeat {
prev = index
if mode == NSLineBreakMode.byCharWrapping {
index += 1
} else {
index = (self.text! as NSString).rangeOfCharacter(from: characterSet, options: [], range: NSRange(location: index + 1, length: self.text!.characters.count - index - 1)).location
}
} while index != NSNotFound && index < self.text!.characters.count && (self.text! as NSString).substring(to: index).boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, attributes: attributes as? [String : Any], context: nil).size.height <= labelHeight
return prev
}
return self.text!.characters.count
}
}
Count Label's number of line:
func countLabelLines(label: UILabel) -> Int {
// Call self.layoutIfNeeded() if your view uses auto layout
self.view.layoutIfNeeded()
let myText = label.text! as NSString
let rect = CGSize(width: label.bounds.width, height: CGFloat.greatestFiniteMagnitude)
let labelSize = myText.boundingRect(with: rect, options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: label.font], context: nil)
return Int(ceil(CGFloat(labelSize.height) / label.font.lineHeight))
}
Example:
if self.countLabelLines(label: lblmedicationDetailText) >= numberOflines{
let readmoreFont = UIFont(name: "Montserrat-Regular", size: 15.0)
let readmoreFontColor = UIColor.init(red: 1.0/255.0, green: 100.0/255.0, blue: 157.0/255.0, alpha: 1.0)
DispatchQueue.main.async {
self.lblmedicationDetailText.addTrailing(with: "... ", moreText: "Read More", moreTextFont: readmoreFont!, moreTextColor: readmoreFontColor)
}
}

let numOfLine = self.numberOfLinesInLabel(self.testingLabel.text!, labelWidth: self.testingLabel.frame.width, labelHeight: self.testingLabel.frame.height, font: self.testingLabel.font)
self.getNumberOfLineDict.setValue(numOfLine, forKey: String(i))
let Lines : Int = getNumberOfLineDict.valueForKey(String(indexPath.row)) as! Int // I did it in tableview. So I used indexPath.row
if Lines > 2
{
ReadmoreBtn.isHidden = false
}
else
{
ReadmoreBtn.isHidden = true
}
To get the number of lines in a text
func numberOfLinesInLabel(yourString: String, labelWidth: CGFloat, labelHeight: CGFloat, font: UIFont) -> Int {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.minimumLineHeight = labelHeight
paragraphStyle.maximumLineHeight = labelHeight
paragraphStyle.lineBreakMode = .ByWordWrapping
let attributes: [String: AnyObject] = [NSFontAttributeName: font, NSParagraphStyleAttributeName: paragraphStyle]
let constrain = CGSizeMake(labelWidth, CGFloat(Float.infinity))
let size = yourString.sizeWithAttributes(attributes)
let stringWidth = size.width
let numberOfLines = ceil(Double(stringWidth/constrain.width))
return Int(numberOfLines)
}

My advice is doing something on UI only.
You can set a UIView(such as it has gradual clear to white view and has a label "View more...") to cover the original label except first two lines.
You can get the first two lines height by label.font.lineHeight * 2.

Related

How do I mirror an NSAttributedString with an icon and text in right-to-left mode

I have a UIButton that holds an NSAttributedString that contains an icon (which is an NSAttributedString itself) followed by a text.
It looks something like this:
I want to make it look like this when the device is configured for an RTL language (e.g. Arabic, Hebrew):
The string is built like this:
var iconText = NSAttributedString(fontName: fontName, fontSize: fontPointSize, fontValue: fontValue, color: color ?? iconColor)
let iconTextRange = NSRange(location: 0, length: iconText.count)
let iconAttrs: [NSAttributedString.Key: Any] = [.font: icon.font(pointSize: pointSize),
.foregroundColor: iconColor]
if !text.isEmpty {
iconText = "\(iconText) \(text)"
}
let attributeString = NSMutableAttributedString(string: iconText, attributes: textAttrs)
attributeString.addAttributes(iconAttrs, range: iconTextRange)
return attributeString
As you can see, first the icon is created using a font, then it's concatenated to the text.
In other parts of the app, I managed to make NSAttributedString's RTL-compliant with this little piece of code:
public extension NSAttributedString {
/// Returns an identical attributed string that'll adjust its direction based on the device's configured language.
func rightToLeftAdjusted() -> NSAttributedString {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.baseWritingDirection = .natural
let attrs: [NSAttributedString.Key: Any] = [.paragraphStyle: paragraphStyle]
let range = NSRange(location: 0, length: length)
let copy = NSMutableAttributedString(attributedString: self)
copy.addAttributes(attrs, range: range)
return copy
}
}
Unfortunately, in this specific case, it doesn't seem to work, the star ALWAYS stays to the left of the text.
Are there any other ways of achieving this?
let preferredLanguage = NSLocale.preferredLanguages[0]
if preferredLanguage == "en" {
button.semanticContentAttribute = .forceLeftToRight
}else if preferredLanguage == "ar" {
button.semanticContentAttribute = .forceRightToLeft
}
The source code is at the below repository link:
https://github.com/ahmetbostanciklioglu/ButtonAlignmentLeftToRightAndRightToLeft.git
Thanks to everyone who helped on the matter.
The solution I got to was, instead of appending the icon as a font, I converted it to a UIImage, then to an NSTextAttachment.
That way, the image flipped to the right side of the text when the app is running in RTL mode.
func addDecorator(icon: DrawbleIcon, pointSize: CGFloat, to text: String, textAttrs: [NSAttributedString.Key: Any]) -> NSAttributedString {
let attributedString = NSMutableAttributedString()
appendIcon(to: attributedString, icon: icon, pointSize: pointSize)
// Append text
if !text.isEmpty {
attributedString.append(NSAttributedString(string: text, attributes: textAttrs))
}
// Adjust text to right-to-left devices
let writingDirection: NSWritingDirection = UIApplication.isRightToLeft ? .rightToLeft : .leftToRight
let leftToRightAttrs: [NSAttributedString.Key: Any] = [.writingDirection: [NSNumber(value: writingDirection.rawValue)]]
attributedString.addAttributes(leftToRightAttrs, range: NSRange(location: 0, length: attributedString.length))
return attributedString
}
private func appendIcon(to attributedString: NSMutableAttributedString, icon: DrawbleIcon, pointSize: CGFloat) {
let iconColor = icon.foreColor
let iconAttrs: [NSAttributedString.Key: Any] = [.font: icon.font(pointSize: pointSize),
.foregroundColor: iconColor]
if let iconText = icon.attributedString(fontPointSize: pointSize, color: nil),
let font = iconText.attributes(at: 0, effectiveRange: nil)[.font] as? UIFont,
let iconImage = NSAttributedString(string: iconText.string, attributes: iconAttrs).image() {
let textAttachment = NSTextAttachment(image: iconImage)
let imageSize = iconImage.size
// Fix image height - without this, the image is higher than the text.
textAttachment.bounds = CGRect(x: CGFloat(0), y: (font.capHeight - imageSize.height) / 2, width: imageSize.width, height: imageSize.height)
attributedString.append(NSAttributedString(attachment: textAttachment))
attributedString.append(NSAttributedString(string: " "))
}
}
private extension String {
/// Generates a `UIImage` instance from this string using a specified
/// attributes and size.
///
/// - Parameters:
/// - attributes: to draw this string with. Default is `nil`.
/// - size: of the image to return.
/// - Returns: a `UIImage` instance from this string using a specified
/// attributes and size, or `nil` if the operation fails.
func image(withAttributes attributes: [NSAttributedString.Key: Any]? = nil, size: CGSize? = nil) -> UIImage? {
let size = size ?? (self as NSString).size(withAttributes: attributes)
return UIGraphicsImageRenderer(size: size).image { _ in
(self as NSString).draw(in: CGRect(origin: .zero, size: size),
withAttributes: attributes)
}
}
}
private extension NSAttributedString {
func image(size: CGSize? = nil) -> UIImage? {
string.image(withAttributes: attributes(at: 0, effectiveRange: nil), size: size)
}
}

Add “…Read More” to the end of UITextView

I need to add "... Read more" to the end of UITextView. I use this solution Add "...Read More" to the end of UILabel. I tried to use it with UILabel - it works perfectly. But in UITextView "... Read more" position isn't stable. I think the problem is in textViewWidth and textViewHeight. When I change their values to -10 it works better but not really. How can I solve it?
My code:
var visibleTextLength: Int {
guard let text = text else { return 0 }
let mode: NSLineBreakMode = textContainer.lineBreakMode
let textViewWidth: CGFloat = frame.size.width //adding - 10 helps a little
let textViewHeight: CGFloat = frame.size.height //adding - 10 helps a little
let sizeConstraint = CGSize(width: textViewWidth, height: CGFloat.greatestFiniteMagnitude)
let attributes: [AnyHashable: Any] = [NSAttributedString.Key.font: font!]
let attributedText = NSAttributedString(string: text, attributes: attributes as? [NSAttributedString.Key: Any])
let boundingRect: CGRect = attributedText.boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, context: nil)
if boundingRect.size.height > textViewHeight {
var index = 0
var prev = 0
let characterSet = CharacterSet.alphanumerics
repeat {
prev = index
if mode == NSLineBreakMode.byCharWrapping {
index += 1
} else {
index = (text as NSString).rangeOfCharacter(from: characterSet, options: [], range: NSRange(location: index + 1, length: text.utf16.count - index - 1)).location
}
} while index != NSNotFound && index < text.utf16.count && (text as NSString).substring(to: index).boundingRect(with: sizeConstraint, options: .usesLineFragmentOrigin, attributes: attributes as? [NSAttributedString.Key: Any], context: nil).size.height <= textViewHeight
return prev
}
return text.utf16.count
}
func addTrailing(with trailingText: String, moreText: String, moreTextFont: UIFont?) {
guard self.visibleTextLength > 0 else { return }
let readMoreText: String = trailingText + moreText
let lengthForVisibleString: Int = self.visibleTextLength
if let text = self.text {
let mutableString: String = text
let trimmedString: String? = (mutableString as NSString).replacingCharacters(in: NSRange(location: lengthForVisibleString, length: text.utf16.count - lengthForVisibleString), with: "")
let readMoreLength: Int = (readMoreText.utf16.count)
guard let safeTrimmedString = trimmedString, safeTrimmedString.utf16.count >= readMoreLength else { return }
let trimmedForReadMore: String = (safeTrimmedString as NSString).replacingCharacters(in: NSRange(location: safeTrimmedString.utf16.count - readMoreLength, length: readMoreLength), with: "") + trailingText
let attributedMoreText = NSMutableAttributedString(string: trimmedForReadMore,
attributes: [NSAttributedString.Key.font: font!])
if let moreTextFont = moreTextFont {
let readMoreAttributed = NSMutableAttributedString(string: moreText, attributes: [NSAttributedString.Key.font: moreTextFont])
attributedMoreText.append(readMoreAttributed)
}
self.attributedText = attributedMoreText
}
}
With - 10 in textViewWidth and textViewHeight

How to remove bottom padding of UILabel with attributedText inside UIStackView

I want to remove the bottom padding of a UILabel with attributedText inside a UIStackview.
I found this solution How to remove the extra padding below an one line UILabel. This works with normal text but not with attributed text.
let textLabel = UILabel()
textLabel.translatesAutoresizingMaskIntoConstraints = false
textLabel.text = "What is a chemical property and how can you observe it?"
textLabel.numberOfLines = 0
textLabel.lineBreakMode = .byWordWrapping
textLabel.backgroundColor = .lightGray
mainStackView.addArrangedSubview(textLabel)
let textLabel2 = UILabel()
textLabel2.translatesAutoresizingMaskIntoConstraints = false
let html = "<html lang=\"en\"><head><meta charset=\"UTF-8\"></head><body><div style=\"font-size:36;\"><p>What is a <em>chemical property</em> and how can you observe it?</p></div></body></html>"
let data = Data(html.utf8)
if let attributedString = try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil) {
let a = NSMutableAttributedString.init(attributedString: attributedString)
let range = (a.string as NSString).range(of: a.string)
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .left
paragraphStyle.firstLineHeadIndent = 0.0
let attributes: [NSAttributedString.Key: Any] = [
.foregroundColor: UIColor.black,
.paragraphStyle: paragraphStyle
]
a.addAttributes(attributes, range: range)
textLabel2.attributedText = a
}
textLabel2.numberOfLines = 0
textLabel2.lineBreakMode = .byWordWrapping
textLabel2.backgroundColor = .yellow
mainStackView.addArrangedSubview(textLabel2)
let textLabel3 = UILabel()
textLabel3.translatesAutoresizingMaskIntoConstraints = false
textLabel3.text = "What is a chemical property and how can you observe it?"
textLabel3.numberOfLines = 0
textLabel3.lineBreakMode = .byWordWrapping
textLabel3.backgroundColor = .lightGray
mainStackView.addArrangedSubview(textLabel3)
A working sample project with this code can be found here: https://github.com/Quobject/testUIlabelInStackviewpadding
The "bottom spacing" is not "spacing" ... your converted <p>...</p> html block adds a newline character at the end of the text.
You can use this extension (found here):
extension NSMutableAttributedString {
func trimmedAttributedString() -> NSAttributedString {
let invertedSet = CharacterSet.whitespacesAndNewlines.inverted
let startRange = string.rangeOfCharacter(from: invertedSet)
let endRange = string.rangeOfCharacter(from: invertedSet, options: .backwards)
guard let startLocation = startRange?.upperBound, let endLocation = endRange?.lowerBound else {
return NSAttributedString(string: string)
}
let location = string.distance(from: string.startIndex, to: startLocation) - 1
let length = string.distance(from: startLocation, to: endLocation) + 2
let range = NSRange(location: location, length: length)
return attributedSubstring(from: range)
}
}
and change this line:
textLabel2.attributedText = a
to:
textLabel2.attributedText = a.trimmedAttributedString()
Result (applying that change to your GitHub repo):

Attributed string do-not take kern value properly

Kerning is not working if i pass -0.36, if i take screen shot from iPhone and comparing with design the string is not matching the length.
func addCharacterSpacing(kernValue: Double = 1.15) {
if let labelText = text, labelText.count > 0 {
let attributedString = NSMutableAttributedString(string: labelText)
attributedString.addAttribute(NSAttributedString.Key.kern, value: kernValue, range: NSRange(location: 0, length: attributedString.length - 1))
attributedText = attributedString
}
}
Finally ended with up by creating #discardableResult func, that exactly match with screen shot from iPhone and comparing with design. Pass parms accordingly.
#discardableResult func applyAttributesWithKerning(_ text: String, font:UIFont, lineSpace: CGFloat, charSpace: CGFloat, color:UIColor) -> NSMutableAttributedString {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpace
var attrs: [NSAttributedString.Key: Any] = [NSAttributedString.Key.paragraphStyle: paragraphStyle]
attrs[NSAttributedString.Key.kern] = charSpace
attrs[NSAttributedString.Key.font] = font
attrs[NSAttributedString.Key.foregroundColor] = color
let boldString = NSMutableAttributedString(string:text, attributes: attrs)
append(boldString)
return self
}

Adjust Elements after Manipulating String into AttributedString - Swift 4

I have the following which allows me to create a bullet list which works really well, however, after the bullet list is created I need to manipulate the outputted Attributed string to have certain elements either in bold or in italics or both.
The function I have is:
#IBOutlet var label: UILabel!
let bulletString = ["String 1","String 2","String 3"]
label.attributedText = label.bulletPoints(stringList: bulletString, font: UIFont.stdFontMediumSeventeen, bullet: "•", lineSpacing: 4, paragraphSpacing: 4, textColor: UIColor.darkGreyColor, bulletColor: UIColor.darkGreyColor)
func bulletPoints(stringList: [String],font: UIFont,bullet: String = "\u{2022}",indentation: CGFloat = 20,lineSpacing: CGFloat = 2,paragraphSpacing: CGFloat = 12,textColor: UIColor = .gray,bulletColor: UIColor = .red) -> NSAttributedString{
let textAttributes: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: textColor]
let bulletAttributes: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: bulletColor]
let paragraphStyle = NSMutableParagraphStyle()
let nonOptions = [NSTextTab.OptionKey: Any]()
paragraphStyle.tabStops = [NSTextTab(textAlignment: .left, location: indentation, options: nonOptions)]
paragraphStyle.defaultTabInterval = indentation
paragraphStyle.lineSpacing = lineSpacing
paragraphStyle.paragraphSpacing = paragraphSpacing
paragraphStyle.headIndent = indentation
let bulletList = NSMutableAttributedString()
for string in stringList {
let formattedString = "\(bullet)\t\(string)\n"
let attributedString = NSMutableAttributedString(string: formattedString)
attributedString.addAttributes(
[NSAttributedStringKey.paragraphStyle : paragraphStyle],
range: NSMakeRange(0, attributedString.length))
attributedString.addAttributes(
textAttributes,
range: NSMakeRange(0, attributedString.length))
let string:NSString = NSString(string: formattedString)
let rangeForBullet:NSRange = string.range(of: bullet)
attributedString.addAttributes(bulletAttributes, range: rangeForBullet)
bulletList.append(attributedString)
}
return bulletList
}
What I am looking for is a way to pass in a boolean to state if the bullet string requires either bold or italic text and if so what the elements of the intital string are that need this treatment.
The bulletPoints function sits in an extension file and works as expected.
Using a model to link the bold/italic to the strings in question, as Neil suggests, helps you in this case. Here's a version which links font traits to the strings for each bullet, then uses those when building up the string.
I've refactored your bulletPoints function as well, to remove the use of ranges and simplify it a little. It could stay in an extension (I assume you have it on UILabel?) but there's no reason for it to, since it returns the string anyway. I've written it as a function which could be used in any class
class ViewController: UIViewController {
#IBOutlet var label: UILabel!
override func viewWillAppear(_ animated: Bool) {
let bulletStrings = [BulletString(string: "String 1", traits: []),
BulletString(string: "String 2", traits: [.traitBold]),
BulletString(string: "String 3", traits: [.traitItalic]),
BulletString(string: "String 4", traits: [.traitBold, .traitItalic])]
label.attributedText = bulletPoints(stringList: bulletStrings, font: UIFont.systemFont(ofSize: 15.0), bullet: "•", lineSpacing: 4, paragraphSpacing: 4, textColor: UIColor.darkGray, bulletColor: UIColor.darkGray)
}
func bulletPoints(stringList: [BulletString],
font: UIFont,
bullet: String = "\u{2022}",
indentation: CGFloat = 20,
lineSpacing: CGFloat = 2,
paragraphSpacing: CGFloat = 12,
textColor: UIColor = .gray,
bulletColor: UIColor = .red) -> NSAttributedString {
let bulletList = NSMutableAttributedString()
for bulletString in stringList {
let attributedString = NSMutableAttributedString(string: "")
let bulletAttributes: [NSAttributedStringKey: Any] = [
.foregroundColor: bulletColor,
.font: font]
attributedString.append(NSAttributedString(string: bullet, attributes: bulletAttributes))
let textAttributes: [NSAttributedStringKey: Any] = [
.font: font.withTraits(traits: bulletString.traits),
.foregroundColor: textColor,
.paragraphStyle : paragraphStyle(indentation: indentation, lineSpacing: lineSpacing, paragraphSpacing: paragraphSpacing)
]
attributedString.append(NSAttributedString(string:"\t\(bulletString.string)\n", attributes: textAttributes))
bulletList.append(attributedString)
}
return bulletList
}
private func paragraphStyle(indentation: CGFloat, lineSpacing: CGFloat, paragraphSpacing: CGFloat) -> NSParagraphStyle {
let style = NSMutableParagraphStyle()
let nonOptions = [NSTextTab.OptionKey: Any]()
style.tabStops = [NSTextTab(textAlignment: .left, location: indentation, options: nonOptions)]
style.defaultTabInterval = indentation
style.lineSpacing = lineSpacing
style.paragraphSpacing = paragraphSpacing
style.headIndent = indentation
return style
}
}
struct BulletString {
let string: String
let traits: UIFontDescriptorSymbolicTraits
}
extension UIFont {
func withTraits(traits:UIFontDescriptorSymbolicTraits...) -> UIFont {
let descriptor = self.fontDescriptor
.withSymbolicTraits(UIFontDescriptorSymbolicTraits(traits))!
return UIFont(descriptor: descriptor, size: 0)
}
}
If you wanted to have the bullets match the style of their strings, i.e. be bolded or italicised, you could just add the attributes in a single pass for each bullet
You can achieve it using multiple fonts and text ranges. If you know the ranges of the text on which you want to apply multiple styles, you can just use fonts. Check the below example.
let fullString = "Bold normal italic"
let attrString = NSMutableAttributedString(string: fullString, attributes: [.font: UIFont.systemFont(ofSize: 18.0)])
let range1 = (fullString as NSString).range(of: "Bold")
let range2 = (fullString as NSString).range(of: "italic")
attrString.addAttributes([.font: UIFont.boldSystemFont(ofSize: 20.0)], range: range1)
attrString.addAttributes([.font: UIFont.boldSystemFont(ofSize: 20.0).italics()], range: range2)
label.attributedText = attrString
Whereas I use simple extension for UIFont.
extension UIFont {
func withTraits(_ traits: UIFontDescriptorSymbolicTraits) -> UIFont {
if let fd = fontDescriptor.withSymbolicTraits(traits) {
return UIFont(descriptor: fd, size: pointSize)
}
return self
}
func italics() -> UIFont {
return withTraits(.traitItalic)
}
}
So basically, what you need to know is, which text should be marked as italic, bold and normal. Afterwards just calculate the ranges for those texts in your original text using NSString.range(of: ) and update the attributes appropriately.
Note: You can also calculate the range using start and endIndex. For reference check this SO answer.
One of the possible ways to make it work with the function mentioned in the question - to modify stringList parameter.
First of all let's define model class BulletString:
class BulletString {
var text: String
var attributes: [NSAttributedStringKey : Any]?
init(string: String) {
text = string
}
}
Now your bullet stringList in you function should be [BulletString] type. Define two bulletStrings and pass them to your function. Here is a working solution with your function:
let bulletString1 = BulletString.init(string: "string1")
bulletString1.attributes = [NSAttributedStringKey.font: UIFont.boldSystemFont(ofSize: 18.0)]
let bulletString2 = BulletString.init(string: "string2")
let bullets = [bulletString1, bulletString2]
label.attributedText = bulletPoints(stringList: bullets, font: UIFont.systemFont(ofSize: 17), bullet: "•", lineSpacing: 4, paragraphSpacing: 4, textColor: UIColor.darkGray, bulletColor: UIColor.darkGray)
label.textColor = .black
func bulletPoints(stringList: [BulletString], font: UIFont,bullet: String = "\u{2022}",indentation: CGFloat = 20,lineSpacing: CGFloat = 2,paragraphSpacing: CGFloat = 12,textColor: UIColor = .gray,bulletColor: UIColor = .red) -> NSAttributedString{
let textAttributes: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: textColor]
let bulletAttributes: [NSAttributedStringKey: Any] = [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: bulletColor]
let paragraphStyle = NSMutableParagraphStyle()
let nonOptions = [NSTextTab.OptionKey: Any]()
paragraphStyle.tabStops = [NSTextTab(textAlignment: .left, location: indentation, options: nonOptions)]
paragraphStyle.defaultTabInterval = indentation
paragraphStyle.lineSpacing = lineSpacing
paragraphStyle.paragraphSpacing = paragraphSpacing
paragraphStyle.headIndent = indentation
let bulletList = NSMutableAttributedString()
for bulletString in stringList {
let formattedString = "\(bullet)\t\(bulletString.text)\n"
let attributedString = NSMutableAttributedString(string: formattedString)
attributedString.addAttributes(
[NSAttributedStringKey.paragraphStyle : paragraphStyle],
range: NSMakeRange(0, attributedString.length))
attributedString.addAttributes(
textAttributes,
range: NSMakeRange(0, attributedString.length))
// Here your custom attributes you provided in BulletString
if let attr = bulletString.attributes {
attributedString.addAttributes(attr, range: NSMakeRange(0, attributedString.length))
}
let string:NSString = NSString(string: formattedString)
let rangeForBullet:NSRange = string.range(of: bullet)
attributedString.addAttributes(bulletAttributes, range: rangeForBullet)
bulletList.append(attributedString)
}
return bulletList
}
Results

Resources