Swift 使用背景色向UILabel添加填充

Swift 使用背景色向UILabel添加填充,swift,uilabel,padding,nsattributedstring,textkit,Swift,Uilabel,Padding,Nsattributedstring,Textkit,我有一个多行UILabel,它包含一个NSAttributedString,它有一个应用的背景色来提供上述效果 这很好,但我需要在标签内填充,以便在左侧留出一点空间。还有其他关于SO的帖子解决了这个问题,比如通过将UILabel子类化来添加UIEdgeInSet。然而,对我来说,这只是在标签的外面添加了填充物 关于如何将填充添加到此标签有何建议 编辑:如果这让人困惑,我道歉,但最终目标是这样的 我用了一种不同的方法。首先,我从UILabel获取所有行,并在每行的起始位置添加额外的空格。要从UIL

我有一个多行UILabel,它包含一个NSAttributedString,它有一个应用的背景色来提供上述效果

这很好,但我需要在标签内填充,以便在左侧留出一点空间。还有其他关于SO的帖子解决了这个问题,比如通过将UILabel子类化来添加UIEdgeInSet。然而,对我来说,这只是在标签的外面添加了填充物

关于如何将填充添加到此标签有何建议

编辑:如果这让人困惑,我道歉,但最终目标是这样的


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

最终
扩展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")
    }
}
使用:

编辑:

上面的代码在某种程度上是有效的,但还不够。所以我创建了一个类,添加了填充和一个形状矩形层

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

根据此处提供的答案:

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

输出(teal背景显示帧):

SwiftUI 这只是SwiftUI中的一个建议。我希望它会有用

步骤1:创建内容
let字符串=
"""
乱数假文
多洛·西特·艾米特,
圣骑士
告别精英。
"""
步骤2:应用属性
let attributed:NSMutableAttributedString=.init(string:string)
attributed.addAttribute(.backgroundColor,值:UIColor.orange,范围:NSRange(位置:0,长度:attributed.length))
attributed.addAttribute(.font,值:UIFont(名称:“Times New Roman”,大小:22)!,范围:NSRange(位置:0,长度:attributed.length))
步骤3:使用UILabel()在SwiftUI中创建
AttributedLabel
struct AttributeLabel:UIViewRepresentable{
let attributedString:NSAttributedString
func makeUIView(上下文:context)->UILabel{
let label=UILabel()
label.lineBreakMode=.byClipping
label.numberOfLines=0
退货标签
}
func updateUIView(uiView:UILabel,context:context){
uiView.attributedText=attributedString
}
}
最后一步:使用它并添加
.padding()
struct ContentView:View{
var body:一些观点{
AttributedLabel(attributedString:attributed)
.padding()
}
}
结果是:


您尝试过什么吗?“在标签内,在左边留出一点空间”可能使用
NSParagraphStyle
?还是我误解了目标效果?@Larme谢谢。是的,我试过了。使用NSParagraphStyle,我可以调整headIntent,以在第一行之外的其他行上创建填充效果,但随后我还需要使用firstLineHeadIndent来覆盖第一行。令人烦恼的是,这会在第一行增加边距,而不是填充。谢谢。这不太管用,但还是要感谢大家的分享。返回一个空格会得到想要的结果。你在…返回两个空格是有原因的吗。。。返回“”+(文本为NSString)。子字符串(带:range)我试图根据开发人员的要求添加空间。这是一个很好的解决方案,我会记住这一点。唯一的缺点是,有时空间应用不正确。@Stamp我又添加了一个解决方案,请检查更新的答案。
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()
        }
    }
}