UITextView/UILabel with background but with spacing between lines

橙三吉。 提交于 2020-01-10 19:43:11

问题


How can I achieve this kind of style in iOS?

I tried using NSAttributedString and setting NSBackgroundColorAttributeName as blackColor but then it doesn't have a clearColor space between the lines. I also tried to set a lineSpacing to the NSMutableParagraphStyle but still, the style looks like one big black block.

I just need a hint in which way to go... thanks!

In other words, not something like this:


回答1:


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>



回答2:


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.




回答3:


EDIT :

For your requirment you need to use different UILabel for each line and manage spacing by setting frame.


This is working for me to have spacing between lines in multiline UILabel.

 NSMutableAttributedString* attrString = [[NSMutableAttributedString alloc] initWithString:yourlblText.text];
NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
[style setLineSpacing:16];
[attrString addAttribute:NSParagraphStyleAttributeName
                   value:style
                   range:NSMakeRange(0, yourlblText.text.length)];
yourlblText.attributedText = attrString;

Also check iOS 6 multiline label line spacing




回答4:


Simply add a new blank line with a paragraph style with the desired line height. In my Swift example, I just append an interlineAttributedString to the original attributedString:

let interlineStyle = NSMutableParagraphStyle()
interlineStyle.maximumLineHeight = 5.0
let interlineAttributedString = NSMutableAttributedString(string: "\n", attributes: [NSParagraphStyleAttributeName: interlineStyle])
attributedString.appendAttributedString(interlineAttributedString)

The extra \n can also be part of the original attributedString. In that case, you would use addAttribute with the appropriate range.

I tested on iOS 9 only, but I'm pretty sure it works well on previous iOS versions. It works perfectly in a UITextView. It is much easier to implement than using many UILabels or drawing the background rectangles.




回答5:


My approach was to create a UIView and add the label on it, constrain to Vertical top and the calculate the number of lines using the font size and line height and then create a CALayer for each line and add those to the UIView.

You can check my implementation here

    func setBorderLabel() {
       let str = "This is label style with solid highlight and have gap between the texts, uses a combination of UIView and UILabel, you can have borders also per line... check comments in the code"
       let trimmedString = str.trimmingCharacters(in: .whitespacesAndNewlines)

       let string = NSMutableAttributedString(string: trimmedString)
       borderLabel.attributedText = string

       let labelWidth: CGFloat = borderLabel.frame.size.width
       var lineCount: Float = 0.0
       let textSize = CGSize(width: labelWidth, height: CGFloat(MAXFLOAT))
       let rHeight: Float = Float(borderLabel.sizeThatFits(textSize).height)
       //var rWidth: Int = lroundf(Float(borderLabel.sizeThatFits(textSize).width))
       let charSize: Float = Float(borderLabel.font.lineHeight)
       lineCount =  (rHeight / charSize).rounded()
       var count = 0
       // This needs a bit of trial and error, you need to play with the FONT_SIZE value to get the desired effect
       for i in stride(from: 0, to: Int(borderLabel.frame.size.height) , by: FONT_SIZE) {
           if count >= Int(lineCount){
               break
           }
           let border = CALayer()
           border.borderColor = #colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1)
           border.frame = CGRect(x: 0, y: CGFloat(i), width: borderLabel.frame.size.width, height: CGFloat(FONT_SIZE - 1) )
           // Uncomment the below lines to have borders per line.
           //border.borderWidth = 1.0
           //border.cornerRadius = 6.0
           border.backgroundColor = #colorLiteral(red: 0.9411764741, green: 0.4980392158, blue: 0.3529411852, alpha: 1)
           borderLabel.layer.addSublayer(border)
           bgView.layer.insertSublayer(border, at: 0)
           count = count + 1
           borderLabel.layer.masksToBounds = true
       }

   }



回答6:


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


来源:https://stackoverflow.com/questions/27360006/uitextview-uilabel-with-background-but-with-spacing-between-lines

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!