Настройка шаблона подчеркивания в NSAttributedString (iOS7+)

Я пытаюсь добавить пунктирное подчеркивание к NSAttributedString (в iOS7+/TextKit). Конечно, я попробовал встроенный NSUnderlinePatternDot:

NSString *string = self.label.text;
NSRange underlineRange = [string rangeOfString:@"consetetur sadipscing elitr"];

NSMutableAttributedString *attString = [[NSMutableAttributedString alloc] initWithString:string];
[attString addAttributes:@{NSUnderlineStyleAttributeName: @(NSUnderlineStyleSingle | NSUnderlinePatternDot)} range:underlineRange];

self.label.attributedText = attString;

Однако стиль, создаваемый этим, на самом деле скорее пунктирный, чем пунктирный:

скриншот

Я упустил что-то очевидное здесь (NSUnderlinePatternReallyDotted? ;)) или, возможно, есть способ настроить шаблон линии-точки?


person Daniel Rinser    schedule 27.08.2014    source источник


Ответы (3)


Согласно ответу Эйко и Дейва, я сделал такой пример

я попытался закончить это, используя UILabel, а не UITextView, но не смог найти решение после поиска в stackoverflow или других веб-сайтах. Итак, я использовал UITextView для этого.

    let storage = NSTextStorage()
    let layout = UnderlineLayout()
    storage.addLayoutManager(layout)
    let container = NSTextContainer()
    container.widthTracksTextView = true
    container.lineFragmentPadding = 0
    container.maximumNumberOfLines = 2
    container.lineBreakMode = .byTruncatingTail
    layout.addTextContainer(container)

    let textView = UITextView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width-40, height: 50), textContainer: container)
    textView.isUserInteractionEnabled = true
    textView.isEditable = false
    textView.textContainerInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
    textView.text = "1sadasdasdasdasdsadasdfdsf"
    textView.backgroundColor = UIColor.white

    let rg = NSString(string: textView.text!).range(of: textView.text!)
    let attributes = [NSAttributedString.Key.underlineStyle.rawValue: 0x11,
                      NSAttributedString.Key.underlineColor: UIColor.blue.withAlphaComponent(0.2),
                      NSAttributedString.Key.foregroundColor: UIColor.black, NSAttributedString.Key.font: UIFont.systemFont(ofSize: 17),
                      NSAttributedString.Key.baselineOffset:10] as! [NSAttributedString.Key : Any]

    storage.addAttributes(attributes, range: rg)
    view.addSubview(textView)

переопределить метод LayoutManage

class UnderlineLayout: NSLayoutManager {
    override func drawUnderline(forGlyphRange glyphRange: NSRange, underlineType underlineVal: NSUnderlineStyle, baselineOffset: CGFloat, lineFragmentRect lineRect: CGRect, lineFragmentGlyphRange lineGlyphRange: NSRange, containerOrigin: CGPoint) {
    if let container = textContainer(forGlyphAt: glyphRange.location, effectiveRange: nil) {
        let boundingRect = self.boundingRect(forGlyphRange: glyphRange, in: container)
        let offsetRect = boundingRect.offsetBy(dx: containerOrigin.x, dy: containerOrigin.y)

        let left = offsetRect.minX
        let bottom = offsetRect.maxY-15
        let width = offsetRect.width
        let path = UIBezierPath()
        path.lineWidth = 3
        path.move(to: CGPoint(x: left, y: bottom))
        path.addLine(to: CGPoint(x: left + width, y: bottom))
        path.stroke()
    }
}

в моем решении я должен расширить lineSpacing, а также сохранить подчеркивание, используя свойство NSAttributedString NSMutableParagraphStyle().lineSpacing, но, похоже, это не сработало, но NSAttributedString.Key.baselineOffset работает. надежда может

person JINRUN.LIU    schedule 16.11.2018

Я провел немного времени, играя с Text Kit, чтобы заставить этот и другие подобные сценарии работать. Фактическое решение Text Kit для этой проблемы состоит в том, чтобы создать подкласс NSLayoutManager и переопределить drawUnderline(forGlyphRange:underlineType:baselineOffset:lineFragmentRect:lineFragmentGlyphRange:containerOrigin:).

Это метод, который вызывается для фактического рисования подчеркивания, запрашиваемого атрибутами в NSTextStorage. Вот описание параметров, которые передаются:

  • glyphRange индекс первого глифа и количество следующих глифов, которые необходимо подчеркнуть
  • underlineType значение NSUnderlineStyle атрибута NSUnderlineAttributeName
  • baselineOffset расстояние между нижней частью ограничивающей рамки и смещением базовой линии для глифов в указанном диапазоне
  • lineFragmentRect прямоугольник, заключающий всю строку, содержащую glyphRange
  • lineFragmentGlyphRange диапазон глифов, составляющих всю строку, содержащую glyphRange
  • containerOrigin смещение контейнера в текстовом представлении, которому он принадлежит

Этапы рисования подчеркивания:

  1. Найдите NSTextContainer, который содержит glyphRange, используя NSLayoutManager.textContainer(forGlyphAt:effectiveRange:).
  2. Получите ограничивающий прямоугольник для glyphRange, используя NSLayoutManager.boundingRect(forGlyphRange:in:)
  3. Сместите ограничивающий прямоугольник по исходной точке текстового контейнера, используя CGRect.offsetBy(dx:dy:)
  4. Нарисуйте собственное подчеркивание где-то между нижней частью ограничивающего прямоугольника смещения и базовой линией (как определено baselineOffset)
person Dave Weston    schedule 23.01.2017

Я знаю, что это история двухлетней давности, но, возможно, она поможет кому-то, столкнувшемуся с той же проблемой, что и я на прошлой неделе. Мой обходной путь состоял в том, чтобы создать подкласс UITextView, добавить подпредставление (контейнер с пунктирными линиями) и использовать метод layoutManager textView enumerateLineFragmentsForGlyphRange для получения количества строк и их фреймов. Зная рамку линии, я рассчитал y там, где хотел разместить точки. Поэтому я создал представление (lineView, в котором я рисовал точки), установил clipsToBounds на YES, ширину на ширину textView, а затем добавил в качестве подпредставления в linesContainer в этом y. После этого я установил width для lineView на значение, возвращаемое enumerateLineFragmentsForGlyphRange. Для нескольких строк тот же подход: обновить кадры, добавить новые строки или удалить в зависимости от того, что возвращает enumerateLineFragmentsForGlyphRange. Этот метод вызывается в textViewDidChange после каждого обновления текста. Вот код для получения массива строк и их кадров

NSLayoutManager *layoutManager = [self layoutManager];
[layoutManager enumerateLineFragmentsForGlyphRange:NSMakeRange(0, [layoutManager numberOfGlyphs]) 
                                        usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *textContainer, NSRange glyphRange, BOOL *stop) {
                                                        [linesFramesArray addObject:NSStringFromCGRect(usedRect)];
                                                   }
];
person salice    schedule 06.10.2016