vertically align text in a CATextLayer?

后端 未结 15 1329
时光说笑
时光说笑 2020-12-10 10:41

I am working on a CATextLayer that I want to use in both Mac and iOS. Can I control the vertical alignment of the text within the layer?

In this particular case, I

相关标签:
15条回答
  • 2020-12-10 10:59

    As best I can tell, the answer to my question is "No."

    0 讨论(0)
  • 2020-12-10 11:01

    Maybe to late for answer, but you can calculate size of text and then set position of textLayer. Also you need to put textLayer textAligment mode to "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];
    
    0 讨论(0)
  • 2020-12-10 11:02

    There is nothing stopping you from creating a CALayer hierarchy with a generic CALayer (container) that has the CATextLayer as a sublayer.

    Instead of calculating font sizes for the CATextLayer, simply calculate the offset of the CATextLayer inside the CALayer so that it is vertically centred. If you set the alignment mode of the text layer to centred and make the width of the text layer the same as the enclosing container it also centres horizontally.

    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: ...)
    

    The sublayer frame is relative to its parent, so the calculation is fairly straightforward. No need to care at this point about font sizes. That's handled by your code dealing with the CATextLayer, not in the layout code.

    0 讨论(0)
  • 2020-12-10 11:04

    Updating this thread (for single and multi line CATextLayer), combining some answers above.

    class VerticalAlignedTextLayer : CATextLayer {
    
        func calculateMaxLines() -> Int {
            let maxSize = CGSize(width: frame.size.width, height: CGFloat(Float.infinity))
            let font = UIFont(descriptor: self.font!.fontDescriptor, size: self.fontSize)
            let charSize = font.lineHeight
            let text = (self.string ?? "") as! NSString
            let textSize = text.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil)
            let linesRoundedUp = Int(ceil(textSize.height/charSize))
            return linesRoundedUp
        }
        
        override func draw(in context: CGContext) {
            let height = self.bounds.size.height
            let fontSize = self.fontSize
            let lines = CGFloat(calculateMaxLines())
            let yDiff = (height - lines * fontSize) / 2 - lines * fontSize / 10
    
            context.saveGState()
            context.translateBy(x: 0, y: yDiff) // Use -yDiff when in non-flipped coordinates (like macOS's default)
            super.draw(in: context)
            context.restoreGState()
        }
    }
    
    0 讨论(0)
  • 2020-12-10 11:10

    I slightly modified this answer by @iamkothed. The differences are:

    • text height calculation is based on NSString.size(with: Attributes). I don't know if it's an improvement over (height-fontSize)/2 - fontSize/10, but I like to think that it is. Although, in my experience, NSString.size(with: Attributes) doesn't always return the most appropriate size.
    • added invertedYAxis property. It was useful for my purposes of exporting this CATextLayer subclass using AVVideoCompositionCoreAnimationTool. AVFoundation operates in "normal" y axis, and that's why I had to add this property.
    • Works only with NSString. You can use Swift's String class though, because it automatically casts to NSString.
    • It ignores CATextLayer.fontSize property and completely relies on CATextLayer.font property which MUST be a UIFont instance.

      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()
          }
      }
      
    0 讨论(0)
  • 2020-12-10 11:12

    It is an late answer, but I have the same question these days, and have solved the problem with following investigation.

    Vertical align depends on the text you need to draw, and the font you are using, so there is no one way solution to make it vertical for all cases.

    But we can still calculate the vertical mid point for different cases.

    According to apple's About Text Handling in iOS, we need to know how the text is drawn.

    For example, I am trying to make vertical align for weekdays strings: Sun, Mon, Tue, ....

    For this case, the height of the text depends on cap Height, and there is no descent for these characters. So if we need to make these text align to the middle, we can calculate the offset of the top of cap character, e.g. The position of the top of character "S".

    According to the the figure below:

    fig

    The top space for the capital character "S" would be

    font.ascender - font.capHeight
    

    And the bottom space for the capital character "S" would be

    font.descender + font.leading
    

    So we need to move "S" a little bit off the top by:

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

    That equals to:

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

    Then I can make the text vertical align middle.

    Conclusion:

    1. If your text does not include any character exceed the baseline, e.g. "p", "j", "g", and no character over the top of cap height, e.g. "f". The you can use the formula above to make the text align vertical.

      y = (font.ascender + font.descender + font.leading) / 2
      
    2. If your text include character below the baseline, e.g. "p", "j", and no character exceed the top of cap height, e.g. "f". Then the vertical formula would be:

      y = (font.ascender + font.descender) / 2
      
    3. If your text include does not include character drawn below the baseline, e.g. "j", "p", and does include character drawn above the cap height line, e.g. "f". Then y would be:

      y = (font.descender + font.leading) / 2
      
    4. If all characters would be occurred in your text, then y equals to:

      y = font.leading / 2
      
    0 讨论(0)
提交回复
热议问题