【iOS開發(fā)UI篇】繪制弧形文字

本文介紹了使用蘋果提供的CoreText框架繪制出弧形效果文字的方法

CoreText是由蘋果官方提供的文本引擎,它提供了多種控制文字布局的方式潜支,通過使用CoreText框架可以控制文字的位置、顏色暖途、尺寸等

蘋果官方提供了一段可以繪制弧形文字的代碼芜飘,使用起來非常方便泪蔫,地址是:
https://developer.apple.com/library/mac/samplecode/CoreTextArcCocoa/Introduction/Intro.html

注意:本文中我們將修改CoreTextArcView.m中的部分代碼堪簿,使得調用接口所得到的圖形更容易理解和辨識(源代碼調試起來比較花時間慨菱,這里將不做對比,只陳述修改后的調試策略戴甩,本篇代碼改動純屬個人喜好)

首先我們在工程中創(chuàng)建新的文件,這里我們就命名為CoreTextArcView闪彼,并將以下代碼導入到.h和.m文件中:

//  CoreTextArcView.h

#import <UIKit/UIKit.h>
#import <CoreText/CoreText.h>

@interface CoreTextArcView : UIView {
@private
    UIFont *            _font;
    NSString *          _string;
    CGFloat             _radius;
    UIColor *           _color;
    CGFloat             _arcSize;
    CGFloat             _shiftH, _shiftV; // horiz & vertical shift
    
    struct {
        unsigned int    showsGlyphBounds:1;
        unsigned int    showsLineMetrics:1;
        unsigned int    dimsSubstitutedGlyphs:1;
        unsigned int    reserved:29;
    }                   _flags;
}

@property(retain, nonatomic) UIFont *font;
@property(retain, nonatomic) NSString *text;
@property(readonly, nonatomic) NSAttributedString *attributedString;
@property(assign, nonatomic) CGFloat radius;
@property(nonatomic) BOOL showsGlyphBounds;
@property(nonatomic) BOOL showsLineMetrics;
@property(nonatomic) BOOL dimsSubstitutedGlyphs;
@property(retain, nonatomic) UIColor *color;
@property(nonatomic) CGFloat arcSize;
@property(nonatomic) CGFloat shiftH, shiftV;

- (instancetype)initWithFrame:(CGRect)frame font:(UIFont *)font text:(NSString *)text radius:(float)radius arcSize:(float)arcSize color:(UIColor *)color;

@end
//  CoreTextArcView.m

#import "CoreTextArcView.h"
#import <AssertMacros.h>
#import <QuartzCore/QuartzCore.h>

#define ARCVIEW_DEBUG_MODE          NO 

#define ARCVIEW_DEFAULT_FONT_NAME   @"Helvetica"
#define ARCVIEW_DEFAULT_FONT_SIZE   24.0
#define ARCVIEW_DEFAULT_RADIUS      -100.0
#define ARCVIEW_DEFAULT_ARC_SIZE    -80.0

@implementation CoreTextArcView

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.font = [UIFont fontWithName:ARCVIEW_DEFAULT_FONT_NAME size:ARCVIEW_DEFAULT_FONT_SIZE];
        self.text = @"Curva Style Label";
        self.radius = ARCVIEW_DEFAULT_RADIUS;
        self.showsGlyphBounds = NO;
        self.showsLineMetrics = NO;
        self.dimsSubstitutedGlyphs = NO;
        self.color = [UIColor grayColor];
        self.arcSize = ARCVIEW_DEFAULT_ARC_SIZE;
        self.shiftH = self.shiftV = 0.0f;
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame font:(UIFont *)font text:(NSString *)text radius:(float)radius arcSize:(float)arcSize color:(UIColor *)color{
    
    self = [super initWithFrame:frame];
    if (self) {
        self.font = font;
        self.text = text;
        self.radius = radius;
        self.showsGlyphBounds = NO;
        self.showsLineMetrics = NO;
        self.dimsSubstitutedGlyphs = NO;
        self.color = color;
        self.arcSize = arcSize;
        self.shiftH = self.shiftV = 0.0f;
    }
    return self;
}

