NSAttributedString 在文本和背景之前和之后有填充

Posted

技术标签:

【中文标题】NSAttributedString 在文本和背景之前和之后有填充【英文标题】:NSAttributedString with padding before and after the text and a background 【发布时间】:2019-12-09 20:12:51 【问题描述】:

我想使用NSAttributedString 设置文本样式。文本应具有背景和自定义填充,以便文本与背景边缘有一点空间。

这就是我想要实现的目标:

这是我不想实现的(第二行的背景不是单词/字符特定的):

这是我在操场上尝试过的代码:

let quote = "some text with a lot of other text and \nsome other text."
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .left
let attributes: [NSAttributedString.Key: Any] = [
    NSAttributedString.Key.paragraphStyle: paragraphStyle,
    NSAttributedString.Key.backgroundColor: UIColor.red,
    NSAttributedString.Key.foregroundColor: UIColor.white
]
let attributedQuote = NSAttributedString(string: quote, attributes: attributes)

这就是 Playground 预览呈现的内容:

[![游乐场预览][3]][3]

文字非常靠近背景边缘。有什么方法可以让文本的背景为文本留出一些空间?我需要一些填充物。 我尝试使用headIndent,但这会将带有背景的文本移动到右侧,而不仅仅是文本。因此它对填充没有用。

【问题讨论】:

paragraphStyle.lineSpacing = desiredLineSpacing,开始和结束填充,您可以添加空格或执行类似于***.com/questions/27459746/…的操作 空格在开头和结尾都有效,但是当文本换行时你需要一个。而且因为我不知道文本何时换行(自动布局、文本长度),所以我无法设置空格。另一个问题的方法并不完全符合我的要求。我希望背景只要单个文本行有字符。 UILabel 上的背景具有标签的宽度和单行的单词或字符的宽度。 你不需要 NSAttributedString 来完成这个简单的任务。使用带有约束和 0 行的 UILabel,在代码中为它分配字符串时,只需这样做:label.text = " \(myTextString) ",并为标签分配背景颜色。 @Starsky 标签的问题是,背景总是与标签的宽度一样宽。但我希望背景只在角色后面可见。所以每一行都有一个背景,只有字符去。 (查看更新的问题以获取图片) @WalterBeiter:看过***.com/a/28042708/3825084,这可能是解决您问题的好方法? 【参考方案1】:

文本应该有一个背景和一个自定义填充,以便文本在背景边缘有一点空间。

我发现最好的方法是使用 TextKit,它有点麻烦,但它是完全模块化的,专为此目的而设计。 在我看来,TextView 本身不能在其draw 方法中绘制矩形,这是 LayoutManager 的工作。

为了方便使用复制粘贴(Swift 5.1 - ios 13),下文提供了项目中使用的整个类。

AppDelegate.swift 存储用于在应用中的任何位置获取文本的属性。

class AppDelegate: UIResponder, UIApplicationDelegate 

    lazy var TextToBeRead: NSAttributedString = 

        var text: String
        if let filepath = Bundle.main.path(forResource: "TextToBeRead", ofType: "txt") 
            do  text = try String(contentsOfFile: filepath) 
            catch  text = "E.R.R.O.R." 
         else  text = "N.O.T.H.I.N.G." 

        return NSAttributedString(string: text)
    ()

ViewController.swift ⟹ 全屏只有一个文本视图。

class ViewController: UIViewController, NSLayoutManagerDelegate 

    @IBOutlet weak var myTextView: UITextView!
    let textStorage = MyTextStorage()
    let layoutManager = MyLayoutManager()

    override func viewDidLoad() 
        super.viewDidLoad()

        self.layoutManager.delegate = self

        self.textStorage.addLayoutManager(self.layoutManager)
        self.layoutManager.addTextContainer(myTextView.textContainer)

        let appDelegate = UIApplication.shared.delegate as? AppDelegate
        self.textStorage.replaceCharacters(in: NSRange(location: 0, length: 0),
                                           with: (appDelegate?.TextToBeRead.string)!)
    

    func layoutManager(_ layoutManager: NSLayoutManager,
                       lineSpacingAfterGlyphAt glyphIndex: Int,
                       withProposedLineFragmentRect rect: CGRect) -> CGFloat  return 20.0 

    func layoutManager(_ layoutManager: NSLayoutManager,
                       paragraphSpacingAfterGlyphAt glyphIndex: Int,
                       withProposedLineFragmentRect rect: CGRect) -> CGFloat  return 30.0 

MyTextStorage.swift

class MyTextStorage: NSTextStorage 

    var backingStorage: NSMutableAttributedString

    override init() 

        backingStorage = NSMutableAttributedString()
        super.init()
    

    required init?(coder: NSCoder) 

        backingStorage = NSMutableAttributedString()
        super.init(coder: coder)
    

//    Overriden GETTERS
    override var string: String 
        get  return self.backingStorage.string 
    

    override func attributes(at location: Int,
                             effectiveRange range: NSRangePointer?) -> [NSAttributedString.Key : Any] 

        return backingStorage.attributes(at: location, effectiveRange: range)
    

//    Overriden SETTERS
    override func replaceCharacters(in range: NSRange, with str: String) 

        backingStorage.replaceCharacters(in: range, with: str)
        self.edited(.editedCharacters,
                    range: range,
                    changeInLength: str.count - range.length)
    

    override func setAttributes(_ attrs: [NSAttributedString.Key : Any]?, range: NSRange) 

        backingStorage.setAttributes(attrs, range: range)
        self.edited(.editedAttributes,
                    range: range,
                    changeInLength: 0)
    

MyLayoutManager.swift

import CoreGraphics //Important to draw the rectangles

class MyLayoutManager: NSLayoutManager 

    override init()  super.init() 

    required init?(coder: NSCoder)  super.init(coder: coder) 

    override func drawBackground(forGlyphRange glyphsToShow: NSRange, at origin: CGPoint)    
        super.drawBackground(forGlyphRange: glyphsToShow, at: origin)

        self.enumerateLineFragments(forGlyphRange: glyphsToShow)  (rect, usedRect, textContainer, glyphRange, stop) in

            var lineRect = usedRect
            lineRect.size.height = 30.0

            let currentContext = UIGraphicsGetCurrentContext()
            currentContext?.saveGState()

            currentContext?.setStrokeColor(UIColor.red.cgColor)
            currentContext?.setLineWidth(1.0)
            currentContext?.stroke(lineRect)

            currentContext?.restoreGState()
        
    

...最后是这样的:

只需要自定义颜色和调整几个参数以贴合您的项目,但这是在文本和背景前后显示带有填充的 NSAttributedString 的基本原理...还有更多如果需要,超过 2 行。

【讨论】:

以上是关于NSAttributedString 在文本和背景之前和之后有填充的主要内容,如果未能解决你的问题,请参考以下文章

NSAttributedString.h 中文本属性key的说明-06

Attributes:文本属性 和NSAttributedString

NSAttributedString

下划线覆盖 NSAttributedString 中的文本

[iOS] 利用 NSAttributedString 进行富文本处理

利用 NSAttributedString 进行富文本处理