Вертикально выравнивать текст в CATextLayer?

Я работаю над CATextLayer, который я хочу использовать как на Mac, так и на iOS. Могу ли я контролировать вертикальное выравнивание текста внутри слоя?

В этом конкретном случае я хочу центрировать его по вертикали, но информация о других вертикальных выравниваниях также будет интересна.

EDIT: я нашел этот, но я не могу заставить его работать.

Ответ 1

Насколько я могу судить, ответ на мой вопрос: "Нет".

Ответ 2

Правильный ответ, как вы уже нашли, находится здесь в Objective-C и работает для iOS. Он работает путем создания подкласса CATextLayer и переопределения функции drawInContext.

Тем не менее, я сделал некоторые улучшения в коде, как показано ниже, используя в качестве основы код Дэвида Хёрла. Изменения происходят исключительно при пересчете вертикальной позиции текста, представленного yDiff. Я проверил это с моим собственным кодом.

Вот код для пользователей Swift:

class LCTextLayer : CATextLayer {

    // REF: http://lists.apple.com/archives/quartz-dev/2008/Aug/msg00016.html
    // CREDIT: David Hoerl - https://github.com/dhoerl 
    // USAGE: To fix the vertical alignment issue that currently exists within the CATextLayer class. Change made to the yDiff calculation.

    override func draw(in context: CGContext) {
        let height = self.bounds.size.height
        let fontSize = self.fontSize
        let yDiff = (height-fontSize)/2 - fontSize/10

        context.saveGState()
        context.translateBy(x: 0, y: yDiff) // Use -yDiff when in non-flipped coordinates (like macOS default)
        super.draw(in: context)
        context.restoreGState()
    }
}

Ответ 3

Может быть, опоздать на ответ, но вы можете рассчитать размер текста, а затем установить положение textLayer. Также вам нужно поместить режим textLayer textAligment в "center"

CGRect labelRect = [text boundingRectWithSize:view.bounds.size options:NSStringDrawingUsesLineFragmentOrigin attributes:@{ NSFontAttributeName : [UIFont fontWithName:@"HelveticaNeue" size:17.0] } context:nil];
CATextLayer *textLayer = [CATextLayer layer];
[textLayer setString:text];
[textLayer setForegroundColor:[UIColor redColor].CGColor];
[textLayer setFrame:labelRect];
[textLayer setFont:CFBridgingRetain([UIFont fontWithName:@"HelveticaNeue" size:17.0].fontName)];
[textLayer setAlignmentMode:kCAAlignmentCenter];
[textLayer setFontSize:17.0];
textLayer.masksToBounds = YES;
textLayer.position = CGPointMake(CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds));
[view.layer addSublayer:textLayer];

Ответ 4

Это поздний ответ, но у меня сейчас тот же вопрос, и я решил проблему с последующим расследованием.

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

Но мы все еще можем рассчитать вертикальную среднюю точку для разных случаев.

Согласно заявлению Apple About Text Handling в iOS, нам нужно знать, как рисуется текст.

Например, я пытаюсь сделать вертикальное выравнивание для строк будних дней: Sun, Mon, Tue,....

В этом случае высота текста зависит от высоты заголовка, и для этих символов спуск отсутствует. Таким образом, если нам нужно выровнять этот текст по центру, мы можем вычислить смещение верхней части верхнего символа, например, положение верхней части символа "S".

Согласно рисунку ниже:

fig

Верхний пробел для заглавной буквы "S" будет

font.ascender - font.capHeight

И нижний пробел для заглавной буквы "S" будет

font.descender + font.leading

Поэтому нам нужно немного сдвинуть "S" с вершины:

y = (font.ascender - font.capHeight + font.descender + font.leading + font.capHeight) / 2

Это равно:

y = (font.ascender + font.descender + font.leading) / 2

Затем я могу сделать текст вертикально выровненным по середине.

Заключение:

  1. Если ваш текст не содержит каких-либо символов, превышающих базовую линию, например, "p", "j", "g", и ни одного символа над вершиной высоты кепки, например, "f". Вы можете использовать формулу выше, чтобы выровнять текст по вертикали.

    y = (font.ascender + font.descender + font.leading) / 2
    
  2. Если ваш текст содержит символ ниже базовой линии, например, "p", "j", и ни один символ не превышает высоту верхнего уровня, например "f". Тогда вертикальная формула будет:

    y = (font.ascender + font.descender) / 2
    
  3. Если текст включает, не включает символ, нарисованный ниже базовой линии, например, "j", "p", и включает символ, нарисованный выше линии высоты кепки, например, "f". Тогда у будет:

    y = (font.descender + font.leading) / 2
    
  4. Если в вашем тексте встречаются все символы, то y равно:

    y = font.leading / 2
    