typedef struct GlyphArcInfo {
    CGFloat         width;
    CGFloat         angle;  // in radians
} GlyphArcInfo;

static void PrepareGlyphArcInfo(CTLineRef line, CFIndex glyphCount, GlyphArcInfo *glyphArcInfo, CGFloat arcSizeRad)
{
    NSArray *runArray = (NSArray *)CTLineGetGlyphRuns(line);
    
    // Examine each run in the line, updating glyphOffset to track how far along the run is in terms of glyphCount.
    CFIndex glyphOffset = 0;
    for (id run in runArray) {
        CFIndex runGlyphCount = CTRunGetGlyphCount((CTRunRef)run);
        
        // Ask for the width of each glyph in turn.
        CFIndex runGlyphIndex = 0;
        for (; runGlyphIndex < runGlyphCount; runGlyphIndex++) {
            glyphArcInfo[runGlyphIndex + glyphOffset].width = CTRunGetTypographicBounds((CTRunRef)run, CFRangeMake(runGlyphIndex, 1), NULL, NULL, NULL);
        }
        
        glyphOffset += runGlyphCount;
    }
    
    double lineLength = CTLineGetTypographicBounds(line, NULL, NULL, NULL);
    
    CGFloat prevHalfWidth = glyphArcInfo[0].width / 2.0;
    glyphArcInfo[0].angle = (prevHalfWidth / lineLength) * arcSizeRad;
    
    // Divide the arc into slices such that each one covers the distance from one glyph's center to the next.
    CFIndex lineGlyphIndex = 1;
    for (; lineGlyphIndex < glyphCount; lineGlyphIndex++) {
        CGFloat halfWidth = glyphArcInfo[lineGlyphIndex].width / 2.0;
        CGFloat prevCenterToCenter = prevHalfWidth + halfWidth;
        
        glyphArcInfo[lineGlyphIndex].angle = (prevCenterToCenter / lineLength) * arcSizeRad;
        
        prevHalfWidth = halfWidth;
    }
}


// ensure that redraw occurs.
-(void)setText:(NSString *)text{
    _string = [NSString stringWithFormat:@"%@",text];
    
    [self setNeedsDisplay];
}

//set arc size in degrees (180 = half circle)
-(void)setArcSize:(CGFloat)degrees{
    _arcSize = degrees * M_PI/180.0;
}

//get arc size in degrees
-(CGFloat)arcSize{
    return _arcSize * 180.0/M_PI;
}

