Управляйте интервалами вокруг настраиваемых текстовых атрибутов в NSLayoutManager

У меня есть собственный подкласс NSLayoutManager, который я использую для рисования жетонов в форме таблеток. Я рисую эти токены для подстрок с настраиваемым атрибутом (TokenAttribute). Рисую без проблем.

Однако мне нужно добавить немного «отступов» вокруг диапазонов с помощью моего TokenAttribute (чтобы фон круглого прямоугольника токена не пересекался с текстом).

введите описание изображения здесь

На приведенном выше изображении я рисую фон своего токена оранжевым цветом, но я хочу добавить дополнительные отступы вокруг 469, чтобы фон не располагался прямо напротив текста.

Я не знаю, как это сделать. Я попытался переопределить -boundingRectForGlyphRange:inTextContainer:, чтобы вернуть ограничивающий прямоугольник с большим количеством отступов по горизонтали, но оказалось, что это фактически не влияет на расположение глифов.

Как увеличить расстояние между определенными глифами/диапазонами глифов?


Вот код, который я использую для рисования фона в моем подклассе менеджера компоновки:

- (void)drawGlyphsForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origin {

    NSTextStorage *textStorage = self.textStorage;
    NSRange glyphRange = glyphsToShow;

    while (glyphRange.length > 0) {

        NSRange characterRange = [self characterRangeForGlyphRange:glyphRange actualGlyphRange:NULL];
        NSRange attributeCharacterRange;
        NSRange attributeGlyphRange;

        id attribute = [textStorage attribute:LAYScrubbableParameterAttributeName 
                                      atIndex:characterRange.location 
                        longestEffectiveRange:&attributeCharacterRange 
                                      inRange:characterRange];

        attributeGlyphRange = [self glyphRangeForCharacterRange:attributeCharacterRange 
                                           actualCharacterRange:NULL];
        attributeGlyphRange = NSIntersectionRange(attributeGlyphRange, glyphRange);

        if (attribute != nil) {
            CGContextRef context = UIGraphicsGetCurrentContext();
            CGContextSaveGState(context);

            UIColor *backgroundColor = [UIColor orangeColor];
            NSTextContainer *textContainer = self.textContainers[0];
            CGRect boundingRect = [self boundingRectForGlyphRange:attributeGlyphRange inTextContainer:textContainer];

            // Offset this bounding rect by the `origin` passed in above
            // `origin` is the origin of the text container!
            // if we don't do this, then bounding rect is incorrectly placed (too high, in my case).
            boundingRect.origin.x += origin.x;
            boundingRect.origin.y += origin.y;

            [backgroundColor setFill];
            UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:boundingRect cornerRadius:boundingRect.size.height / 2.0];
            [path fill];

            [super drawGlyphsForGlyphRange:attributeGlyphRange atPoint:origin];
            CGContextRestoreGState(context);

        } else {
            [super drawGlyphsForGlyphRange:glyphsToShow atPoint:origin];
        }

        glyphRange.length = NSMaxRange(glyphRange) - NSMaxRange(attributeGlyphRange);
        glyphRange.location = NSMaxRange(attributeGlyphRange);
    }
}

