Получение глифа boundingRect в draw#rect в UILabel

Используя Swift, я хочу получить boundingRect глифа в draw#rect в UILabel.

UILabel уже имеет размер (скажем, 300x300 в примере) и качества, такие как центрирование текста.

class RNDLabel: UILabel {

    override func draw(_ rect: CGRect) {

        let manager = NSLayoutManager()

        let store = NSTextStorage(attributedString: NSAttributedString(
            string: text!,
            attributes: [NSAttributedString.Key.font: font]))
        store.addLayoutManager(manager)

        let textContainer = NSTextContainer(size: rect.size)
        // note, intrinsicContentSize is identical there, no difference
        manager.addTextContainer(textContainer)

        let glyphRange = manager.glyphRange(
           forCharacterRange: NSRange(location: 0, length: 1),
           actualCharacterRange: nil)
        let glyphRect = manager.boundingRect(
           forGlyphRange: glyphRange, in: textContainer)

        print("glyphRect \(glyphRect)")         
        ...context?.addRect(glyphRect), context?.drawPath(using: .stroke)

        super.draw(rect)
    }

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

Зеленый квадрат не правильный - он должен быть больше похож на эти красные квадраты!

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

Кажется, есть ряд проблем:

  • конечно, layoutManager, который я делаю, должен получить качества UILabel, например, «текст по центру»? (Я полагаю, что вы не можете получить прямой доступ к layoutManager UILabel?)