- (void)drawRect:(CGRect)rect {
    // Don't draw if we don't have a font or string
    if (self.font == NULL || self.text == NULL)
        return;
    
    // Initialize the text matrix to a known value
    CGContextRef context = UIGraphicsGetCurrentContext();
    //Reset the transformation
    //Doing this means you have to reset the contentScaleFactor to 1.0
    CGAffineTransform t0 = CGContextGetCTM(context);
    
    
    CGFloat xScaleFactor = t0.a > 0 ? t0.a : -t0.a;
    CGFloat yScaleFactor = t0.d > 0 ? t0.d : -t0.d;
    t0 = CGAffineTransformInvert(t0);
    if (xScaleFactor != 1.0 || yScaleFactor != 1.0)
        t0 = CGAffineTransformScale(t0, xScaleFactor, yScaleFactor);
    
    CGContextConcatCTM(context, t0);
    
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);
    
    if(ARCVIEW_DEBUG_MODE){
        // Draw a black background (debug)
        CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
        CGContextFillRect(context, self.layer.bounds);
    }
    
    NSAttributedString *attStr = self.attributedString;
    CFAttributedStringRef asr = (__bridge CFAttributedStringRef)attStr;
    CTLineRef line = CTLineCreateWithAttributedString(asr);
    assert(line != NULL);
    
    CFIndex glyphCount = CTLineGetGlyphCount(line);
    if (glyphCount == 0) {
        CFRelease(line);
        return;
    }
    
    GlyphArcInfo *  glyphArcInfo = (GlyphArcInfo*)calloc(glyphCount, sizeof(GlyphArcInfo));
    PrepareGlyphArcInfo(line, glyphCount, glyphArcInfo, _arcSize);
    
    // Move the origin from the lower left of the view nearer to its center.
    CGContextSaveGState(context);
    
    CGContextTranslateCTM(context, CGRectGetMidX(rect)+_shiftH, CGRectGetMidY(rect)+_shiftV - self.radius / 2.0); //注意這是要修改的一行
    
    if(ARCVIEW_DEBUG_MODE){
        // Stroke the arc in red for verification.
        CGContextBeginPath(context);
        CGContextAddArc(context, 0.0, 0.0, self.radius, M_PI_2+_arcSize/2.0, M_PI_2-_arcSize/2.0, 1);
        CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
        CGContextStrokePath(context);
    }
    
    // Rotate the context 90 degrees counterclockwise (per 180 degrees)
    CGContextRotateCTM(context, _arcSize/2.0);
    
    // Now for the actual drawing. The angle offset for each glyph relative to the previous glyph has already been calculated; with that information in hand, draw those glyphs overstruck and centered over one another, making sure to rotate the context after each glyph so the glyphs are spread along a semicircular path.
    
    CGPoint textPosition = CGPointMake(0.0, self.radius);
    CGContextSetTextPosition(context, textPosition.x, textPosition.y);
    
    CFArrayRef runArray = CTLineGetGlyphRuns(line);
    CFIndex runCount = CFArrayGetCount(runArray);
    
    CFIndex glyphOffset = 0;
    CFIndex runIndex = 0;
    for (; runIndex < runCount; runIndex++) {
        CTRunRef run = (CTRunRef)CFArrayGetValueAtIndex(runArray, runIndex);
        CFIndex runGlyphCount = CTRunGetGlyphCount(run);
        Boolean drawSubstitutedGlyphsManually = false;
        CTFontRef runFont = CFDictionaryGetValue(CTRunGetAttributes(run), kCTFontAttributeName);
        
        // Determine if we need to draw substituted glyphs manually. Do so if the runFont is not the same as the overall font.
        if (self.dimsSubstitutedGlyphs && ![self.font isEqual:(__bridge UIFont *)runFont]) {
            drawSubstitutedGlyphsManually = true;
        }
        
        CFIndex runGlyphIndex = 0;
        for (; runGlyphIndex < runGlyphCount; runGlyphIndex++) {
            CFRange glyphRange = CFRangeMake(runGlyphIndex, 1);
            CGContextRotateCTM(context, -(glyphArcInfo[runGlyphIndex + glyphOffset].angle));
            
            // Center this glyph by moving left by half its width.
            CGFloat glyphWidth = glyphArcInfo[runGlyphIndex + glyphOffset].width;
            CGFloat halfGlyphWidth = glyphWidth / 2.0;
            CGPoint positionForThisGlyph = CGPointMake(textPosition.x - halfGlyphWidth, textPosition.y);
            
            // Glyphs are positioned relative to the text position for the line, so offset text position leftwards by this glyph's width in preparation for the next glyph.
            textPosition.x -= glyphWidth;
            
            CGAffineTransform textMatrix = CTRunGetTextMatrix(run);
            textMatrix.tx = positionForThisGlyph.x;
            textMatrix.ty = positionForThisGlyph.y;
            CGContextSetTextMatrix(context, textMatrix);
            
            if (!drawSubstitutedGlyphsManually) {
                CTRunDraw(run, context, glyphRange);
            }
            else {
                // We need to draw the glyphs manually in this case because we are effectively applying a graphics operation by setting the context fill color. Normally we would use kCTForegroundColorAttributeName, but this does not apply as we don't know the ranges for the colors in advance, and we wanted demonstrate how to manually draw.
                CGFontRef cgFont = CTFontCopyGraphicsFont(runFont, NULL);
                CGGlyph glyph;
                CGPoint position;
                
                CTRunGetGlyphs(run, glyphRange, &glyph);
                CTRunGetPositions(run, glyphRange, &position);
                
                CGContextSetFont(context, cgFont);
                CGContextSetFontSize(context, CTFontGetSize(runFont));
                CGContextSetRGBFillColor(context, 0.25, 0.25, 0.25, 0.5);
                CGContextShowGlyphsAtPositions(context, &glyph, &position, 1);
                
                CFRelease(cgFont);
            }
            
            // Draw the glyph bounds
            if ((self.showsGlyphBounds) != 0) {
                CGRect glyphBounds = CTRunGetImageBounds(run, context, glyphRange);
                
                CGContextSetRGBStrokeColor(context, 0.0, 0.0, 1.0, 1.0);
                CGContextStrokeRect(context, glyphBounds);
            }
            // Draw the bounding boxes defined by the line metrics
            if ((self.showsLineMetrics) != 0) {
                CGRect lineMetrics;
                CGFloat ascent, descent;
                
                CTRunGetTypographicBounds(run, glyphRange, &ascent, &descent, NULL);
                
                // The glyph is centered around the y-axis
                lineMetrics.origin.x = -halfGlyphWidth;
                lineMetrics.origin.y = positionForThisGlyph.y - descent;
                lineMetrics.size.width = glyphWidth;
                lineMetrics.size.height = ascent + descent;
                
                CGContextSetRGBStrokeColor(context, 0.0, 1.0, 0.0, 1.0);
                CGContextStrokeRect(context, lineMetrics);
            }
        }
        
        glyphOffset += runGlyphCount;
    }
    
    CGContextSetFillColorWithColor(context, [UIColor clearColor].CGColor);
    CGContextSetAlpha(context,0.0);
    CGContextFillRect(context, rect);
    
    CGContextRestoreGState(context);
    
    free(glyphArcInfo);
    CFRelease(line);
}