person jbrennan    schedule 29.12.2015    source источник
comment
Я не гуру, но немного покопался в TextKit. Что вы используете, чтобы нарисовать этот фон? Можете ли вы показать небольшой код?   -  person Moshe    schedule 29.12.2015
comment
Почему вы не можете изменить boundingRect и сделать его немного больше? Прямоугольник не должен обрезаться, поэтому, если вы добавите немного больше отступов, он должен работать.   -  person Moshe    schedule 29.12.2015
comment
@Moshe Я могу изменить boundingRect, и он станет больше, как и ожидалось, но макет текста не изменится, поэтому токены также могут натыкаться на другой текст:\ чего я хочу избежать.   -  person jbrennan    schedule 29.12.2015
comment
Посмотрите, поможет ли переопределение lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect: в пользовательском текстовом контейнере. Похоже, ваш менеджер компоновки консультируется с текстовым контейнером для этого boundingRect, и это может быть правильным местом для его изменения.   -  person Moshe    schedule 29.12.2015
comment
@Моше потрясающе! Это именно то, что я искал! Попытка сделать это в NSLayoutManager очень сложна. NSTextContainer отлично подходит для этого.   -  person Sam Soffes    schedule 03.02.2016
comment
Подождите, это действительно сработало? ????   -  person Moshe    schedule 03.02.2016
comment
Как бы мы переопределили метод в NSTextContainer, чтобы сделать это?   -  person Matthew    schedule 17.11.2016
comment
@jbrennan ты смог найти решение? Я играл с boundingRect и столкнулся с теми же проблемами, что и вы. Я также играл с func setLocation(CGPoint, forStartOfGlyphRange: NSRange), и хотя это многообещающе, я не могу найти надежный способ, чтобы дополнение стало кумулятивным (например, если у меня есть несколько подстрок с TokenAttribute). Часть меня задается вопросом, потребует ли это погружения в механизм набора текста.   -  person Cory Juhlin    schedule 06.05.2019
comment
@CoryJuhlin ты смог это понять? У меня похожая проблема.   -  person Sam Corder    schedule 12.05.2019
comment
@SamCorder, к сожалению, нет :( пришлось решить проблему и двигаться дальше из-за крайних сроков, но я надеюсь вернуться к ней в ближайшее время!   -  person Cory Juhlin    schedule 13.05.2019
comment
@SamCorder Вам удалось решить проблему? В настоящее время я делаю это путем определения пользовательского NSTextAttachment, но это создает изображение, которое визуально прекрасно, но не идеально, потому что его нельзя выбрать.   -  person Gene S    schedule 17.05.2019
comment
@GeneS Вот что я в итоге сделал. Мне нужно было отображать текст поверх различных фигур, и в итоге я нарисовал текст поверх изображения и прикрепил его к строке с атрибутами.   -  person Sam Corder    schedule 17.05.2019
comment
Кто-нибудь добился каких-либо успехов в реализации чего-то подобного? Переопределение lineFragmentRectForProposedRect:atIndex:writingDirection:remainingRect позволяет вам изменять прямоугольник строки, но вы получаете обратные вызовы только для прямоугольника, представляющего всю строку, а не текст, который вы хотите. Использование setLocation в пользовательском наборщике работает, но это смещает соответствующий текстовый рейнджер, так что он может перекрываться с другим текстом. Пользовательский NSTextAttachment в моем случае не подходит, так как мне нужно, чтобы пользователь мог редактировать символы текста токена по отдельности.   -  person Noah Gilmore    schedule 26.09.2019


Ответы (1)


Существуют методы, определенные в NSLayoutManagerDelegate, которые служат точками настройки на основе глифов.

Использовать

func layoutManager(_ layoutManager: NSLayoutManager, shouldGenerateGlyphs glyphs: UnsafePointer<CGGlyph>, properties props: UnsafePointer<NSLayoutManager.GlyphProperty>, characterIndexes charIndexes: UnsafePointer<Int>, font aFont: NSFont, forGlyphRange glyphRange: NSRange) -> Int

чтобы идентифицировать глифы, связанные с пробелами, окружающими интересующий вас диапазон, и пометить их, изменив их значение в массиве props на NSLayoutManager.GlyphProperty.controlCharacter. Затем передайте этот измененный массив в

NSLayoutManager.setGlyphs(_:properties:characterIndexes:font:forGlyphRange:)

После этого вы можете реализовать

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

чтобы снова определить интересующие глифы и вернуть предопределенное действие:

NSLayoutManager.ControlCharacterAction.whitespace

Это, в конце концов, позволяет вам реализовать

func layoutManager(_ layoutManager: NSLayoutManager, boundingBoxForControlGlyphAt glyphIndex: Int, for textContainer: NSTextContainer, proposedLineFragment proposedRect: NSRect, glyphPosition: NSPoint, characterIndex charIndex: Int) -> NSRect

чтобы изменить ограничивающую рамку, используемую для глифов. Просто верните соответствующие размеры. Это повлияет на следующий механизм компоновки.

Удачи!

person Peer Benderson    schedule 03.02.2020
comment
Отлично! Я не знал, что layoutManager:boundingBoxForControlGlyphAt:... вызывается только для .whitespace управляющих символов. Спасибо! - person Sam Soffes; 27.05.2020