UITextView/UILabel с фоном, но с интервалом между строками

Как мне добиться такого стиля в iOS?

Я пытался с помощью NSAttributedString и настройка NSBackgroundColorAttributeName как blackColor но тогда у него нет clearColor пространство между линиями. Я также пытался установить lineSpacing к NSMutableParagraphStyle но все же стиль выглядит как один большой черный блок.

Мне просто нужна подсказка, в какую сторону идти... спасибо!

Другими словами, не как то так:

6 ответов

Решение

После некоторых исследований я нашел лучшее решение для того, что мне было нужно. Решение ниже - только iOS7+.

Сначала мы добавим это к - (void)drawRect:(CGRect)rect вашей UITextView подкласс.

- (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];
    }];
 }];

Затем мы устанавливаем межстрочный интервал для UITextView:

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

Вышеуказанный метод вызывается только если вы установили NSLayoutManagerDelegate, Вы могли бы сделать это в своем init, initWithFrame а также initWithCode методы как это:

self.layoutManager.delegate = self;

Также не забудьте объявить, что ваш подкласс является делегатом в вашем .h файл:

@interface YOUR_SUBCLASS_OF_UITEXTVIEW : UITextView <NSLayoutManagerDelegate>

Swift 3 версия решения @Fabio:

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

Вы также можете создать переменную внутри вашего textView подкласс для управления цветом прямоугольника:

var color: UIColor?

А затем использовать его вместо черного по умолчанию в вашем textView подкласс:

self.color?.setFill()

Также, если вы делаете это, не забудьте использовать setNeedsDisplay() перерисовать textView и примените свой собственный цвет.

Для iOS 11 приписанная строка имеет проблему с таким поведением пользовательского интерфейса. Я создал подкласс UITextView с пробелом и свойством bgColor. Надеюсь, это тоже поможет. Ответ - только расширение к решению @Fabio.

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

Просто добавьте новую пустую строку со стилем абзаца с желаемой высотой строки. В моем примере Swift я просто добавляю interlineAttributedString к оригинальному attribuStString:

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

Экстра \n также может быть частью исходной строки attributed. В этом случае вы бы использовали addAttribute с соответствующим диапазоном.

Я тестировал только на iOS 9, но я уверен, что он хорошо работает на предыдущих версиях iOS. Это прекрасно работает в UITextView. Это гораздо проще реализовать, чем использовать множество UILabels или рисовать фоновые прямоугольники.

РЕДАКТИРОВАТЬ:

Для вашего требования вы должны использовать different UILabel за each line а также manage spacing от setting frame,


Это работает для меня, чтобы иметь spacing между lines в 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;

Также проверьте многострочный межстрочный интервал в iOS 6

Мой подход состоял в том, чтобы создать UIView и добавить на него метку, ограничить его вертикальной вершиной и вычислить количество строк, используя размер шрифта и высоту строки, а затем создать CALayer для каждой строки и добавить их в UIView.

Вы можете проверить мою реализацию здесь

    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
       }

   }
Другие вопросы по тегам