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 の両方をサポートしたいからです (Google マップを好む人向け)。

写真:
これは iOS 5 で、赤い色は textLayer の背景です (わかりやすくするため) ここに画像の説明を入力

そしてこれはiOS 6です
ここに画像の説明を入力


更新:以下のすべての回答がそれぞれの方法で正しいと確信していますが、これを実行する最も簡単な方法による投稿を見つけました。HelveticaNeueiOS5 では上部にスペースがなく、iOS6 ではほとんどスペースが残らないのとは異なり、iOS5 と iOS6 の両方Helveticaに少しのスペースが残ります。

更新 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 の最初のベースラインは、最初の行の文字列で作成されたa を渡されたときにCATextLayer取得された上昇によって、常に境界の上から下に配置されます。CTLineGetTypographicBoundsCTLine

iOS 6 では、これはすべてのフォントに当てはまりません。したがって、 を配置するときCATextLayerに、適切な視覚的配置を得るためにどこに配置するかを確実に決定することはできなくなりました。それともできますか?...

まず、余談ですがCATextLayer、少し前に iOS 5 で のポジショニング動作を解決しようとしたとき、キャップの高さ、UIFont からのアセンダーなどのすべての組み合わせを使用してみましCTLineGetTypographicBoundsた。その過程で、a) と からの上昇が特定の書体で一貫していないことUIFont ascenderCTFontGetAscentおよびCTLineGetTypographicBoundsb) 上昇がしばしば奇妙であることに気付きました。a) の解決策は、使用する値を知ることです。CATextLayerクリッピングされるアクセントがある可能性が高い場合は、境界をオフセットして上に十分なスペースを残す以外に、b) に対する解決策は実際にはありません。

iOS 6 に戻ります。最悪の問題のある書体 (6.0 の時点で、おそらく変更される可能性があります) を回避する場合でもCATextLayer、残りの書体をプログラムで配置することができます。違反者は次のとおりです: AcademyEngravedLetPlainCourierHoeflerText、およびPalatino - 視覚的には、これらのファミリは で正しく (つまり、クリッピングCATextLayerなしで) 配置されますが、3 つの上昇ソースのいずれも、ベースラインが配置されている場所の有用な指標を提供しません。Helveticaおよび.HelveticaNeueUI (別名システム フォント) ファミリは、 で指定されたアセントでベースラインに正しく配置されUIFont ascenderますが、他のアセント ソースは役に立ちません。

私が行ったテストからのいくつかの例。サンプル テキストは、異なる色で 3 回描画されます。座標原点はグレーボックスの左上です。黒のテキストはCTLineDraw、 からの上昇分だけ下にオフセットして描画されCTLineGetTypographicBoundsます。透明な赤はCATextLayer、灰色のボックスに等しい境界で描画されます。透明な青は、灰色のボックスの原点に位置するUIKit NSString追加と で描画されます。drawAtPoint:withFont:UIFont

1) 動作の良いフォント、Copperplate-Light。3 つのサンプルは一致しており、あずき色を示しており、上昇がすべてのソースからほぼ同じであることを意味します。iOS 5 および 6 についても同様です。

銅版ライト iOS 6

2) iOS 5 でのクーリエCATextLayer。テキストの位置が高すぎます (赤) が、CTLineDrawそこから上昇するとCTLineGetTypographicBounds(黒)CATextLayer位置が一致するため、そこから配置して修正できます。NSString drawAtPoint:withFont:(青) クリッピングせずにテキストを配置します。( iOS 6 ではHelvetica.HelveticaNeueUIはこのように動作します)

宅配便 iOS 5

3) iOS 6 のクーリエ(赤) はテキストが切り取られないように配置されますが、配置は(黒) または(青)で使用されるアセンダーCATextLayerからの上昇と一致しなくなりました。これは、プログラムによる配置には使用できません。( Aca ​​demyEngravedLetPlainHoeflerTextPalatinoも iOS 6 ではこのように動作します)CTLineGetTypographicBoundsUIFontNSString drawAtPoint:withFont:

宅配便 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について学び、Steveが提案したように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 のフレームの上端に表示されます。

したがって、私が考えている解決策の 1 つは、コード内の textLayer オブジェクトを CATextLayer から CALayer (または CALayer のサブクラス) に変更し、Core Text を使用してコンテンツをカスタム描画し、一貫性のあるすべてのものを制御できるようにすることです。 iOS 5.0/5.1 および 6.0。

于 2012-09-27T09:34:07.433 に答える