4

背景:我在 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的背景(为了更明显) 在此处输入图像描述

而这个是iOS 6
在此处输入图像描述


更新:虽然我确定下面的所有答案都以他们自己的方式正确,但我通过 t0rst 最简单的方式找到了执行此操作的帖子。HelveticaNeueiOS5和iOS6都留有一点空间,不像HelveticaiOS5顶部没有空间,iOS6空间很小。

更新 2:多玩了一下,发现了小空间的大小。无需详细说明,空间是您字体大小的 1/6。所以为了弥补它,我写了

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

使用该代码,我每次都会遇到死角。请注意,这仅HelveticaNeue-Bold在 iOS5 和 iOS6 上进行了测试。我不能说别的。

4

5 回答 5

8

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

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

首先,CATextLayer顺便说一句:不久前在 iOS 5 中尝试解决 ' 的定位行为时,我尝试使用上限高度、UIFont 中的上升器等的所有组合,然后最终发现 ascent fromCTLineGetTypographicBounds是我需要的。在这个过程中,我发现 a) 从UIFont ascender,CTFontGetAscent和的上升CTLineGetTypographicBounds对于某些字体是不一致的,并且 b) 上升经常是奇怪的——要么裁剪重音,要么在上方留出很多空间。a) 的解决方案是知道使用哪个值。b) 没有真正的解决方案,除了通过偏移CATextLayer边界在上面留出足够的空间,如果它可能会有被剪掉的重音。

回到 iOS 6。如果你避免使用最糟糕的字体(从 6.0 开始,并且可能会发生变化),你仍然可以CATextLayer对其余字体进行编程定位。违规者是:AcademyEngravedLetPlainCourierHoeflerTextPalatino - 从视觉上看,这些家庭在 中正确定位(即没有剪辑)CATextLayer,但三个上升源都没有为您提供基线放置位置的可用指示。Helvetica.HelveticaNeueUI(又名系统字体)系列在 给出的上升沿基线正确定位UIFont ascender,但其他上升源没有用。

我所做的测试中的一些例子。示例文本以不同的颜色绘制了 3 次。坐标原点在灰色框的左上角。黑色文本CTLineDraw通过从 的上升向下偏移绘制CTLineGetTypographicBounds;绘制透明红色CATextLayer,边界等于灰色框;用位于灰色框原点的UIKit NSString附加部分和.drawAtPoint:withFont:UIFont

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

铜版光 iOS 6

2) iOS 5 下的CourierCATextLayer 。文本位置太高(红色),但从(黑色)CTLineDraw上升与定位匹配 - 所以我们可以从那里放置和更正。(蓝色)放置文本而不进行剪切。(Helvetica.HelveticaNeueUI在 iOS 6 中的行为如下)CTLineGetTypographicBoundsCATextLayerNSString drawAtPoint:withFont:

快递 iOS 5

3) iOS 6 下的CourierCATextLayer 。 (红色)现在放置文本,使其不被剪裁,但定位不再匹配从CTLineGetTypographicBounds(黑色)或从(蓝色)UIFont中使用的上升器的上升。NSString drawAtPoint:withFont:这对于程序化定位是不可用的。(AcademyEngravedLetPlainHoeflerTextPalatino在 iOS 6 中的行为也是如此)

快递 iOS 6

希望这有助于避免我浪费的一些时间,如果你想更深入一点,试试这个:

- (NSString*)reportInconsistentFontAscents
{
    NSMutableString*            results;
    NSMutableArray*             fontNameArray;
    CGFloat                     fontSize = 28;
    NSString*                   fn;
    NSString*                   sample = @"Éa3Çy";
    CFRange                     range;
    NSMutableAttributedString*  mas;
    UIFont*                     uifont;
    CTFontRef                   ctfont;
    CTLineRef                   ctline;
    CGFloat                     uif_ascent;
    CGFloat                     ctfont_ascent;
    CGFloat                     ctline_ascent;

    results = [NSMutableString stringWithCapacity: 10000];
    mas = [[NSMutableAttributedString alloc] initWithString: sample];
    range.location = 0, range.length = [sample length];

    fontNameArray = [NSMutableArray arrayWithCapacity: 250];
    for (fn in [UIFont familyNames])
        [fontNameArray addObjectsFromArray: [UIFont fontNamesForFamilyName: fn]];
    [fontNameArray sortUsingSelector: @selector(localizedCaseInsensitiveCompare:)];
    [fontNameArray addObject: [UIFont systemFontOfSize: fontSize].fontName];
    [fontNameArray addObject: [UIFont italicSystemFontOfSize: fontSize].fontName];
    [fontNameArray addObject: [UIFont boldSystemFontOfSize: fontSize].fontName];

    [results appendString: @"Font name\tUIFA\tCTFA\tCTLA"];

    for (fn in fontNameArray)
    {
        uifont = [UIFont fontWithName: fn size: fontSize];
        uif_ascent = uifont.ascender;

        ctfont = CTFontCreateWithName((CFStringRef)fn, fontSize, NULL);
        ctfont_ascent = CTFontGetAscent(ctfont);

        CFAttributedStringSetAttribute((CFMutableAttributedStringRef)mas, range, kCTFontAttributeName, ctfont);
        ctline = CTLineCreateWithAttributedString((CFAttributedStringRef)mas);
        ctline_ascent = 0;
        CTLineGetTypographicBounds(ctline, &ctline_ascent, 0, 0);

        [results appendFormat: @"\n%@\t%.3f\t%.3f\t%.3f", fn, uif_ascent, ctfont_ascent, ctline_ascent];

        if (fabsf(uif_ascent - ctfont_ascent) >= .5f // >.5 can round to pixel diffs in display
         || fabsf(uif_ascent - ctline_ascent) >= .5f)
            [results appendString: @"\t*****"];

        CFRelease(ctline);
        CFRelease(ctfont);
    }

    [mas release];

    return results;
}
于 2012-10-18T22:53:37.677 回答
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);
于 2012-11-21T13:00:58.923 回答
1

在等待最终解决方案的同时,我研究了 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
于 2012-10-09T07:03:00.533 回答
0

我认为同时支持你可以为文本图层创建一个类别,在类别中你可以有条件地为两个版本编码。与更改图像时对导航栏所做的相同。

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

于 2012-09-24T15:19:37.663 回答
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。

于 2012-09-27T09:34:07.433 回答