@synthesize font = _font;
@synthesize text = _string;
@synthesize radius = _radius;
@synthesize color = _color;
@synthesize arcSize = _arcSize;
@synthesize shiftH = _shiftH;
@synthesize shiftV = _shiftV;

@dynamic attributedString;
- (NSAttributedString *)attributedString {
    // Create an attributed string with the current font and string.
    assert(self.font != nil);
    assert(self.text != nil);
    
    // Create our attributes...
    
    // font
    CTFontRef fontRef = CTFontCreateWithName((CFStringRef)self.font.fontName, self.font.pointSize, NULL);
    
    // color
    CGColorRef colorRef = self.color.CGColor;
    
    // pack it into attributes dictionary
    
    NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
                                    (__bridge id)fontRef, (id)kCTFontAttributeName,
                                    colorRef, (id)kCTForegroundColorAttributeName,
                                    nil];
    assert(attributesDict != nil);
    
    // Create the attributed string
    NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:self.text attributes:attributesDict];
    
    return attrString;
}

@dynamic showsGlyphBounds;
- (BOOL)showsGlyphBounds {
    return _flags.showsGlyphBounds;
}

- (void)setShowsGlyphBounds:(BOOL)show {
    _flags.showsGlyphBounds = show ? 1 : 0;
}

@dynamic showsLineMetrics;
- (BOOL)showsLineMetrics {
    return _flags.showsLineMetrics;
}

- (void)setShowsLineMetrics:(BOOL)show {
    _flags.showsLineMetrics = show ? 1 : 0;
}

@dynamic dimsSubstitutedGlyphs;
- (BOOL)dimsSubstitutedGlyphs {
    return _flags.dimsSubstitutedGlyphs;
}

- (void)setDimsSubstitutedGlyphs:(BOOL)dim {
    _flags.dimsSubstitutedGlyphs = dim ? 1 : 0;
}

@end

這段代碼是官方提供的范例甜孤,對于我們繪制弧形文字已經足夠使用,在此不做過多分析畏腕,我們只關注頭文件中提供的實例方法即可:

- (instancetype)initWithFrame:(CGRect)frame font:(UIFont *)font text:(NSString *)text radius:(float)radius arcSize:(float)arcSize color:(UIColor *)color;

通過調用這個方法我們就可以繪制出有弧形效果的文字了缴川,另外還可以為文字修改字體、內容描馅、弧度半徑把夸、張角、顏色等铭污,這里我們繪制多個文字進行對比來了解各個參數的意義恋日,尤其是弧度半徑radius和張角arcSize兩個參數

接下來我們將微調源代碼的這一行:

CGContextTranslateCTM(context, CGRectGetMidX(rect)+_shiftH, CGRectGetMidY(rect)+_shiftV - self.radius / 2.0);