Ответ 5

Версия Swift 3 для регулярных и присваиваемых строк.

class ECATextLayer: CATextLayer {
    override open func draw(in ctx: CGContext) {
        let yDiff: CGFloat
        let fontSize: CGFloat
        let height = self.bounds.height

        if let attributedString = self.string as? NSAttributedString {
            fontSize = attributedString.size().height
            yDiff = (height-fontSize)/2
        } else {
            fontSize = self.fontSize
            yDiff = (height-fontSize)/2 - fontSize/10
        }

        ctx.saveGState()
        ctx.translateBy(x: 0.0, y: yDiff)
        super.draw(in: ctx)
        ctx.restoreGState()
    }
}

Ответ 6

Благодарю @iamktothed, он работает. следующая - быстрая версия 3:

class CXETextLayer : CATextLayer {

override init() {
    super.init()
}

override init(layer: Any) {
    super.init(layer: layer)
}

required init(coder aDecoder: NSCoder) {
    super.init(layer: aDecoder)
}

override func draw(in ctx: CGContext) {
    let height = self.bounds.size.height
    let fontSize = self.fontSize
    let yDiff = (height-fontSize)/2 - fontSize/10

    ctx.saveGState()
    ctx.translateBy(x: 0.0, y: yDiff)
    super.draw(in: ctx)
    ctx.restoreGState()
}
}

Ответ 7

работает код gbk. ниже обновлен код gbk для XCode 8 beta 6. Текущий на 1 октября 2016 г.

Шаг 1. Подкласс CATextLayer. В приведенном ниже коде я назвал подкласс "MyCATextLayer" Вне вашего класса контроллера просмотра скопируйте/вставьте приведенный ниже код.

class MyCATextLayer: CATextLayer {

// REF: http://lists.apple.com/archives/quartz-dev/2008/Aug/msg00016.html
// CREDIT: David Hoerl - https://github.com/dhoerl
// USAGE: To fix the vertical alignment issue that currently exists within the CATextLayer class. Change made to the yDiff calculation.

override init() {
    super.init()
}

required init(coder aDecoder: NSCoder) {
    super.init(layer: aDecoder)
}

override func draw(in ctx: CGContext) {
    let height = self.bounds.size.height
    let fontSize = self.fontSize
    let yDiff = (height-fontSize)/2 - fontSize/10

    ctx.saveGState()
    ctx.translateBy(x: 0.0, y: yDiff)
    super.draw(in: ctx)
    ctx.restoreGState()
   }
}

Шаг 2. В вашем классе контроллера класса в файле .swift создайте свой CATextLabel. В примере кода я назвал подкласс "MyDopeCATextLayer".

let MyDopeCATextLayer: MyCATextLayer = MyCATextLayer()

Шаг 3. Установите новый CATextLayer с нужным текстом/цветом/границами/фреймом.

MyDopeCATextLayer.string = "Hello World"    // displayed text
MyDopeCATextLayer.foregroundColor = UIColor.purple.cgColor //color of text is purple
MyDopeCATextLayer.frame = CGRect(x: 0, y:0, width: self.frame.width, height: self.frame.height)  
MyDopeCATextLayer.font = UIFont(name: "HelveticaNeue-UltraLight", size: 5) //5 is ignored, set actual font size using  ".fontSize" (below)
MyDopeCATextLayer.fontSize = 24
MyDopeCATextLayer.alignmentMode = kCAAlignmentCenter //Horizontally centers text.  text is automatically centered vertically because it set in subclass code
MyDopeCATextLayer.contentsScale = UIScreen.main.scale  //sets "resolution" to whatever the device is using (prevents fuzzyness/blurryness)

Шаг 4. сделанный

Ответ 8

Код для Swift 3, основанный на коде @iamktothed

Если вы используете атрибутную строку для установки свойств шрифта, вы можете использовать функцию size() из NSAttributedString для вычисления высоты строки. Я думаю, что этот код также разрешает проблемы, описанные @Enix

class LCTextLayer: CATextLayer {

    override init() {
        super.init()
    }

    override init(layer: Any) {
        super.init(layer: layer)
    }

    required init(coder aDecoder: NSCoder) {
        super.init(layer: aDecoder)
    }

