来自iOS 6的CATextLayer上的不需要的垂直填充

时间:2012-09-24 14:54:26

标签: ios ipad ios6 layer catextlayer

后台:我在iOS 5中启动了我的项目,并构建了一个带图层的漂亮按钮。我在按钮上添加了一个textLayer,并使用以下代码将其居中:

    float textLayerVerticlePadding = ((self.bounds.size.height - fontSize) /2);
    textLayer = [[CATextLayer alloc]init];
    [textLayer setFrame:CGRectOffset(self.bounds, 0, textLayerVerticlePadding)];

效果很好,在iOS 6之前看起来很中心。

问题: iOS 6在textLayer中最顶层边界和文本之间添加了空格(填充)。这扰乱了上面的计算。有没有办法确保iOS 6没有?因为我想支持iOS 5和6(对于那些喜欢谷歌地图的人)。

图片:
这个是iOS 5,红色是textLayer的背景(使其更加明显) enter image description here

这个是iOS 6 enter image description here


更新:虽然我确定以下所有答案都是以自己的方式正确的,但我发现帖子是第一个最简单的方法来执行此操作。 HelveticaNeue为iOS5和iOS6留下了一点空间,与Helvetica不同,它在iOS5顶部没有空间,在iOS6中没有空间。

更新2:再玩一遍,找出小空间的大小。没有详细说明,空间是字体大小的1/6。所以为了弥补它我写的

float textLayerVerticlePadding = ((self.bounds.size.height - fontSize) /2) - (fontSize/6);
[textLayer setFrame:CGRectOffset(self.bounds, 0, textLayerVerticlePadding)];

有了这段代码,我每次都会得到一个死角。请注意,这仅在iOS5和iOS6上使用HelveticaNeue-Bold进行测试。我不能说其他任何事情。

5 个答案:

答案 0 :(得分:8)

在iOS 5及之前的版本中,CATextLayer中的第一个基线始终位于从CTLineGetTypographicBounds获得的上升时从边界顶部向下定位的CTLine第一行的字符串。

在iOS 6中,这不再适用于所有字体。因此,当您定位CATextLayer时,您无法再可靠地决定将其放置在何处以获得正确的视觉对齐。或者你呢? ...

首先,暂且不说:在iOS 5中尝试计算CATextLayer的定位行为时,我尝试使用帽子高度的所有组合,来自UIFont等的ascender,然后最终发现上升CTLineGetTypographicBounds是我需要的那个。在此过程中,我发现a)来自UIFont ascenderCTFontGetAscentCTLineGetTypographicBounds的上升对于某些字体是不一致的,并且b)上升经常是奇怪的 - 要么裁剪重音或离开通往上方的空间。 a)的解决方案是知道要使用哪个值。 b)除了通过偏移CATextLayer边界而留下足够的空间,如果它可能会有被剪裁的重音符号,则没有真正的解决方案。

返回iOS 6.如果您避免使用最严重的违规字体(从6.0开始,可能会有变化),您仍然可以使用其他字体对CATextLayer进行编程定位。违规者是: AcademyEngravedLetPlain Courier HoeflerText Palatino - 在视觉上,这些家庭的位置正确(即没有剪裁) )在CATextLayer中,但三个上升源中没有一个为您提供基线放置位置的可用指示。 Helvetica .HelveticaNeueUI (又称系统字体)系列在UIFont ascender给出的上升时正确定位基线,但其他上升源无法使用。< / p>

我做过的测试的一些例子。示例文本以不同颜色绘制三次。坐标原点位于灰色框的左上角。 CTLineDraw的黑色文字由CTLineGetTypographicBounds的上升向下偏移;透明红色由CATextLayer绘制,边界等于灰色框;透明蓝色是使用UIKit NSString加法drawAtPoint:withFont:绘制的,位于灰色框的原点和UIFont

1)表现良好的字体,Copperplate-Light。这三个样本是重合的,给出了栗色,并且意味着所有来源的上升距离足够近。 iOS 5和6也是如此。

copperplate-light iOS 6

