NSAttributedString with padding before and after the text and a background

前端 未结 1 1555
长发绾君心
长发绾君心 2021-01-16 08:24

I want to style a text using NSAttributedString. The text should have a background and a custom padding, so that the text has a little bit of space to the backg

相关标签:
1条回答
  • 2021-01-16 08:58

    The text should have a background and a custom padding, so that the text has a little bit of space to the background's edge.

    The best way I found is using TextKit, it's a little bit cumbersome but it's completely modular and is made for this purpose.
    In my view, it isn't up to the TextView itself to draw the rectangles in its draw method, that's the LayoutManager's work.

    The entire classes used in the project are provided hereafter in order to ease the work with copy-paste (Swift 5.1 - iOS 13).

    AppDelegate.swift stores the property to get the text wherever you are in the app.

    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 ⟹ only one single text view at full screen.

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

    ... and here's what it looks like in the end:

    There's only to customize the colors and adjust few parameters to stick to your project but this is the rationale to display a NSAttributedString with padding before and after the text and a background... for much more than 2 lines if need be.

    0 讨论(0)
提交回复
热议问题