2

I have a View Controller that contains a UITextView. This UITextView gets the text updated programmatically depending on the data model controlling it (around 30 different text options). The content in the UITextView will always be quite long, at about 450 words each. Paragraphs will range from 2 - 5.

It all works perfect. I just need a visual way to break up the paragraphs. Right now, I'm using \n\n to create a couple of line breaks in between paragraphs. While this works, I ultimately need headings.

In IB, I changed the text property from Plain to Attributed. This allows me to modify the text in a WYSIWYG-like fashion right in Xcode. Works great, but I am not using IB to change the text.

I did try using a WebView (code below). I created an extremely basic HTML file with H1 and P tags. This will unfortunately create 30+ HTML files in my project. I don't like this from a manageability standpoint. The text also can NOT be selectable, which it is with a webview (I don't want to resort to CSS [-webkit-user-select] to mitigate this issue.)

    let path: String? = Bundle.main.path(forResource: "myHTML", ofType: "html")
    let file = try? String(contentsOfFile: path!, encoding: String.Encoding.utf8)
    let baseURL = URL(fileURLWithPath: Bundle.main.bundlePath)
    self.myWebView.loadHTMLString(file!, baseURL: baseURL)

My ideal solution

Can I stick with my current setup? Programmatically changing the UITextView text with my data model, WHILE being able to change the text attributes in code to create my paragraph headings?

This is my current UI without any attributed text This is my ultimate end-goal

Edit - Trying to get this to work with MVC

allFormattedDescriptions: [ 
Formatted(heading: "heading 1", descriptionText: "Lorem Ipsum Paragraph 1"),
Formatted(heading: "heading 2", descriptionText: "Lorem Ipsum Paragraph 2"),
Formatted(heading: "heading 3", descriptionText: "Lorem Ipsum Paragraph 3")
]
// Ideal formatting; every paragraph will have a heading. Can handle that with one object that requires both a heading and description text (paragraph).

struct Formatted {

var heading: String!
var descriptionText: String!

var bodyParagraphStyle: NSMutableParagraphStyle = {
    let style = NSMutableParagraphStyle()
    style.lineSpacing = 10
    style.paragraphSpacingBefore = 6
    style.paragraphSpacing = 6
    return style
}()

var headerParagraphStyle: NSMutableParagraphStyle = {
    let style = NSMutableParagraphStyle()
    style.paragraphSpacingBefore = 24
    return style
}()

var bodyAttributes: [String: AnyObject]!
var headerAttributes: [String: AnyObject]!

}
Cœur
  • 37,241
  • 25
  • 195
  • 267
Joe
  • 3,772
  • 3
  • 33
  • 64
  • You can definitely do it in code. `NSMutableAttributedString` allows you to add and remove attributes at will. You can also use it to display HTML. If you don't need HTML displaying then you can call `enumerateAttributesInRange` to get all attributes and modify them OR you can just call `addAttributes` or `setAttributes` to set the various styles. This includes paragraph styling and indentation, linespacing, colours, underline, strikethrough, font, etc.. – Brandon Jan 31 '17 at 01:32

1 Answers1

7

Use NS(Mutable)ParagraphStyle and NS(Mutable)AttributedString. For example:

class ViewController: UIViewController {
    @IBOutlet weak var textView: UITextView!

    var allFormattedDescriptions = [
        Formatted(heading: "Introduction to Bacon Ipsum", descriptionText: "Bacon ipsum dolor amet jerky pig pastrami capicola biltong turkey, ball tip fatback andouille porchetta flank swine brisket bacon pork loin. Tongue shank cupim, pastrami spare ribs meatball drumstick pork pork chop. Sirloin flank tenderloin bresaola doner, cupim ribeye drumstick ham hock t-bone pork short ribs shoulder. Fatback ribeye pastrami pancetta, chuck turkey andouille boudin burgdoggen shoulder tongue kielbasa doner shankle turducken. Rump strip steak drumstick, shankle cupim prosciutto jerky bacon doner. Pork chop jowl burgdoggen, cow turkey ball tip doner. Cow ham meatball chuck flank meatloaf prosciutto."),
        Formatted(heading: "Kielbasa?", descriptionText: "Spare ribs boudin ham leberkas landjaeger filet mignon. Short loin fatback hamburger leberkas chicken. Frankfurter chuck short ribs ball tip, ground round cupim shank brisket venison turducken boudin. Pig sirloin pork loin meatloaf short loin turkey swine.")
    ]

    override func viewDidLoad() {
        super.viewDidLoad()

        let textContent = NSMutableAttributedString()
        for (index, desc) in allFormattedDescriptions.enumerated() {
            let includeLinebreak = index < allFormattedDescriptions.count - 1
            textContent.append(desc.attributeString(includeLineBreak: includeLinebreak))
        }
        textView.attributedText = textContent
    }
}

struct Formatted {
    var heading: String
    var descriptionText: String

    var bodyParagraphStyle: NSMutableParagraphStyle = {
        let style = NSMutableParagraphStyle()
        style.lineSpacing = 10
        style.paragraphSpacingBefore = 6
        style.paragraphSpacing = 6
        return style
    }()

    var headerParagraphStyle: NSMutableParagraphStyle = {
        let style = NSMutableParagraphStyle()
        style.paragraphSpacingBefore = 24
        return style
    }()

    var bodyAttributes: [NSAttributedStringKey: Any]
    var headerAttributes: [NSAttributedStringKey: Any]

    func attributeString(includeLineBreak: Bool = true) -> NSAttributedString {
        let result = NSMutableAttributedString()
        result.append(NSAttributedString(string: self.heading + "\n", attributes: self.headerAttributes))
        result.append(NSAttributedString(string: self.descriptionText, attributes: self.bodyAttributes))
        if includeLineBreak {
            result.append(NSAttributedString(string: "\n", attributes: self.bodyAttributes))
        }

        return result as NSAttributedString
    }

    init(heading: String, descriptionText: String) {
        self.heading = heading
        self.descriptionText = descriptionText
        self.bodyAttributes = [
             NSAttributedStringKey.font: UIFont(name: "Hoefler Text", size: 14)!,
            NSAttributedStringKey.paragraphStyle: bodyParagraphStyle
        ]
        self.headerAttributes = [
            NSAttributedStringKey.font: UIFont(name: "Avenir", size: 22)!,
            NSAttributedStringKey.paragraphStyle: headerParagraphStyle,
            NSAttributedStringKey.foregroundColor: UIColor.red
        ]
    }
}

Result:

Formatted text

Aaron Halvorsen
  • 2,610
  • 1
  • 23
  • 31
Code Different
  • 90,614
  • 16
  • 144
  • 163
  • 1
    This is very nice. I love how you separated the body and paragraph attributes. I'm trying to break this apart into MVC but having a tough time. I just want the model to handle the heading and paragraph text input. There's a small edit to my original post to show the path I'm trying to head down. Any further advice would be very much appreciated. – Joe Jan 31 '17 at 17:17