Functional Formatting in Swift: NSAttributedStrings and NSParagraphStyle

Last year I wrote a post that concerned the use of NSAttributedStrings in Swift. In this post I take that code and rewrite it using a functional approach.

The first thing to do is to define functions that return functions.
typealias CharacterStyle = String -> NSAttributedString
typealias ParagraphStyle = [NSAttributedString] -> NSAttributedString
With these in place, the functions that will return these functions are written:
 
func bodyText(size:CGFloat, style:UIFontDescriptorSymbolicTraits?) -> CharacterStyle {
    var desc = UIFontDescriptor(fontAttributes: [UIFontDescriptorTextStyleAttribute:UIFontTextStyleBody])
    if let style = style {
        desc = desc.fontDescriptorWithSymbolicTraits(style)
    }
    let font = UIFont(descriptor: desc, size: size)
    return {
        string in
         return NSMutableAttributedString(string: string, attributes:[NSFontAttributeName:font])
    }
}

func paragraph(indent indent:CGFloat, spacing:CGFloat) -> ParagraphStyle {
    return { attrStrings in
        let para = NSMutableAttributedString()
        for s in attrStrings {
                para.appendAttributedString(s)
        }
        let paraStyle = NSMutableParagraphStyle()
        paraStyle.firstLineHeadIndent = indent
        paraStyle.paragraphSpacingBefore = spacing
        para.addAttribute(NSParagraphStyleAttributeName, value: paraStyle, range: NSRange(location: 0,length: para.string.characters.count))
        return para
    }
}
I'm not going to discuss at length here the process of what is explained in Functional Programming in Swift (Chapter 3: Wrapping Core Image). Instead, these are examples of how the principles could be applied to refine code that I've written previously.

Note: The book tackles image filters and I transfer this knowledge to attributed strings.

Usage

Now the "boilerplate" code has been hidden away, the implementation can take place very simply and yet be flexible:
// styling functions are created
let bodyStyle = bodyText(12, style: nil)
let bodyStyleItalic = bodyText(12, style: .TraitItalic)
        
// build an array of styled attributed strings
let attrStrings = [bodyStyle("Hello Swift! This is a tutorial looking at "),
bodyStyleItalic("attributed"), bodyStyle(" strings!")]
// create a paragraph from the strings
let para = paragraph(indent:10.0, spacing: 12.0)(attrStrings)
Once character styles have been applied to an array of strings, a paragraph can be constructed from the resulting array of attributed strings. One of the most appealing things about this approach is that it is possible to see immediately the styles being applied to each part of the paragraph.

Notes

Notice the final line of code though,  here a function that takes a function is returned
let para = paragraph(indent:10.0, spacing: 12.0)
and immediately employed by passing the argument (attrStrings). If we'd wanted to separate things out then we could've written:
let para = paragraph(indent:10.0, spacing: 12.0)
para(attrStrings)
But there is no need to.

Finishing up

Finally some code to load this into a view
// Create UITextView
let view = UITextView(frame: CGRect(x: 0, y: 20, width: CGRectGetWidth(self.view.frame), height: CGRectGetWidth(self.view.frame)-20))
        
// Add string to UITextView
view.attributedText = para
        
// Add UITextView to main view
self.view.addSubview(view)
and a gist containing the full code.

Endorse on Coderwall

Comments