iOS 5下的 Courier CATextLayer文字位置太高(红色),但CTLineDraw上升CTLineGetTypographicBounds(黑色)匹配{{ 1}}定位 - 所以我们可以从那里放置和纠正。 CATextLayer(蓝色)放置文本而不剪裁。 ( Helvetica .HelveticaNeueUI 在iOS 6中的行为如下)

courier iOS 5

iOS 6下的 Courier NSString drawAtPoint:withFont:(红色)现在放置文本,使其不会被剪裁,但定位不再与CATextLayer的上升匹配(黑色)或来自CTLineGetTypographicBounds(蓝色)中使用的UIFont上升者。这对于编程定位是不可用的。 ( AcademyEngravedLetPlain HoeflerText Palatino 在iOS 6中的行为也是如此)

courier iOS 6

希望这有助于避免我经历的浪费时间的一些时间,如果你想深入了解一下,可以玩一下:

NSString drawAtPoint:withFont:

答案 1 :(得分:3)

t0rst的回答对我有帮助。 我认为capHeight和xHeight是关键。

    CATextLayer *mytextLayer = [CATextLayer layer];
    CGFloat fontSize = 30;
    UIFont *boldFont = [UIFont boldSystemFontOfSize:fontSize];
    mytextLayer.font = (__bridge CFTypeRef)(boldFont.fontName);
    mytextLayer.fontSize = fontSize;

    CGFloat offsetY = 0;

    //if system version is grater than 6
    if(([[[UIDevice currentDevice] systemVersion] compare:@"6" options:NSNumericSearch] == NSOrderedDescending)){
        offsetY = -(boldFont.capHeight - boldFont.xHeight);
    }

    //you have to set textX, textY, textWidth
    mytextLayer.frame = CGRectMake(textX, textY + offsetY, textWidth, fontSize);

答案 2 :(得分:1)

Wile我正在等待一个终极解决方案,我研究了RTLabel和TTTAttributedLabel,并做了一个简单的类来在CALayer上绘制文本,正如史蒂夫建议的那样。希望它有所帮助,请不要犹豫,指出我所犯的任何错误。

CustomTextLayer.h

#import <QuartzCore/QuartzCore.h>

@interface CustomTextLayer : CALayer {
    NSString                        *_text;
    UIColor                         *_textColor;

    NSString                        *_font;
    float                           _fontSize;

    UIColor                         *_strokeColor;
    float                           _strokeWidth;

    CTTextAlignment                 _textAlignment;
    int                             _lineBreakMode;

    float                           _suggestHeight;
}

-(float) suggestedHeightForWidth:(float) width;

@property (nonatomic, retain) NSString *text;
@property (nonatomic, retain) UIColor *textColor;

@property (nonatomic, retain) NSString *font;
@property (nonatomic, assign) float fontSize;

@property (nonatomic, retain) UIColor *strokeColor;
@property (nonatomic, assign) float strokeWidth;

@property (nonatomic, assign) CTTextAlignment textAlignment;

@end

CustomTextLayer.m

#import <CoreText/CoreText.h>
#import "CustomTextLayer.h"

@implementation CustomTextLayer

@synthesize text = _text, textColor = _textColor;
@synthesize font = _font, fontSize = _fontSize;
@synthesize strokeColor = _strokeColor, strokeWidth = _strokeWidth;
@synthesize textAlignment = _textAlignment;

-(id) init {
    if (self = [super init]) {
        _text = @"";
        _textColor = [UIColor blackColor];

        _font = @"Helvetica";
        _fontSize = 12;

        _strokeColor = [UIColor whiteColor];
        _strokeWidth = 0.0;

        _textAlignment = kCTLeftTextAlignment;
        _lineBreakMode = kCTLineBreakByWordWrapping;

    }
    return self;
}

-(void) dealloc {
    [_text release];
    [_textColor release];

    [_font release];

    [_strokeColor release];

    [super dealloc];
}

-(void) setText:(NSString *)text {
    [_text release];
    _text = [text retain];
    [self setNeedsDisplay];
}

-(void) setTextColor:(UIColor *)textColor {
    [_textColor release];
    _textColor = [textColor retain];
    [self setNeedsDisplay];
}