這里是在移動參考上下文的原點的位置膀篮,可以看出原點的y值還和要繪制的弧度半徑相關,我們希望將原點的位置固定(只取決于底版view的尺寸)岂膳,不受要繪制的文字的參數影響誓竿,因此我們需將代碼修改為:

CGContextTranslateCTM(context, CGRectGetMidX(rect)+_shiftH, CGRectGetMidY(rect)+_shiftV);

這樣就可以了,具體效果后面我們會看到

準備工作就緒谈截,我們將開始繪制多個弧形文字來看下效果和使用方法是怎樣的

  • 首先我們創(chuàng)建一個作為背景的UIView對象筷屡,這個view的尺寸決定了弧形文字視圖的尺寸,view的高度和弧度半徑radius有關簸喂,view的寬度和張角arcSize有關毙死,詳細解釋后面會提到
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    UIView *arcBgView = [[UIView alloc] init];
    arcBgView.bounds = CGRectMake(0, 0, 300, 600);
    arcBgView.center = self.view.center;
    arcBgView.backgroundColor = [UIColor yellowColor];
    [self.view addSubview:arcBgView];
}
  • 接下來,在要繪制文字的文件中引入CoreTextArcView.h頭文件喻鳄,并調用接口創(chuàng)建CoreTextArcView實例并添加就可以了扼倘,例如
    CoreTextArcView *arcViewTest = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度半徑200張角45" radius:200 arcSize:45 color:[UIColor redColor]];
    arcViewTest.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcViewTest];

這樣就添加了一個弧形文字,效果如下诽表,注意黃色區(qū)域為高600寬300唉锌,圓弧的中心此時位于黃色區(qū)域的正中心,弧度半徑為200竿奏,略小于高的一半300

弧形文字測試界面
  • 一條文字可能不足以對比出效果袄简,我們固定張角,調整半徑泛啸,繪制多條文字
    CoreTextArcView *arcView1 = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度0張角30" radius:0 arcSize:30 color:[UIColor redColor]];
    arcView1.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcView1];
    
    CoreTextArcView *arcView2 = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度50張角30" radius:50 arcSize:30 color:[UIColor redColor]];
    arcView2.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcView2];
    
    CoreTextArcView *arcView3 = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度100張角30" radius:100 arcSize:30 color:[UIColor redColor]];
    arcView3.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcView3];
    
    CoreTextArcView *arcView4 = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度150張角30" radius:150 arcSize:30 color:[UIColor redColor]];
    arcView4.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcView4];
    
    CoreTextArcView *arcView5 = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度200張角30" radius:200 arcSize:30 color:[UIColor redColor]];
    arcView5.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcView5];
    
    CoreTextArcView *arcView6 = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度半徑250張角30" radius:250 arcSize:30 color:[UIColor redColor]];
    arcView6.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcView6];
    
    CoreTextArcView *arcView7 = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度半徑300張角30" radius:300 arcSize:30 color:[UIColor redColor]];
    arcView7.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcView7];
不同弧度半徑弧形文字對比圖

這里我們看到幾個問題:

第一绿语,最上面一條半徑為300的文字已經超出了邊框外,這是因為CoreTextArcView在繪制文字時所使用的半徑radius候址,其實是在文字下方首先繪制一條弧線吕粹,再將文字貼合弧線外側繪制,因此本圖中當radius已經和黃色區(qū)域高度的一半相等時(這里都為300)岗仑,弧線已經貼到了邊框頂部匹耕,文字自然就被繪制到邊框外了,因此在繪制時我們要注意控制radius略小于設定的frame的高度的一半即可

第二荠雕,我們使用了一個很大的邊框稳其,但是圖形只繪制到了邊框內部的上半部分,那么下半部分是不是沒有用呢炸卑?答案并非如此既鞠,因為除了上弧形的文字,我們還可以繪制下弧形的文字盖文,而使用的方法是相似的嘱蛋,只需要將radius和arcSize都變成負值即可

    CoreTextArcView *arcView1 = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度半徑0張角45" radius:-0 arcSize:-45 color:[UIColor redColor]];
    arcView1.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcView1];
    
    CoreTextArcView *arcView2 = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度半徑50張角45" radius:-50 arcSize:-45 color:[UIColor redColor]];
    arcView2.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcView2];
    
    CoreTextArcView *arcView3 = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度半徑100張角45" radius:-100 arcSize:-45 color:[UIColor redColor]];
    arcView3.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcView3];
    
    CoreTextArcView *arcView4 = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度半徑150張角45" radius:-150 arcSize:-45 color:[UIColor redColor]];
    arcView4.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcView4];
    
    CoreTextArcView *arcView5 = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度半徑200張角45" radius:-200 arcSize:-45 color:[UIColor redColor]];
    arcView5.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcView5];
    
    CoreTextArcView *arcView6 = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度半徑250張角45" radius:-250 arcSize:-45 color:[UIColor redColor]];
    arcView6.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcView6];
    
    CoreTextArcView *arcView7 = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度半徑300張角45" radius:-300 arcSize:-45 color:[UIColor redColor]];
    arcView7.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcView7];
將弧形文字繪制到下方

這里我們看到最外層radius為-300的文字沒有繪制到邊框外,因為負值半徑改變了弧形文字貼合弧形圖的方向,也就將文字繪制到了弧形內側洒敏,這里我們可以通過再繪制一個正半徑和一個負半徑全角度的文字來說明:

    //藍色的是正半徑龄恋,外圈繪制
    CoreTextArcView *arcViewOutFull = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度半徑150張角360弧度半徑150張角360弧度半徑150張角360" radius:150 arcSize:360 color:[UIColor blueColor]];
    arcViewOutFull.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcViewOutFull];
    
    //紅色的是負半徑,內圈繪制
    CoreTextArcView *arcViewInFull = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度半徑-150張角360弧度半徑-150張角360弧度半徑-150張角360" radius:-150 arcSize:360 color:[UIColor redColor]];
    arcViewInFull.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcViewInFull];
正半徑與負半徑對比圖

這樣我們就能看出來正負半徑帶來的繪制差異了桐玻,也就是半徑正值或負值將決定文字要繪制到弧形圖的外側或者內側篙挽,另外也可以看出,arcSize是用來決定文字展開的角度有多大的镊靴,文字將會根據這個角度自動填充間隙铣卡,arcSize如果為負值時,也會影響文字的排列順序偏竟,所以需要結合半徑是正值還是負值來使用才可以滿足不同的需求

到這里為止煮落,利用CoreTextArcView來繪制弧形文字的基本方法就說的差不多了,總結起來步驟大概是這樣的:

  1. 添加CoreTextArcView文件(可以是其他名稱)和代碼(根據個人喜好決定是否修改源代碼來增強使用效率)
  2. 添加一個view作為繪制弧形文字的背景底版踊谋,弧形的中心將是該view的中心點蝉仇,view的寬高設定一定要符合弧形文字的尺寸需要(寬度對應文字的張角arcSize,高度的一半對應文字的弧度半徑radius)
  3. 調用接口-initWithFrame:font:radius:arcSize:color: 來創(chuàng)建和配置所需要的弧形文字(注意半徑和張角的正值或負值將決定文字的繪制方式和樣式)
  4. 將創(chuàng)建好的弧形文字視圖的背景色設為透明色(否則會以餅狀形式覆蓋住圓心到文字之間的區(qū)域V巢稀)
  5. 添加到底版view上顯示即可

多說兩句

  1. 在文章開始我們提到過通過修改源代碼使得調用接口進行調試的結果變得簡單直觀轿衔,這只是個人喜好,本人沒有仔細鉆研過蘋果提供的源代碼睦疫,只是通過修改的方式找到了符合自己的更好使用的方法害驹,也就是在設置好參數后,腦海中就已經出現大致的圖形了蛤育,在使用源代碼時這個部分因為添加了self.radius的成分而使得半徑的變化摻雜了多余的影響因素宛官,對判斷位置有一些干擾,故而做出修改

  2. 前面提到過瓦糕,如果我們只繪制一半弧形文字時底洗,會浪費掉一半底版區(qū)域,也會給設置底版view的位置帶來一些麻煩咕娄,因此我們可以進一步修改亥揖,使得源代碼能夠在繪制上弧形文字時,將弧形中心移到view底部中心圣勒,在繪制下弧形文字時徐块,將弧形中心移到view頂部中心,這樣既可以不必創(chuàng)建過大的底版view灾而,也可以在將view添加到其他view上進行組合時,能更好的以弧形圓心為參照進行調整