  • мы должны использовать что-то вроде CTLineGetOffsetForStringIndex? Это вообще возможно в draw#rect

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

Как?

Для справки, моя общая цель состоит в том, чтобы перемещать глиф, основываясь на фактическом поле глифа (которое, конечно, отличается для «y», «X» и т. д.). Но вообще есть много полезных причин знать поле глифа.


person Fattie    schedule 08.07.2019    source источник
comment
Проще говоря, разве проблема не в том, что стек TextKit не имеет прямого отношения к UILabel? Рисунок UILabel не является рисунком TextKit. То, что вы просите сделать, было бы намного проще, если бы это был UITextView или просто представление, которое вы рисуете сами с помощью TextKit.   -  person matt    schedule 10.07.2019
comment
@matt - спасибо - (1) я просто не знаю. (2) Я понимаю, ну, я слышал, что в целом UItextView более податлив. Однако (3) я хочу сделать это в UILabel, и вы думаете, что есть способ. Вы можете видеть, что код компилируется и работает нормально, возможно, просто отсутствует смещение?   -  person Fattie    schedule 10.07.2019
comment
Да, но вам будет трудно узнать, что это за смещение. можно было бы подумать, что есть способ Нет, я бы не стал. Возможно, вы так подумали бы. Лично, если бы у меня была такая цель, как перетаскивание глифов, я бы не начинал с UILabel. Я из тех людей, которые предпочитают использовать структуру, а не бороться с ней.   -  person matt    schedule 10.07.2019
comment
Ясно - вы так думаете, потому что принципиально они не выставляют layoutManager в UILabel? {Мне приходит в голову, что можно сделать поддельный, закадровый UITextView с теми же размерами и качествами, и найти правильный?? Или .... мы не можем просто установить известные нам качества (из UILabel) в layoutManager, который мы там делаем??}   -  person Fattie    schedule 10.07.2019
comment
В основном, да. UITextView можно заставить действовать так же, как UILabel (сделать его недоступным для редактирования и прокрутки), но с тем важным отличием, что весь стек текстового набора отображается напрямую. Вот с чего я бы начал. То, что вы делаете, зависит от вас, конечно.   -  person matt    schedule 10.07.2019
comment
они не раскрывают layoutManager в UILabel. Почему вы считаете, что в UILabel есть менеджер компоновки? Я не вижу ни одного, использующего lldb или Hopper. Я мог что-то упустить, но я бы не ожидал, что Apple модернизирует UILabel для использования NSLayoutManager. Это возможно, но есть ли у вас основания полагать, что это правда? Декомпилируя drawText(in:), он вроде бы вызывает textRect(forBounds:), который вроде бы делает верстку вручную. Я не вижу никаких менеджеров компоновки.   -  person Rob Napier    schedule 15.07.2019
comment
Я строил то, что вы описываете, несколько раз в прошлом; Я всегда просто делал макет с основным текстом. Если бы я писал это снова сегодня, возможно, я бы использовал TextKit. Но я не понимаю, почему вы пытаетесь построить это на UILabel. Если это просто любопытство, это здорово, но я бы провел некоторое время в Хоппере, чтобы посмотреть, как UILabel работает под прикрытием. Он в основном построен на CoreGraphics и NSAttributedString, а не на CoreText или NSLayoutManager.   -  person Rob Napier    schedule 15.07.2019
comment
Кстати, я подумал, а почему бы и нет? Я просто реконструирую -[UILabel _drawTextInRect:baselineCalculationOnly:] достаточно, чтобы решить эту проблему и хахахахахах. Декомпиляция Хоппера — это почти 1000 строк кода на псевдо-C, наполненных особыми случаями и хахахами. Неа. Может быть, это забавный проект, но я бы не стал с ним заморачиваться. Просто создайте простую метку с помощью TextKit или CoreText, которая работает так, как вы хотите, и перетащите ее.   -  person Rob Napier    schedule 15.07.2019
comment
Привет @RobNapier Но я не понимаю, почему вы пытаетесь построить это на UILabel (то же самое, что и @matt), ответить проще некуда - шок и удивление немного ошеломляют? - Наверняка вы работали над большими проектами? Представьте себе какую-нибудь широко используемую функцию от предыдущих команд, которая использует UILabel: естественно, вы попытаетесь просто модифицировать ее. Ваше прекрасное расследование (1000 строк..!) кажется последним словом, спасибо! Так что просто добавьте textview сверху.   -  person Fattie    schedule 15.07.2019
comment
Неа; ты прав. Если вы копаетесь в системе текстового макета в течение нескольких лет и знаете историю, вам кажется очевидным, что настройка UILabel никогда не является тем, что вам нужно, и что она очень индивидуальна (и особенно то, что она предшествует CoreText немного , и TextKit намного), но это не очевидно, если вы подошли к нему свежим. Но попытка точного соответствия UILabel или его нетривиальная модификация — это классическая головная боль, которой лучше избегать.   -  person Rob Napier    schedule 15.07.2019


Ответы (2)


Оказывается, ответ на этот вопрос выглядит так:

На самом деле, удивительно это или нет, у вас в основном нет доступа к информации о глифах конкретно в UILabel.

Так что на самом деле вы не можете получить фактические формы этих глифов в UILabel.

В частности РобН. указал, что исследование показало, что в UILabel _drawTextInRect:baselineCalculationOnly работает, и это большая куча специального кода.

Резюме ситуации, казалось бы, заключается в том, что UILabel просто предшествует NSLayoutManager/Core Text и (пока) просто не использует эти современные системы.

Только те современные системы дают вам это...

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

... своего рода доступ к фигурам глиф за глифом.

person Fattie    schedule 15.07.2019

Ваш первоначальный вопрос является своего рода невопросом, потому что он говорит о двух совершенно противоположных вещах.

  • С одной стороны, вы говорите UILabel.

  • С другой стороны, вы используете такие термины, как NSLayoutManager и NSTextStorage и NSTextContainer — стек TextKit.

Это противоположности, потому что UILabel не рисуется со стеком TextKit. Итак, то, что вы просите сделать, похоже на попытку поднять лист бумаги с помощью магнита; магниты действительно поднимают предметы, но то, что они поднимают, — это магнитные материалы, а бумага к ним не относится. Безусловно, UILabel должен рисовать каким-то детерминированным способом, но что это такое, совершенно неизвестно и непрозрачно, и не имеет ничего общего с TextKit.

С другой стороны, UITextView или представление, которое вы сами рисуете с помощью TextKit, действительно использует TextKit, и теперь применяются все инструменты TextKit. Итак, то, что вы просите сделать, было бы намного проще, если бы это было UITextView (или представлением, которое вы рисуете самостоятельно с помощью TextKit).

Это не должно сильно менять внешний вид интерфейса. UITextView можно заставить действовать так же, как UILabel (сделать его недоступным для редактирования и прокрутки), но с тем важным отличием, что весь стек текстового набора отображается напрямую. Вот с чего я бы начал. Я из тех, кто предпочитает использовать фреймворк, а не бороться с ним.

person matt    schedule 17.07.2019