-(void) setFont:(NSString *)font {
    [_font release];
    _font = [font retain];
    [self setNeedsDisplay];
}

-(void) setFontSize:(float)fontSize {
    _fontSize = fontSize;
    [self setNeedsDisplay];
}

-(void) setStrokeColor:(UIColor *)strokeColor {
    [_strokeColor release];
    _strokeColor = strokeColor;
    [self setNeedsDisplay];
}

-(void) setStrokeWidth:(float)strokeWidth {
    _strokeWidth = 0 ? (strokeWidth < 0) : (-1 * strokeWidth);
    [self setNeedsDisplay];
}

-(void) setTextAlignment:(CTTextAlignment)textAlignment {
    _textAlignment = textAlignment;
    [self setNeedsDisplay];
}

-(void) setFrame:(CGRect)frame {
    [super setFrame: frame];
    [self setNeedsDisplay];
}

-(float) suggestedHeightForWidth:(float) width {

    CTFontRef fontRef = CTFontCreateWithName((CFStringRef)_font, _fontSize, NULL);

    CTParagraphStyleSetting paragraphStyles[2] = {
        {.spec = kCTParagraphStyleSpecifierLineBreakMode, .valueSize = sizeof(CTLineBreakMode), .value = (const void *) &_lineBreakMode},
        {.spec = kCTParagraphStyleSpecifierAlignment, .valueSize = sizeof(CTTextAlignment), .value = (const void *) &_textAlignment}
    };
    CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(paragraphStyles, 2);

    NSDictionary *attrDict = [[NSDictionary alloc] initWithObjectsAndKeys:(id)fontRef, (NSString *)kCTFontAttributeName, (id)_textColor.CGColor, (NSString *)(kCTForegroundColorAttributeName), (id)_strokeColor.CGColor, (NSString *)(kCTStrokeColorAttributeName), (id)[NSNumber numberWithFloat: _strokeWidth], (NSString *)(kCTStrokeWidthAttributeName), (id)paragraphStyle, (NSString *)(kCTParagraphStyleAttributeName), nil];

    CFRelease(fontRef);
    CFRelease(paragraphStyle);

    NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:_text attributes: attrDict];

    // Determine suggested frame height
    CFRange textRange = CFRangeMake(0, [attrStr length]);
    CGSize constraint = CGSizeMake(width, 9999);

    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrStr);
    CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, textRange, NULL, constraint, NULL);
    textSize = CGSizeMake(ceilf(textSize.width), ceilf(textSize.height));

    [attrDict release];
    [attrStr release];

    return textSize.height;
}

-(void) renderText:(CGContextRef)ctx {
    CGContextSetTextMatrix(ctx, CGAffineTransformIdentity);
    CGContextTranslateCTM(ctx, 0, self.bounds.size.height);
    CGContextScaleCTM(ctx, 1.0, -1.0);

    CTFontRef fontRef = CTFontCreateWithName((CFStringRef)_font, _fontSize, NULL);

    CTParagraphStyleSetting paragraphStyles[2] = {
        {.spec = kCTParagraphStyleSpecifierLineBreakMode, .valueSize = sizeof(CTLineBreakMode), .value = (const void *) &_lineBreakMode},
        {.spec = kCTParagraphStyleSpecifierAlignment, .valueSize = sizeof(CTTextAlignment), .value = (const void *) &_textAlignment}
    };
    CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate(paragraphStyles, 2);

    NSDictionary *attrDict = [[NSDictionary alloc] initWithObjectsAndKeys:(id)fontRef, (NSString *)kCTFontAttributeName, (id)_textColor.CGColor, (NSString *)(kCTForegroundColorAttributeName), (id)_strokeColor.CGColor, (NSString *)(kCTStrokeColorAttributeName), (id)[NSNumber numberWithFloat: _strokeWidth], (NSString *)(kCTStrokeWidthAttributeName), (id)paragraphStyle, (NSString *)(kCTParagraphStyleAttributeName), nil];

    CFRelease(fontRef);
    CFRelease(paragraphStyle);

    NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:_text attributes: attrDict];

    CGMutablePathRef path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, self.bounds);
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attrStr);

    CFRange textRange = CFRangeMake(0, [attrStr length]);
    CTFrameRef frame = CTFramesetterCreateFrame(framesetter, textRange, path, NULL);
    CFArrayRef lines = CTFrameGetLines(frame);
    NSInteger numberOfLines = CFArrayGetCount(lines);
    CGPoint lineOrigins[numberOfLines];
    CTFrameGetLineOrigins(frame, CFRangeMake(0, numberOfLines), lineOrigins);

    for (CFIndex lineIndex = 0; lineIndex < numberOfLines; lineIndex++) {
        CGPoint lineOrigin = lineOrigins[lineIndex];
        CGContextSetTextPosition(ctx, lineOrigin.x,  lineOrigin.y);
        CTLineRef line = CFArrayGetValueAtIndex(lines, lineIndex);

        if (lineIndex == numberOfLines - 1) {
            CFRange lastLineRange = CTLineGetStringRange(line);

            if (!(lastLineRange.length == 0 && lastLineRange.location == 0) && lastLineRange.location + lastLineRange.length < textRange.location + textRange.length) {
                NSUInteger truncationAttributePosition = lastLineRange.location;
                CTLineTruncationType truncationType;
                if (numberOfLines != 1) {
                    truncationType = kCTLineTruncationEnd;
                    truncationAttributePosition += (lastLineRange.length - 1);
                }

                NSAttributedString *tokenString = [[NSAttributedString alloc] initWithString:@"\u2026" attributes:attrDict];
                CTLineRef truncationToken = CTLineCreateWithAttributedString((CFAttributedStringRef)tokenString);

                NSMutableAttributedString *truncationString = [[attrStr attributedSubstringFromRange: NSMakeRange(lastLineRange.location, lastLineRange.length)] mutableCopy];
                if (lastLineRange.length > 0) {
                    unichar lastCharacter = [[truncationString string] characterAtIndex: lastLineRange.length - 1];
                    if ([[NSCharacterSet newlineCharacterSet] characterIsMember:lastCharacter]) {
                        [truncationString deleteCharactersInRange:NSMakeRange(lastLineRange.length - 1, 1)];
                    }
                }
                [truncationString appendAttributedString: tokenString];
                CTLineRef truncationLine = CTLineCreateWithAttributedString((CFAttributedStringRef) truncationString);

                CTLineRef truncatedLine = CTLineCreateTruncatedLine(truncationLine, self.bounds.size.width, truncationType, truncationToken);
                if (!truncatedLine) {
                    // If the line is not as wide as the truncationToken, truncatedLine is NULL
                    truncatedLine = CFRetain(truncationToken);
                }

                CTLineDraw(truncatedLine, ctx);

                CFRelease(truncatedLine);
                CFRelease(truncationLine);
                CFRelease(truncationToken);
            } else {
                CTLineDraw(line, ctx);
            }
        } else {
            CTLineDraw(line, ctx);
        }
    }

    [attrStr release];
    [attrDict release];

    CFRelease(path);
    CFRelease(frame);
    CFRelease(framesetter);

}

-(void) drawInContext:(CGContextRef)ctx {
    [super drawInContext: ctx];
    [self renderText: ctx];
}

@end

答案 3 :(得分:0)

我认为支持你可以为文本图层创建一个类别,在类别中你可以为两个版本有条件地编码。 改变图像的时候和导航条一样。

您可以像对不同ios版本的不同框架一样使框架居中

答案 4 :(得分:0)

在我看来,iOS 6在绘制CATextLayer的文本内容时考虑了字体的线高(或影响字形的实际垂直绘制位置的其他字体相关特征)。结果是在iOS 6.0中,CATextLayer中具有特定字体的文本不会显示在CATextLayer的框架的顶部边缘。我发现某些字体有这样的垂直填充,而其他字体没有。在iOS 5.0 / 5.1中,文本的字形实际显示在CATextLayer框架的顶部边缘。

所以我想的一个可能的解决方案可能是将代码中的textLayer对象从CATextLayer更改为CALayer(或子类CALayer),并使用Core Text自定义绘制内容,以便控制所有内容在iOS 5.0 / 5.1和6.0中保持一致。