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>