Friday, December 15, 2017

Adjust Elements after Manipulating String into AttributedString - Swift 4

Leave a Comment

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.

3 Answers

Answers 1

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)     }  } 

Results of example

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

Answers 2

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. Hope it helps. Happy coding.

Answers 3

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

enter image description here

If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment