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

问题描述

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

设计

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

layout

代码

这是我的代码,使它看起来像上面的左图。请参阅代码中的注释 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 时,主字符串的最后一行末尾有额外的空格。请注意,我将此设置为零:mainStringParagraphStyle.paragraphSpacing = 0,但末尾仍有空格,因为 mainStringParagraphStyle.linespacing = 60

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

我把代码图片放在下面:

test

代码

    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 中使用换行符。此外,我使用了 lineSpacing 而不是 lineHeightMultiple。这个参数影响行之间的缩进而不影响最后一行:

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
}

enter image description here

也看看我的图书馆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,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
}

Results