UITextView/UILabel with background but with spacing between lines

Swift 3 version of @Fabio's solution :

class splitedTextView: UITextView, NSLayoutManagerDelegate {

    override func awakeFromNib() {
        self.layoutManager.delegate = self
    }

    override func draw(_ rect: CGRect) {
        self.layoutManager.enumerateLineFragments(forGlyphRange: NSMakeRange(0, self.text.characters.count)) { (rect, usedRect, textContainer, glyphRange, Bool) in
            let rectanglePath = UIBezierPath(rect: CGRect(x: usedRect.origin.x, y: usedRect.origin.y+3, width: usedRect.size.width, height: usedRect.size.height-4))
            UIColor.black.setFill()
            rectanglePath.fill()
        }
    }

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

You can also create a variable inside your textView subclass to manage rectangle's color :

var color: UIColor?

And then use it instead of default black in your textView subclass :

self.color?.setFill()

Also if you do that, don't forget to use setNeedsDisplay() to redraw your textView and apply your custom color.


For iOS 11 attributed string has a problem with this kind of UI behavior. I've created UITextView subclass with space and bgColor property. Hope this helps too. The answer is just an extension to @Fabio solution.

class SpacingTextView: UITextView, NSLayoutManagerDelegate {

private var textHolder: String?
var spacing: CGFloat = 3
var bgColor: UIColor = .white

override var attributedText: NSAttributedString! {
    didSet {
        self.textHolder = self.attributedText.string
        self.setNeedsDisplay()
    }
}

override var text: String! {
    didSet {
        self.textHolder = self.attributedText.string
        self.setNeedsDisplay()
    }
}

override init(frame: CGRect, textContainer: NSTextContainer?) {
    super.init(frame: frame, textContainer: textContainer)
    self.configure()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.configure()
}

private func configure() {
    self.layoutManager.delegate = self
    self.backgroundColor = .clear
}

override func draw(_ rect: CGRect) {
    super.draw(rect)
    guard let txt = self.textHolder else {
        return
    }
    let textRange = NSRange(location: 0, length: txt.count)

    self.layoutManager.enumerateLineFragments(forGlyphRange: textRange) { (rect, usedRect, _, _, _) in
        var bgRect = usedRect
        bgRect.origin.y += self.spacing / 2
        bgRect.size.height -= self.spacing
        let bezierPath = UIBezierPath(rect: bgRect)
        self.bgColor.setFill()
        bezierPath.fill()
        bezierPath.close()
    }
}

func layoutManager(_ layoutManager: NSLayoutManager,
                   lineSpacingAfterGlyphAt glyphIndex: Int, withProposedLineFragmentRect rect: CGRect) -> CGFloat {
    return rect.size.height
}

func layoutManager(_ layoutManager: NSLayoutManager,
                   shouldUse action: NSLayoutManager.ControlCharacterAction,
                   forControlCharacterAt charIndex: Int) -> NSLayoutManager.ControlCharacterAction {
    return .lineBreak
}
}

After some research I found the best solution for what I needed. The solution below is only iOS7+.

First we add this to - (void)drawRect:(CGRect)rect of your UITextView subclass.

- (void)drawRect:(CGRect)rect    

    /// Position each line behind each line.
    [self.layoutManager enumerateLineFragmentsForGlyphRange:NSMakeRange(0, self.text.length) usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *textContainer, NSRange glyphRange, BOOL *stop) {

        /// The frame of the rectangle.
        UIBezierPath *rectanglePath = [UIBezierPath bezierPathWithRect:CGRectMake(usedRect.origin.x, usedRect.origin.y+3, usedRect.size.width, usedRect.size.height-4)];

        /// Set the background color for each line.
        [UIColor.blackColor setFill];

        /// Build the rectangle.
        [rectanglePath fill];
    }];
 }];

Then we set the line spacing for the UITextView:

- (CGFloat)layoutManager:(NSLayoutManager *)layoutManager lineSpacingAfterGlyphAtIndex:(NSUInteger)glyphIndex withProposedLineFragmentRect:(CGRect)rect
{
    return 15;
}

The method above is only called if you set the NSLayoutManagerDelegate. You could do that in your init, initWithFrame and initWithCode methods like this:

self.layoutManager.delegate = self;

Also don't forget to declare that your subclass is a delegate in your .h file:

@interface YOUR_SUBCLASS_OF_UITEXTVIEW : UITextView <NSLayoutManagerDelegate>