    override open func draw(in ctx: CGContext) {

        if let attributedString = self.string as? NSAttributedString {

            let height = self.bounds.size.height
            let stringSize = attributedString.size()
            let yDiff = (height - stringSize.height) / 2

            ctx.saveGState()
            ctx.translateBy(x: 0.0, y: yDiff)
            super.draw(in: ctx)
            ctx.restoreGState()
        }
    }
}

Ответ 9

Таким образом, нет прямого способа сделать это, но вы можете сделать то же самое, используя текстовые метрики:

http://developer.apple.com/library/ios/#documentation/UIKit/Reference/NSString_UIKit_Additions/Reference/Reference.html

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

Ответ 10

Вам нужно знать, где CATextLayer поместит исходный текст вашего текста. Как только вы это знаете, смещайте систему координат внутри слоя, т.е. Скорректируйте bounds.origin.y на разницу между тем, где базовая линия обычно сидит и где вы хотите, учитывая метрики шрифта.

CATextLayer - это немного черный ящик, и найти, где будет сидеть базовый уровень, немного сложно - см. мой ответ здесь для iOS - я понятия не имею, что поведение находится на Mac.

Ответ 11

Я немного изменил этот ответ @iamkothed. Различия:

  • Высота текста рассчитывается на основе NSString.size(with: Attributes). Я не знаю, если это улучшение по сравнению с (height-fontSize)/2 - fontSize/10, но мне нравится думать, что это так. Хотя, по моему опыту, NSString.size(with: Attributes) не всегда возвращает наиболее подходящий размер.
  • добавлено свойство invertedYAxis. Это было полезно для моих целей экспорта этого CATextLayer подкласса с использованием AVVideoCompositionCoreAnimationTool. AVFoundation работает по "нормальной" оси Y, и именно поэтому мне пришлось добавить это свойство.
  • Работает только с NSString. Вы можете использовать класс Swift String, потому что он автоматически приводит к NSString.
  • Он игнорирует свойство CATextLayer.fontSize и полностью полагается на свойство CATextLayer.font которое ДОЛЖНО быть экземпляром UIFont.

    class VerticallyCenteredTextLayer: CATextLayer {
        var invertedYAxis: Bool = true
    
        override func draw(in ctx: CGContext) {
            guard let text = string as? NSString, let font = self.font as? UIFont else {
                super.draw(in: ctx)
                return
            }
    
            let attributes = [NSAttributedString.Key.font: font]
            let textSize = text.size(withAttributes: attributes)
            var yDiff = (bounds.height - textSize.height) / 2
            if !invertedYAxis {
                yDiff = -yDiff
            }
            ctx.saveGState()
            ctx.translateBy(x: 0.0, y: yDiff)
            super.draw(in: ctx)
            ctx.restoreGState()
        }
    }
    

Ответ 12

Я хотел бы предложить решение, которое учитывает многострочную переноску внутри доступного поля:

final class CACenteredTextLayer: CATextLayer {
    override func draw(in ctx: CGContext) {
        guard let attributedString = string as? NSAttributedString else { return }

        let height = self.bounds.size.height
        let boundingRect: CGRect = attributedString.boundingRect(
            with: CGSize(width: bounds.width,
                         height: CGFloat.greatestFiniteMagnitude),
            options: NSStringDrawingOptions.usesLineFragmentOrigin,
            context: nil)
        let yDiff: CGFloat = (height - boundingRect.size.height) / 2

        ctx.saveGState()
        ctx.translateBy(x: 0.0, y: yDiff)
        super.draw(in: ctx)
        ctx.restoreGState()
    }
}

Ответ 13

Ничто не мешает вам создать иерархию CALayer с универсальным CALayer (контейнером), в котором CATextLayer используется в качестве подслоя.

Вместо расчета размеров шрифта для CATextLayer, просто рассчитайте смещение CATextLayer внутри CALayer, чтобы он был центрирован по вертикали. Если вы установили режим выравнивания текстового слоя по центру и сделали ширину текстового слоя такой же, как у контейнера, он также центрируется по горизонтали.

let container = CALayer()
let textLayer = CATextLayer()

// create the layer hierarchy
view.layer.addSublayer(container)
container.addSublayer(textLayer)

// Setup the frame for your container
...


// Calculate the offset of the text layer so that it is centred
let hOffset = (container.frame.size.height - textLayer.frame.size.height) * 0.5

textLayer.frame = CGRect(x:0.0, y: hOffset, width: ..., height: ...)

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