Joe
Joe

Reputation: 3961

Adding paragraph headings programmatically to a UITextView

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]!

}

Upvotes: 3

Views: 3073

Answers (1)

Code Different
Code Different

Reputation: 93151

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

Upvotes: 8

Related Questions