以编程方式将段落标题添加到 UITextView

Adding paragraph headings programmatically to a UITextView

我有一个包含 UITextView 的视图控制器。这个 UITextView 根据控制它的数据模型(大约 30 种不同的文本选项)以编程方式更新文本。 UITextView 中的内容总是很长,每个大约 450 字。段落范围为 2 - 5。

一切都很完美。我只需要一种视觉方式来分解段落。现在,我正在使用 \n\n 在段落之间创建几个换行符。虽然这行得通,但我最终还是需要标题。

在 IB 中,我将文本 属性 从 Plain 更改为 Attributed。这允许我在 Xcode 中以 WYSIWYG-like 的方式修改文本。效果很好,但我没有使用 IB 来更改文本。

我确实尝试过使用 WebView(下面的代码)。我用 H1 和 P 标签创建了一个非常基本的 HTML 文件。不幸的是,这将在我的项目中创建 30+ HTML 个文件。从可管理性的角度来看,我不喜欢这样。文本也不能选择,它与 webview 一起使用(我不想诉诸 CSS [-webkit-user-select] 来缓解这个问题。)

    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)

我的理想方案

我可以坚持目前的设置吗?使用我的数据模型以编程方式更改 UITextView 文本,同时能够更改代码中的文本属性以创建我的段落标题?

编辑 - 试图让它与 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]!

}

使用 NS(Mutable)ParagraphStyleNS(Mutable)AttributedString。例如:

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

结果: