向具有背景颜色的 UILabel 添加填充

Adding padding to a UILabel with a background colour

我有一个包含 NSAttributedString 的多行 UILabel,它应用了背景色来产生上述效果。

这很好,但我需要在 标签内填充 ,以便在左侧提供一点 space。 SO 上还有其他帖子解决了这个问题,例如通过子类化 UILabel 来添加 UIEdgeInsets。然而,这只是为我在标签的外部添加了填充。

关于如何向该标签添加填充有什么建议吗?

编辑:如果这让您感到困惑,我们深表歉意,但最终目标是这样的...

我用了一种不同的方式。首先,我从 UILabel 中获取所有行,并在每行的起始位置添加额外的空白 space 。要从 UILabel 中获取所有行,我只需修改此 link ()

中的代码

最终extension UILabel代码:

extension UILabel {
    var addWhiteSpace: String {
        guard let text = text, let font = font else { return "" }
        let ctFont = CTFontCreateWithName(font.fontName as CFString, font.pointSize, nil)
        let attStr = NSMutableAttributedString(string: text)
        attStr.addAttribute(kCTFontAttributeName as NSAttributedString.Key, value: ctFont, range: NSRange(location: 0, length: attStr.length))
        let frameSetter = CTFramesetterCreateWithAttributedString(attStr as CFAttributedString)
        let path = CGMutablePath()
        path.addRect(CGRect(x: 0, y: 0, width: self.frame.size.width, height: CGFloat.greatestFiniteMagnitude), transform: .identity)
        let frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), path, nil)
        guard let lines = CTFrameGetLines(frame) as? [Any] else { return "" }
        
        return lines.map { line in
            let lineRef = line as! CTLine
            let lineRange: CFRange = CTLineGetStringRange(lineRef)
            let range = NSRange(location: lineRange.location, length: lineRange.length)
            return "  " + (text as NSString).substring(with: range)
        }.joined(separator: "\n")
    }
}

使用:

let labelText = yourLabel.addWhiteSpace
let attributedString = NSMutableAttributedString(string: labelText)
attributedString.addAttribute(NSAttributedString.Key.backgroundColor, value: UIColor.red, range: NSRange(location: 0, length: labelText.count))
yourLabel.attributedText = attributedString
yourLabel.backgroundColor = .yellow

编辑:

上面的代码在某些时候有效,但还不够。所以我创建了一个 class 并添加了填充和一个形状矩形层。

class AttributedPaddingLabel: UILabel {
    
    private let leftShapeLayer = CAShapeLayer()
    
    var leftPadding: CGFloat = 5
    var attributedTextColor: UIColor = .red
    
    override func awakeFromNib() {
        super.awakeFromNib()
        self.addLeftSpaceLayer()
        self.addAttributed()
    }
    
    override func drawText(in rect: CGRect) {
        let insets = UIEdgeInsets(top: 0, left: leftPadding, bottom: 0, right: 0)
        super.drawText(in: rect.inset(by: insets))
    }
    
    override var intrinsicContentSize: CGSize {
        let size = super.intrinsicContentSize
        return CGSize(width: size.width + leftPadding, height: size.height)
    }
    
    override var bounds: CGRect {
        didSet {
            preferredMaxLayoutWidth = bounds.width - leftPadding
        }
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)
        leftShapeLayer.path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: leftPadding, height: rect.height)).cgPath
    }
    
    private func addLeftSpaceLayer() {
        leftShapeLayer.fillColor = attributedTextColor.cgColor
        self.layer.addSublayer(leftShapeLayer)
    }
    
    private func addAttributed() {
        let lblText = self.text ?? ""
        let attributedString = NSMutableAttributedString(string: lblText)
        attributedString.addAttribute(NSAttributedString.Key.backgroundColor, value: attributedTextColor, range: NSRange(location: 0, length: lblText.count))
        self.attributedText = attributedString
    }
}

使用方法:

class ViewController: UIViewController {
    
    @IBOutlet weak var lblText: AttributedPaddingLabel!

    override func viewDidLoad() {
        super.viewDidLoad()
        
        lblText.attributedTextColor = .green
        lblText.leftPadding = 10
        
    }
}

根据此处提供的答案:

只是为了演示(几个硬编码的值,理想情况下,它们是动态的/计算的):

class ViewController: UIViewController, NSLayoutManagerDelegate {
    
    var myTextView: UITextView!
    let textStorage = MyTextStorage()
    let layoutManager = MyLayoutManager()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        myTextView = UITextView()
        myTextView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(myTextView)
        
        let g = view.safeAreaLayoutGuide
        
        NSLayoutConstraint.activate([
            myTextView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            myTextView.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            myTextView.widthAnchor.constraint(equalToConstant: 248.0),
            myTextView.heightAnchor.constraint(equalToConstant: 300.0),
        ])
    
        self.layoutManager.delegate = self
        
        self.textStorage.addLayoutManager(self.layoutManager)
        self.layoutManager.addTextContainer(myTextView.textContainer)
        
        let quote = "This is just some sample text to demonstrate the word wrapping with padding at the beginning (leading) and ending (trailing) of the lines of text."
        self.textStorage.replaceCharacters(in: NSRange(location: 0, length: 0), with: quote)
        
        guard let font = UIFont(name: "TimesNewRomanPSMT", size: 24.0) else {
            fatalError("Could not instantiate font!")
        }
        let attributes: [NSAttributedString.Key : Any] = [NSAttributedString.Key.font: font]
        self.textStorage.setAttributes(attributes, range: NSRange(location: 0, length: quote.count))

        myTextView.isUserInteractionEnabled = false
        
        // so we can see the frame of the textView
        myTextView.backgroundColor = .systemTeal
    }
    
    func layoutManager(_ layoutManager: NSLayoutManager,
                       lineSpacingAfterGlyphAt glyphIndex: Int,
                       withProposedLineFragmentRect rect: CGRect) -> CGFloat { return 14.0 }
    
    func layoutManager(_ layoutManager: NSLayoutManager,
                       paragraphSpacingAfterGlyphAt glyphIndex: Int,
                       withProposedLineFragmentRect rect: CGRect) -> CGFloat { return 14.0 }
}

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

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 = 41.0
            
            let currentContext = UIGraphicsGetCurrentContext()
            currentContext?.saveGState()
            
            // outline rectangles
            //currentContext?.setStrokeColor(UIColor.red.cgColor)
            //currentContext?.setLineWidth(1.0)
            //currentContext?.stroke(lineRect)

            // filled rectangles
            currentContext?.setFillColor(UIColor.orange.cgColor)
            currentContext?.fill(lineRect)

            currentContext?.restoreGState()
        }
    }
}

输出(蓝绿色背景显示框架):

SwiftUI

这只是 SwiftUI 中的一个建议。希望对你有用。

第 1 步:创建内容
let string =
"""
Lorem ipsum
dolor sit amet,
consectetur
adipiscing elit.
"""
第 2 步:应用属性
let attributed: NSMutableAttributedString = .init(string: string)
attributed.addAttribute(.backgroundColor, value: UIColor.orange, range: NSRange(location: 0, length: attributed.length))
attributed.addAttribute(.font, value: UIFont(name: "Times New Roman", size: 22)!, range: NSRange(location: 0, length: attributed.length))
第 3 步:使用 UILabel() 在 SwiftUI 中创建 AttributedLabel
struct AttributedLabel: UIViewRepresentable {
    let attributedString: NSAttributedString
    
    func makeUIView(context: Context) -> UILabel {
        let label = UILabel()
        label.lineBreakMode = .byClipping
        label.numberOfLines = 0

        return label
    }
    
    func updateUIView(_ uiView: UILabel, context: Context) {
        uiView.attributedText = attributedString
    }
}
最后一步:使用它并添加 .padding()
struct ContentView: View {
    var body: some View {
        AttributedLabel(attributedString: attributed)
            .padding()
    }
}

这是结果: