Search code examples
iosuilabelbordernsattributedstring

iOS: Draw border around text UILabel


I want to draw an outline around the text of UILabel like this:

enter image description here

I tried using attributed text but there is no attribute for rendering border around text. Only underline is available. I also tried rendering html using attributed text but that didn't help either:

let htmlLabelText = String(format: "<html><body><span style='color:blue; border: 1.5px solid #55DF49; border-radius: 50px;'>%@</span></body></html>", labelText)
var attributedString = NSMutableAttributedString()
guard let stringData = data(using: .unicode) else { return NSMutableAttributedString() }
do {
    attributedString = try NSMutableAttributedString(
        data: stringData,
        options: [NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType],
        documentAttributes: nil
    )
} catch {}

I checked other post but none of them helped.


Solution

  • The problem is quite hard to solve for UILabel, because you have no direct access to NSLayoutManager, which is key for my solution.

    I have created IBDesignable UILabel subclass LineHighlightedLabel, which can do the job. The visual is not quite the same as image you provided but you can get there.

    Important part is to set text to UILabel as NSAttributedString, not just plain text.

    @IBDesignable
    public class LineHighlightedLabel: UILabel {
        public override func draw(_ rect: CGRect) {
            let layoutManager = NSLayoutManager()
            let textStorage = NSTextStorage.init(attributedString: attributedText!)
            textStorage.addLayoutManager(layoutManager)
    
            let textContainer = NSTextContainer.init(size: bounds.size)
            textContainer.lineFragmentPadding  = 0
            textContainer.maximumNumberOfLines = numberOfLines
            textContainer.lineBreakMode        = lineBreakMode
    
            layoutManager.addTextContainer(textContainer)
    
            layoutManager.enumerateLineFragments(forGlyphRange: NSMakeRange(0, textStorage.length)) { (rect, usedRect, textContainer, glyphRange, bool) in
                let lineRect = CGRect(x: usedRect.origin.x, y: usedRect.origin.y + 1, width: usedRect.size.width, height: usedRect.size.height - 2)
                UIColor.green.setStroke()
                let lineRectPath = UIBezierPath(roundedRect: lineRect, cornerRadius: 5)
                lineRectPath.lineWidth = 0.5
                lineRectPath.stroke()
            }
    
            super.drawText(in: rect)
        }
    
        public override func layoutSubviews() {
            super.layoutSubviews()
            setNeedsDisplay()
        }
    }
    

    LineHighlightedLabel is providing nice preview in interface builder, you can play with values easily.

    enter image description here