一个字符串具有多个段落样式

One string with multiple paragraph styles

我想要一个具有不同段落样式的字符串。目标是为字符串的不同部分自定义 paragraph/line 间距。我研究并发现了这个 answer 但由于我添加了多个换行符,所以不确定如何实现。

设计

这是我在布局方面的目标:

代码

这是我的代码,它看起来像上面的左图。请看代码中的注释Not working。请注意主字符串的间距是如何设置的,但其他字符串无法设置自己的自定义间距:

struct BookModel: Codable {
    let main: String
    let detail: String
}

func createAttributedString(for model: BookModel) -> NSMutableAttributedString {
    let fullString = NSMutableAttributedString()
    
    let mainString = NSMutableAttributedString(string: model.main)
    let mainStringParagraphStyle = NSMutableParagraphStyle()
    mainStringParagraphStyle.alignment = .center
    mainStringParagraphStyle.lineSpacing = 10
    mainStringParagraphStyle.paragraphSpacing = 30
    let mainStringAttributes: [NSAttributedString.Key: Any] = [.paragraphStyle: mainStringParagraphStyle]
    
    let spacingAfterQuote = NSMutableAttributedString(string: "\n")
    
    let lineImageAttachment = NSTextAttachment(image: #imageLiteral(resourceName: "line-image"))
    let lineImageString = NSMutableAttributedString(attachment: lineImageAttachment)
    let lineParagraphStyle = NSMutableParagraphStyle()
    lineParagraphStyle.alignment = .left
    lineParagraphStyle.lineSpacing = 0 // Not working - instead of 0 it is 30 from `mainStringParagraphStyle`
    lineParagraphStyle.paragraphSpacing = 0 // Not working - instead of 0 it is 30 from `mainStringParagraphStyle`
    let lineAttributes: [NSAttributedString.Key: Any] = [.paragraphStyle: lineParagraphStyle]
    
    let spacingAfterSeparator = NSMutableAttributedString(string: "\n")
    let spacingAfterSeparatorParagraphStyle = NSMutableParagraphStyle()
    spacingAfterSeparatorParagraphStyle.alignment = .left
    spacingAfterSeparatorParagraphStyle.lineSpacing = 0 // Not working - instead of 0 it is 30 from `mainStringParagraphStyle`
    spacingAfterSeparatorParagraphStyle.paragraphSpacing = 5 // Not working - instead of 5 it is 30 from `mainStringParagraphStyle`
    let spacingAfterSeparatorAttributes: [NSAttributedString.Key: Any] = [.paragraphStyle: spacingAfterSeparatorParagraphStyle]
    
    let detailString = NSMutableAttributedString(string: model.detail)
    let detailStringAttributes: [NSAttributedString.Key: Any] = [.font: UIFont.systemFont(ofSize: 20)]
    
    fullString.append(mainString)
    fullString.append(spacingAfterQuote)
    fullString.append(lineImageString)
    fullString.append(spacingAfterSeparator)
    fullString.append(detailString)
    
    fullString.addAttributes(mainStringAttributes, range: fullString.mutableString.range(of: model.main))
    fullString.addAttributes(lineAttributes, range: fullString.mutableString.range(of: lineImageString.string))
    fullString.addAttributes(spacingAfterSeparatorAttributes, range: fullString.mutableString.range(of: spacingAfterSeparator.string))
    fullString.addAttributes(detailStringAttributes, range: fullString.mutableString.range(of: model.detail))
    
    return fullString
}

关于如何实现右边的图像有什么想法吗?

问题更新 1

下面的代码有效!只有一个小问题。当我添加 lineSpacing 时,在主字符串的最后一行末尾有额外的 space。请注意,我将此设置为零:mainStringParagraphStyle.paragraphSpacing = 0,但最后仍然有 space,因为 mainStringParagraphStyle.lineSpacing = 60.

我问这个的原因是为了对间距进行更精细的粒度控制。例如,在线图像和主字符串之间有一个完美的数字。对此有什么想法吗?

我把代码和图片放在下面:

代码:

    func createAttributedString(for model: BookModel) -> NSMutableAttributedString {
        let fullString = NSMutableAttributedString()
        
        let mainStringParagraphStyle = NSMutableParagraphStyle()
        mainStringParagraphStyle.alignment = .center
        mainStringParagraphStyle.paragraphSpacing = 0 // The space after the end of the paragraph
        mainStringParagraphStyle.lineSpacing = 60 // NOTE: This controls the spacing after the last line instead of just `paragraphSpacing`
            
        let mainString = NSAttributedString(string: "\(model.main)\n",
                                            attributes: [.paragraphStyle: mainStringParagraphStyle, .font: UIFont.systemFont(ofSize: 24)])
        
        let lineImageStringParagraphStyle = NSMutableParagraphStyle()
        lineImageStringParagraphStyle.alignment = .center
        
        let lineImageAttachment = NSTextAttachment(image: #imageLiteral(resourceName: "line-view"))
        let lineImageString = NSMutableAttributedString(attachment: lineImageAttachment)
        lineImageString.addAttribute(.paragraphStyle, value: lineImageStringParagraphStyle, range: NSRange(location: 0, length: lineImageString.length))
        
        let detailStringParagraphStyle = NSMutableParagraphStyle()
        detailStringParagraphStyle.alignment = .center
        detailStringParagraphStyle.paragraphSpacingBefore = 5 // The distance between the paragraph’s top and the beginning of its text content
        detailStringParagraphStyle.lineSpacing = 0
        
        let detailString = NSAttributedString(string: "\n\(model.detail)",
                                              attributes: [.paragraphStyle: detailStringParagraphStyle, .font: UIFont.systemFont(ofSize: 12)])
        
        fullString.append(mainString)
        fullString.append(lineImageString)
        fullString.append(detailString)
        
        return fullString
    }

更新的答案:

这是一个新的例子。我用图像设置段落顶部和底部的间距。如果需要,这允许在 model.mainmodel.detail 中使用换行符。另外,我使用了 lineHeightMultiple 而不是 lineSpacing。该参数影响行与行之间的缩进,但不影响最后一行:

func createAttributedString(for model: BookModel) -> NSAttributedString {
    let fullString = NSMutableAttributedString()
    
    let mainStringParagraphStyle = NSMutableParagraphStyle()
    mainStringParagraphStyle.alignment = .center
    mainStringParagraphStyle.lineHeightMultiple = 2 // Note that this is a multiplier, not a value in points
    
    let mainString = NSAttributedString(string: "\(model.main)\n", attributes: [.paragraphStyle: mainStringParagraphStyle, .font: UIFont.systemFont(ofSize: 24)])
    
    let lineImageStringParagraphStyle = NSMutableParagraphStyle()
    lineImageStringParagraphStyle.alignment = .center
    lineImageStringParagraphStyle.paragraphSpacingBefore = 10 // The space before image
    lineImageStringParagraphStyle.paragraphSpacing = 20 // The space after image
    
    let lineImageAttachment = NSTextAttachment(image: #imageLiteral(resourceName: "line-image"))
    let lineImageString = NSMutableAttributedString(attachment: lineImageAttachment)
    lineImageString.addAttribute(.paragraphStyle, value: lineImageStringParagraphStyle, range: NSRange(location: 0, length: lineImageString.length))
    
    let detailStringParagraphStyle = NSMutableParagraphStyle()
    detailStringParagraphStyle.alignment = .center
    
    let detailString = NSAttributedString(string: "\n\(model.detail)", attributes: [.paragraphStyle: detailStringParagraphStyle, .font: UIFont.systemFont(ofSize: 12)])
    
    fullString.append(mainString)
    fullString.append(lineImageString)
    fullString.append(detailString)
    
    return fullString
}

也可以看看我的图书馆 StringEx。它允许您从模板创建 NSAttributedString 并应用样式,而无需编写大量代码:

import StringEx

...

func createAttributedString(for model: BookModel) -> NSAttributedString {
    let pattern = "<main />\n<image />\n<detail />"
    let ex = pattern.ex
    
    ex[.tag("main")]
        .insert(model.main)
        .style([
            .aligment(.center),
            .lineHeightMultiple(2),
            .font(.systemFont(ofSize: 24))
        ])
    
    let lineImageAttachment = NSTextAttachment(image: #imageLiteral(resourceName: "line-image"))
    let lineImageString = NSAttributedString(attachment: lineImageAttachment)
    
    ex[.tag("image")]
        .insert(lineImageString)
        .style([
            .aligment(.center),
            .paragraphSpacingBefore(10),
            .paragraphSpacing(20)
        ])
    
    ex[.tag("detail")]
        .insert(model.detail)
        .style([
            .aligment(.center),
            .font(.systemFont(ofSize: 12))
        ])
    
    return ex.attributedString
}

旧答案:

我想你可以只设置第一段(主字符串)末尾的间距和最后一段(详细字符串)开头的间距:

func createAttributedString(for model: BookModel) -> NSMutableAttributedString {
    let fullString = NSMutableAttributedString()
    
    let mainStringParagraphStyle = NSMutableParagraphStyle()
    mainStringParagraphStyle.alignment = .center
    mainStringParagraphStyle.paragraphSpacing = 30 // The space after the end of the paragraph
    
    let mainString = NSAttributedString(string: "\(model.main)\n", attributes: [.paragraphStyle: mainStringParagraphStyle])
    
    let lineImageStringParagraphStyle = NSMutableParagraphStyle()
    lineImageStringParagraphStyle.alignment = .center
    
    let lineImageAttachment = NSTextAttachment(image: #imageLiteral(resourceName: "line-image"))
    let lineImageString = NSMutableAttributedString(attachment: lineImageAttachment)
    lineImageString.addAttribute(.paragraphStyle, value: lineImageStringParagraphStyle, range: NSRange(location: 0, length: lineImageString.length))
    
    let detailStringParagraphStyle = NSMutableParagraphStyle()
    detailStringParagraphStyle.alignment = .center
    detailStringParagraphStyle.paragraphSpacingBefore = 5 // The distance between the paragraph’s top and the beginning of its text content
    
    let detailString = NSAttributedString(string: "\n\(model.detail)", attributes: [.paragraphStyle: detailStringParagraphStyle])
    
    fullString.append(mainString)
    fullString.append(lineImageString)
    fullString.append(detailString)
    
    return fullString
}