這里我們整理三種繪制方式需要修改的代碼細節(jié):

// 弧形圓心位于底版view正中心扳剿,適用于所有繪制情況旁趟,尤其是弧形文字張角大于180度的情況(比如上面繪制一整個圓形)或者弧形文字跨越水平線(左弧形或右弧形)
CGContextTranslateCTM(context, CGRectGetMidX(rect)+_shiftH, CGRectGetMidY(rect)+_shiftV);

// 弧形圓心位于底版view底部中心,適用于上弧形
CGContextTranslateCTM(context, CGRectGetMidX(rect)+_shiftH, _shiftV);

// 弧形圓心位于底版view頂部中心,使用與下弧形
CGContextTranslateCTM(context, CGRectGetMidX(rect)+2*CGRectGetMidY(rect)_shiftH, -_shiftV);

我們添加一段測試代碼來檢測一下锡搜,首先是上弧形文字橙困,注意半徑略小于view的高度才能完整顯示

    CoreTextArcView *arcViewTopTest = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度半徑280張角45" radius:280 arcSize:45 color:[UIColor redColor]];
    arcViewTopTest.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcViewTopTest];
上弧形文字示例

接下來是下弧形文字

    CoreTextArcView *arcViewBottomTest = [[CoreTextArcView alloc] initWithFrame:arcBgView.bounds font:[UIFont systemFontOfSize:20] text:@"弧度半徑300張角45" radius:-300 arcSize:-45 color:[UIColor redColor]];
    arcViewBottomTest.backgroundColor = [UIColor clearColor];
    [arcBgView addSubview:arcViewBottomTest];
下弧形文字示例

簡單的變換使用就是這些了,除此之外還有其他很多種變化方法可以嘗試

【參考資料】
Github - Apple CoreText Framework CoreTextArcView
https://github.com/alokc83/iOS-Example-Collections/tree/master/WWDC_2012_SourceCode/iOS/226%20-%20Core%20Text%20and%20Fonts/CoreTextArcCocoa
Github - XMCircleType
https://github.com/MichMich/XMCircleType
stack overflow - Curve text on existing circle
http://stackoverflow.com/questions/3841642/curve-text-on-existing-circle/7114184#7114184

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末耕餐,一起剝皮案震驚了整個濱河市凡傅,隨后出現的幾起案子,更是在濱河造成了極大的恐慌肠缔,老刑警劉巖夏跷,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異明未,居然都是意外死亡槽华,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門趟妥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來猫态,“玉大人,你說我怎么就攤上這事披摄∏籽” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵疚膊,是天一觀的道長义辕。 經常有香客問我,道長酿联,這世上最難降的妖魔是什么终息? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮贞让,結果婚禮上周崭,老公的妹妹穿的比我還像新娘。我一直安慰自己喳张,他們只是感情好续镇,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著销部,像睡著了一般摸航。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上舅桩,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天酱虎,我揣著相機與錄音,去河邊找鬼擂涛。 笑死读串,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播恢暖,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼排监,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了杰捂?” 一聲冷哼從身側響起舆床,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎嫁佳,沒想到半個月后挨队,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡脱拼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年瞒瘸,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熄浓。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡情臭,死狀恐怖,靈堂內的尸體忽然破棺而出赌蔑,到底是詐尸還是另有隱情俯在,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布娃惯,位于F島的核電站跷乐,受9級特大地震影響,放射性物質發(fā)生泄漏趾浅。R本人自食惡果不足惜愕提,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望皿哨。 院中可真熱鬧浅侨,春花似錦、人聲如沸证膨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽央勒。三九已至不见,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間崔步,已是汗流浹背稳吮。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留井濒,地道東北人灶似。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓慎陵,卻偏偏與公主長得像,于是被迫代替她去往敵國和親喻奥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內容