IOS coretext

转载自:http://blog.csdn.net/fengsh998/article/details/8691823

IOS CoreText.framework --- 基本用法

分类: Iphone 14046人阅读 评论(8) 收藏 举报

API接口文档。

https://developer.apple.com/library/mac/#documentation/Carbon/Reference/CoreText_Framework_Ref/_index.html


CoreText 框架中最常用的几个类:

  1. CTFont
  2. CTFontCollection
  3. CTFontDescriptor
  4. CTFrame
  5. CTFramesetter
  6. CTGlyphInfo
  7. CTLine
  8. CTParagraphStyle
  9. CTRun
  10. CTTextTab
  11. CTTypesetter

先来了解一下该框架的整体视窗组合图:


CTFrame 作为一个整体的画布(Canvas),其中由行(CTLine)组成,而每行可以分为一个或多个小方块(CTRun)。

注意:你不需要自己创建CTRun,Core Text将根据NSAttributedString的属性来自动创建CTRun。每个CTRun对象对应不同的属性,正因此,你可以自由的控制字体、颜色、字间距等等信息。

通常处理步聚:

1.使用core text就是先有一个要显示的string,然后定义这个string每个部分的样式->attributedString -> 生成 CTFramesetter -> 得到CTFrame -> 绘制(CTFrameDraw)
其中可以更详细的设置换行方式,对齐方式,绘制区域的大小等。
2.绘制只是显示,点击事件就需要一个判断了。
CTFrame 包含了多个CTLine,并且可以得到各个line的其实位置与大小。判断点击处在不在某个line上。CTLine 又可以判断这个点(相对于ctline的坐标)处的文字范围。然后遍历这个string的所有NSTextCheckingResult,根据result的rang判断点击处在不在这个rang上,从而得到点击的链接与位置。


字体的基本知识:

字体(Font):是一系列字号、样式和磅值相同的字符(例如:10磅黑体Palatino)。现多被视为字样的同义词

字面(Face):是所有字号的磅值和格式的综合

字体集(Font family):是一组相关字体(例如:Franklin family包括Franklin Gothic、Fran-klinHeavy和Franklin Compressed)

磅值(Weight):用于描述字体粗度。典型的磅值,从最粗到最细,有极细、细、book、中等、半粗、粗、较粗、极粗

样式(Style):字形有三种形式:Roman type是直体;oblique type是斜体;utakuc type是斜体兼曲线(比Roman type更像书法体)。

x高度(X height):指小写字母的平均高度(以x为基准)。磅值相同的两字母,x高度越大的字母看起来比x高度小的字母要大

Cap高度(Cap height):与x高度相似。指大写字母的平均高度(以C为基准)

下行字母(Descender):例如在字母q中,基线以下的字母部分叫下伸部分

上行字母(Ascender):x高度以上的部分(比如字母b)叫做上伸部分

基线(Baseline):通常在x、v、b、m下的那条线

描边(Stroke):组成字符的线或曲线。可以加粗或改变字符形状

衬线(Serif):用来使字符更可视的一条水平线。如字母左上角和下部的水平线。

无衬线(Sans Serif):可以让排字员不使用衬线装饰。

方形字(Block):这种字体的笔画使字符看起来比无衬线字更显眼,但还不到常见的衬线字的程度。例如Lubalin Graph就是方形字,这种字看起来好像是木头块刻的一样

手写体脚本(Calligraphic script):是一种仿效手写体的字体。例如Murray Hill或者Fraktur字体

艺术字(Decorative):像绘画般的字体

Pi符号(Pisymbol):非标准的字母数字字符的特殊符号。例如Wingdings和Mathematical Pi

连写(Ligature):是一系列连写字母如fi、fl、ffi或ffl。由于字些字母形状的原因经常被连写,故排字员已习惯将它们连写。




字符属性名称:

const CFStringRef kCTCharacterShapeAttributeName;              
//字体形状属性  必须是CFNumberRef对象默认为0,非0则对应相应的字符形状定义,如1表示传统字符形状
<pre name="code" class="declaration" style="margin:0em 0.333em 1em 0.5em; background-color:rgb(255,255,255); font-size:13px; font-family:Courier,Consolas,monospace; color:rgb(102,102,102)">const CFStringRef kCTFontAttributeName;                        
 
//字体属性   必须是CTFont对象
const CFStringRef kCTKernAttributeName;                        
//字符间隔属性 必须是CFNumberRef对象
const CFStringRef kCTLigatureAttributeName;                 
//<span style="font-family:Verdana;font-size:13px">设置是否使用连字属性,设置为0,表示不使用连字属性。标准的英文连字有FI,FL.默认值为1,既是使用标准连字。也就是当搜索到f时候,会把fl当成一个文字。</span>必须是CFNumberRef 默认为1,可取0,1,2
const CFStringRef kCTForegroundColorAttributeName;             
//字体颜色属性  必须是CGColor对象,默认为black
const CFStringRef kCTForegroundColorFromContextAttributeName; 
 //上下文的字体颜色属性 必须为CFBooleanRef 默认为False,
const CFStringRef kCTParagraphStyleAttributeName;              
//段落样式属性 必须是CTParagraphStyle对象 默认为NIL
const CFStringRef kCTStrokeWidthAttributeName;              
//笔画线条宽度 必须是CFNumberRef对象,默为0.0f,标准为3.0f
const CFStringRef kCTStrokeColorAttributeName;              
//笔画的颜色属性 必须是CGColorRef 对象,默认为前景色
const CFStringRef kCTSuperscriptAttributeName;              
//设置字体的上下标属性 必须是CFNumberRef对象 默认为0,可为-1为下标,1为上标,需要字体支持才行。如排列组合的样式Cn1
const CFStringRef kCTUnderlineColorAttributeName;           
//字体下划线颜色属性 必须是CGColorRef对象,默认为前景色
const CFStringRef kCTUnderlineStyleAttributeName;           
//字体下划线样式属性 必须是CFNumberRef对象,默为kCTUnderlineStyleNone 可以通过CTUnderlineStypleModifiers 进行修改下划线风格
const CFStringRef kCTVerticalFormsAttributeName;
//文字的字形方向属性 必须是CFBooleanRef 默认为false,false表示水平方向,true表示竖直方向
const CFStringRef kCTGlyphInfoAttributeName;
//字体信息属性 必须是CTGlyphInfo对象
const CFStringRef kCTRunDelegateAttributeName
//CTRun 委托属性 必须是CTRunDelegate对象
举例说明:
  1. NSMutableAttributedString *mabstring = [[NSMutableAttributedString alloc]initWithString:@"This is a test of characterAttribute. 中文字符"];  


  1. //设置字体属性  
  2.   CTFontRef font = CTFontCreateWithName(CFSTR("Georgia"), 40, NULL);  
  3.   [mabstring addAttribute:(id)kCTFontAttributeName value:(id)font range:NSMakeRange(0, 4)];   

  1. //设置斜体字  
  2.     CTFontRef font = CTFontCreateWithName((CFStringRef)[UIFont italicSystemFontOfSize:20].fontName, 14, NULL);  
  3.     [mabstring addAttribute:(id)kCTFontAttributeName value:(id)font range:NSMakeRange(0, 4)];  

  1. //下划线  
  2.     [mabstring addAttribute:(id)kCTUnderlineStyleAttributeName value:(id)[NSNumber numberWithInt:kCTUnderlineStyleDouble] range:NSMakeRange(0, 4)];   

  1. //下划线颜色  
  2.     [mabstring addAttribute:(id)kCTUnderlineColorAttributeName value:(id)[UIColor redColor].CGColor range:NSMakeRange(0, 4)];  


  1. //设置字体简隔 eg:test   
  2.     long number = 10;  
  3.     CFNumberRef num = CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt8Type,&number);  
  4.     [mabstring addAttribute:(id)kCTKernAttributeName value:(id)num range:NSMakeRange(10, 4)];  

  1. //设置连字  
  2. long number = 1;  
  3.     CFNumberRef num = CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt8Type,&number);  
  4.     [mabstring addAttribute:(id)kCTLigatureAttributeName value:(id)num range:NSMakeRange(0, [str length])];  
连字还不会使用,未看到效果。
  1. //设置字体颜色  
  2.     [mabstring addAttribute:(id)kCTForegroundColorAttributeName value:(id)[UIColor redColor].CGColor range:NSMakeRange(0, 9)];  

  1. //设置字体颜色为前影色  
  2.     CFBooleanRef flag = kCFBooleanTrue;  
  3.     [mabstring addAttribute:(id)kCTForegroundColorFromContextAttributeName value:(id)flag range:NSMakeRange(5, 10)];  
无明显效果。
  1. //设置空心字  
  2.     long number = 2;  
  3.     CFNumberRef num = CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt8Type,&number);  
  4.     [mabstring addAttribute:(id)kCTStrokeWidthAttributeName value:(id)num range:NSMakeRange(0, [str length])];  

  1. //设置空心字  
  2.     long number = 2;  
  3.     CFNumberRef num = CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt8Type,&number);  
  4.     [mabstring addAttribute:(id)kCTStrokeWidthAttributeName value:(id)num range:NSMakeRange(0, [str length])];  
  5.        
  6.     //设置空心字颜色  
  7.     [mabstring addAttribute:(id)kCTStrokeColorAttributeName value:(id)[UIColor greenColor].CGColor range:NSMakeRange(0, [str length])];  

在设置空心字颜色时,必须先将字体高为空心,否则设置颜色是没有效果的。

  1. //对同一段字体进行多属性设置      
  2.     //红色  
  3.     NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithObject:(id)[UIColor redColor].CGColor forKey:(id)kCTForegroundColorAttributeName];  
  4.     //斜体  
  5.     CTFontRef font = CTFontCreateWithName((CFStringRef)[UIFont italicSystemFontOfSize:20].fontName, 40, NULL);  
  6.     [attributes setObject:(id)font forKey:(id)kCTFontAttributeName];  
  7.     //下划线  
  8.     [attributes setObject:(id)[NSNumber numberWithInt:kCTUnderlineStyleDouble] forKey:(id)kCTUnderlineStyleAttributeName];  
  9.       
  10.     [mabstring addAttributes:attributes range:NSMakeRange(0, 4)];  

最后是draw了。

  1. -(void)characterAttribute  
  2. {  
  3.     NSString *str = @"This is a test of characterAttribute. 中文字符";  
  4.     NSMutableAttributedString *mabstring = [[NSMutableAttributedString alloc]initWithString:str];  
  5.       
  6.     [mabstring beginEditing];  
  7.     /* 
  8.     long number = 1; 
  9.     CFNumberRef num = CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt8Type,&number); 
  10.     [mabstring addAttribute:(id)kCTCharacterShapeAttributeName value:(id)num range:NSMakeRange(0, 4)]; 
  11.     */  
  12.     /* 
  13.     //设置字体属性 
  14.     CTFontRef font = CTFontCreateWithName(CFSTR("Georgia"), 40, NULL); 
  15.     [mabstring addAttribute:(id)kCTFontAttributeName value:(id)font range:NSMakeRange(0, 4)]; 
  16.     */  
  17.     /* 
  18.     //设置字体简隔 eg:test  
  19.     long number = 10; 
  20.     CFNumberRef num = CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt8Type,&number); 
  21.     [mabstring addAttribute:(id)kCTKernAttributeName value:(id)num range:NSMakeRange(10, 4)]; 
  22.     */  
  23.   
  24.     /* 
  25.     long number = 1; 
  26.     CFNumberRef num = CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt8Type,&number); 
  27.     [mabstring addAttribute:(id)kCTLigatureAttributeName value:(id)num range:NSMakeRange(0, [str length])]; 
  28.      */  
  29.     /* 
  30.     //设置字体颜色 
  31.     [mabstring addAttribute:(id)kCTForegroundColorAttributeName value:(id)[UIColor redColor].CGColor range:NSMakeRange(0, 9)]; 
  32.      */  
  33.     /* 
  34.     //设置字体颜色为前影色 
  35.     CFBooleanRef flag = kCFBooleanTrue; 
  36.     [mabstring addAttribute:(id)kCTForegroundColorFromContextAttributeName value:(id)flag range:NSMakeRange(5, 10)]; 
  37.      */  
  38.       
  39.     /* 
  40.     //设置空心字 
  41.     long number = 2; 
  42.     CFNumberRef num = CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt8Type,&number); 
  43.     [mabstring addAttribute:(id)kCTStrokeWidthAttributeName value:(id)num range:NSMakeRange(0, [str length])]; 
  44.       
  45.     //设置空心字颜色 
  46.     [mabstring addAttribute:(id)kCTStrokeColorAttributeName value:(id)[UIColor greenColor].CGColor range:NSMakeRange(0, [str length])]; 
  47.      */  
  48.       
  49.     /* 
  50.     long number = 1; 
  51.     CFNumberRef num = CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt8Type,&number); 
  52.     [mabstring addAttribute:(id)kCTSuperscriptAttributeName value:(id)num range:NSMakeRange(3, 1)]; 
  53.     */  
  54.       
  55.     /* 
  56.     //设置斜体字 
  57.     CTFontRef font = CTFontCreateWithName((CFStringRef)[UIFont italicSystemFontOfSize:20].fontName, 14, NULL); 
  58.     [mabstring addAttribute:(id)kCTFontAttributeName value:(id)font range:NSMakeRange(0, 4)]; 
  59.     */   
  60.       
  61.     /* 
  62.     //下划线 
  63.     [mabstring addAttribute:(id)kCTUnderlineStyleAttributeName value:(id)[NSNumber numberWithInt:kCTUnderlineStyleDouble] range:NSMakeRange(0, 4)];  
  64.     //下划线颜色 
  65.     [mabstring addAttribute:(id)kCTUnderlineColorAttributeName value:(id)[UIColor redColor].CGColor range:NSMakeRange(0, 4)]; 
  66.      */  
  67.       
  68.       
  69.       
  70.     //对同一段字体进行多属性设置      
  71.     //红色  
  72.     NSMutableDictionary *attributes = [NSMutableDictionary dictionaryWithObject:(id)[UIColor redColor].CGColor forKey:(id)kCTForegroundColorAttributeName];  
  73.     //斜体  
  74.     CTFontRef font = CTFontCreateWithName((CFStringRef)[UIFont italicSystemFontOfSize:20].fontName, 40, NULL);  
  75.     [attributes setObject:(id)font forKey:(id)kCTFontAttributeName];  
  76.     //下划线  
  77.     [attributes setObject:(id)[NSNumber numberWithInt:kCTUnderlineStyleDouble] forKey:(id)kCTUnderlineStyleAttributeName];  
  78.       
  79.     [mabstring addAttributes:attributes range:NSMakeRange(0, 4)];  
  80.        
  81.   
  82.       
  83.     NSRange kk = NSMakeRange(0, 4);  
  84.       
  85.     NSDictionary * dc = [mabstring attributesAtIndex:0 effectiveRange:&kk];  
  86.       
  87.     [mabstring endEditing];  
  88.       
  89.     NSLog(@"value = %@",dc);  
  90.       
  91.   
  92.       
  93.     CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)mabstring);  
  94.       
  95.     CGMutablePathRef Path = CGPathCreateMutable();  
  96.       
  97.     CGPathAddRect(Path, NULL ,CGRectMake(10 , 0 ,self.bounds.size.width-10 , self.bounds.size.height-10));  
  98.       
  99.     CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), Path, NULL);      
  100.       
  101.     //获取当前(View)上下文以便于之后的绘画,这个是一个离屏。  
  102.     CGContextRef context = UIGraphicsGetCurrentContext();  
  103.       
  104.     CGContextSetTextMatrix(context , CGAffineTransformIdentity);  
  105.       
  106.     //压栈,压入图形状态栈中.每个图形上下文维护一个图形状态栈,并不是所有的当前绘画环境的图形状态的元素都被保存。图形状态中不考虑当前路径,所以不保存  
  107.     //保存现在得上下文图形状态。不管后续对context上绘制什么都不会影响真正得屏幕。  
  108.     CGContextSaveGState(context);  
  109.       
  110.     //x,y轴方向移动  
  111.     CGContextTranslateCTM(context , 0 ,self.bounds.size.height);  
  112.       
  113.     //缩放x,y轴方向缩放,-1.0为反向1.0倍,坐标系转换,沿x轴翻转180度  
  114.     CGContextScaleCTM(context, 1.0 ,-1.0);  
  115.       
  116.     CTFrameDraw(frame,context);  
  117.       
  118.     CGPathRelease(Path);  
  119.     CFRelease(framesetter);  
  120. }  

  1. - (void)drawRect:(CGRect)rect  
  2. {  
  3.     [self characterAttribute];  
  4. }  


CORETEXT框架图


另对于Context的了解可以参考:http://www.padovo.com/blog/2013/01/31/study-coretext/


CoreText使用教程

(2013-07-11 14:21:39)
  

Core Text 是基于 iOS 3.2+ 和 OSX 10.5+的一种能够对文本格式和文本布局进行精细控制的文本引擎。
它良好的结合了 UIKit 和 Core Graphics/Quartz:

  • UIKit 的 UILabel 允许你通过在 IB 中简单的拖曳添加文本,但你不能改变文本的颜色和其中的单词。
  • Core Graphics/Quartz几乎允许你做任何系统允许的事情,但你需要为每个字形计算位置,并画在屏幕上。
  • Core Text 正结合了这两者!你可以完全控制位置、布局、类似文本大小和颜色这样的属性,而 Core Text将帮你完善其它的东西——类似文本换行、字体呈现等等。

Core Text 对于创建杂志和书籍应用十分方便——它们在 iPad 上非常受欢迎!

这篇教程将会引领你使用 Core Text,通过创建一个简单的杂志应用——为僵尸!
你将学会如何:

  • 在屏幕上呈现格式化后的文本;
  • 微调文本外观;
  • 在文本内容中添加图片;
  • 最后是创建杂志应用,加载文本标记,对已呈现的文本进行格式化修改。
  • 吃掉大脑!这是个玩笑,只对此杂志的读者。

事不宜迟,让我们为僵尸的快乐生活做出自己应有的贡献吧——通过创建他们的专属 iPad 杂志!

创建一个 Core Text 项目

开启 Xcode,点击 File\New\New Project,选择 iOS\Application\View-basedApplication,并点击 Next,将项目命名为 CoreTextMagazine,选择 iPad 作为设备,点击Next,选择保存项目的目录,点击 Create。
下一步就是为项目添加 Core Text 框架:

  1. 在项目导航中点击项目文件(左侧栏)
  2. 在 Target 列中点击项目中唯一的 “CoreTextMagazine”
  3. 点击 “Build phases” 标签
  4. 展开 “Link Binary With Libraries” 栏,并点击 “+” 按钮
  5. 选择列表中的 “CoreText.framework” 并点击 “Add”

coretextFramework
你已经设置完了——下面是添加代码时间!

添加一个 Core Text 视图

要尽快上手 Core Text,你需要创建一个自定义的 UIView,使用 Core Text 作为其 drawRect:方法。
点击File\New\New File,选择 iOS\Cocoa Touch\Objective-C class,并点击Next。输入 UIView 作为 Subclass,点击 Next,将新类命名为 CTView,并点击 Save。
在 CTView.h 文件中,在 @interface 前添加下面的代码,引用 Core Text 框架:

1
#import

下一步,你将设置这个新的自定义视图为应用的主视图。

在项目浏览器中选择 “CoreTextMagazineViewController.xib” 文件, 并打开 XCode的实用工具栏 (它在你按下 XCode 顶部工具栏的视图区第三项时显示)。 点击这个实用工具栏上第三个图标选择 Identity选项卡。
现在点击界面编辑器的空白区域选中窗口的视图 – 您应该看到实用工具栏上有一个 Class 字段显示为“UIView”。 输入“CTView” 后回车。
SetClassIdentity
现在您的应用将在后显示您的自定义 Core Text 视图了,不过先等等 – 先加入一些绘制文字的代码好用于测试。

打开 CTView.m 删除所有预定义的方法。 输入下面的代码在你的视图上绘制一个“Hello world”:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- ( void )drawRect : (CGRect )rect
{
    [super drawRect :rect ];
    CGContextRef context =UIGraphicsGetCurrentCont ext ( );
 
    CGMutablePathRef path =CGPathCreateMutable ( ); //1
    CGPathAddRect (path, NULL, self.bounds );
 
    NSAttributedString * attString = [ [ [ NSAttributedString alloc ]
        initWithString : @ "Hello coretext world!" ]autorelease ]; //2
 
    CTFramesetterRef framesetter =
        CTFramesetterCreateWithA ttributedString ( (CFAttributedStringRef )attString ); //3
    CTFrameRef frame =
        CTFramesetterCreateFrame (framesetter,
            CFRangeMake ( 0, [attString length ] ),path, NULL );
 
    CTFrameDraw (frame, context ); //4
 
    CFRelease (frame ); //5
    CFRelease (path );
    CFRelease (framesetter );
}

让我们来一步一步讨论,使用注释标记上述指定每个节:

  1. 这里你需要创建一个用于绘制文本的路径区域。Mac 上的 Core Text 支持矩形图形等不同形状,但在 iOS上只支持矩形。在这个示例中,你将通过 self.bounds 使用整个视图矩形区域创建 CGPath 引用。
  2. 在 Core Text 中使用 NSAttributedString 而不是NSString,NSAttributedString 是一个非常强大的 NSString 派生类,它允许你对文本应用格式化属性。现在我们还没有用到格式化,这里仅仅使用纯文本。
  3. CTFramesetter 是使用 Core Text 绘制时最重要的类。它管理您的字体引用和文本绘制帧。 目前您需要了解CTFramesetterCreateWithAttributedString 通过应用属性化文本创建 CTFramesetter 。本节中,在 framesetter 之后通过一个所选的文本范围(这里我们选择整个文本)与需要绘制到的矩形路径创建一个帧。
  4. CTFrameDraw 将 frame 描述到设备上下文。
  5. 最后,释放所有使用的对象。

你可能会想“既然已经又了 Objective-C,为什么我还要用 C ?!”
好吧,为了简捷,iOS 的很多底层库都是用 plain C 编写的。不用担心,Core Text 的函数应用起来很简单。
只有一件事要牢记:在你引用名字中有 “Create” 的函数时,不要忘记使用 CFRelease。
不管你信不信,这就是用 Core Text 画简单文本的所有东西!点击运行,看看结果。

helloWorldFlipped
噢,看上去有点不对劲,是吧?跟很多底层 API 一样,Core Text 使用Y翻转坐标系统。更糟糕的是,内容的呈现也是上下翻转的。注意,当你混合使用 UIKit 绘图和 Core Text绘图时,你将获得很奇葩的结果。

然后修改内容的方向!在 “CGContextRef context =UIGraphicsGetCurrentContext();” 一行后添加代码如下:

1
2
3
4
// Flip thecoordinate system
CGContextSetTextMatrix (context,CGAffineTransformIdentit y );
CGContextTranslateCTM (context, 0,self.bounds.size.height );
CGContextScaleCTM (context, 1.0, - 1.0 );

This is very simple code, which just flips the content byapplying a transformation to the view’s context. Just copy/paste iteach time you do drawing with CT.
代码很简单,只是通过转换内容将其翻转。你只需要在画 CT 时复制/粘帖它们。
再运行一次——恭喜你完成了第一个 Core Text 应用!

helloWorld

Core Text 对象模型

如果您对 CTFramesetter 与 CTFrame 还有些不明白。这里我来做一个有关 Core Text如何渲染文本内容的简述。
Core Text 对象模型如下:
CTClasses
您创建 CTFramesetter 关联您提供的 NSAttributedString 。此时 CTTypesetter实例将自动创建, 它管理您的字体。下一步使用 CTFramesetter 创建您要用于渲染文本的一个或多个帧。
当您创建帧时,您指定一个用于此帧矩形内的子文本范围。Core Text 为每行文本自动创建一个 CTLine (注意这里)与并创建多个 CTRun 文本分段,每个 CTRun 内的文本有着同样的格式。
例如,Core Text 可能为您的几个红色单词创建一个 CTRun,其它 CTRun 包括纯文本,另外一些 CTRun是粗体等。再次重申,你不要自己直接创建 CTRun 实例, Core Text 使用其于您提供的 NSAttributedString相关属性创建它们。
每个 CTRun 对象可以采用不同的属性,所以你可以精确的控制字距,连字,宽度,高度等更多属性。

映射到杂志应用

要创建这个杂志应用,我们要具备可以将一些文本标记成具有不同属性的性能。我们可以直接使用NSAttributedString的方法来做到这点,比如setAttributes:range,但在实践中这是一种笨拙的处理方式(除非你费力地编写大量代码)。
因此,为了更简单地处理问题,我们将创建一个简单的文本标记解析器,它允许我们在杂志内容中使用简单的标签设置格式。
进到“File\New’New File“下,选择”iOS\Cocoa Touch\Objective-C class”,然后点击下一步,进入NSObject子类,点击下一步,将新类命名为MarkupParser.m再保存。

切换到 MarkupParser.h 文件删除所有内容并粘贴下面的代码 – 它定义了用于解析的一些属性与方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import
#import
 
@interface MarkupParser : NSObject {
 
    NSString * font;
    UIColor * color;
    UIColor * strokeColor;
    float strokeWidth;
 
    NSMutableArray * images;
}
 
@property (retain, nonatomic ) NSString * font;
@property (retain, nonatomic ) UIColor * color;
@property (retain, nonatomic ) UIColor * strokeColor;
@property (assign, readwrite ) float strokeWidth;
 
@property (retain, nonatomic ) NSMutableArray * images;
 
- ( NSAttributedString * )attrStringFromMarkup : ( NSString * )html;
 
@end

接着打开 MarkupParser.m 并使用下面的代码替换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#import "MarkupParser.h"
 
@implementationMarkupParser
 
@synthesize font, color,strokeColor, strokeWidth;
@synthesize images;
 
- ( id )init
{
    self = [super init ];
    if (self ) {
        self.font = @ "Arial";
        self.color = [UIColor blackColor ];
        self.strokeColor = [UIColor whiteColor ];
        self.strokeWidth = 0.0;
        self.images = [ NSMutableArray array ];
    }
    return self;
}
 
- ( NSAttributedString * )attrStringFromMarkup : ( NSString * )markup
{
 
}
 
- ( void )dealloc
{
    self.font = nil;
    self.color = nil;
    self.strokeColor = nil;
    self.images = nil;
 
    [super dealloc ];
}
 
@end

正如你所看到的,这是个简单的解析器代码 – 它只包括了几个属性用于记录字体,文本颜色,画笔大于与画笔颜色。后面我将在文字中加入图片,所以需要一个数组保存文字中使用到的图片列表。
编写一个解析器通常是很困难的工作, 在这里我将向你展示使用正则表达示创建一个非常简单的解析器。本教程中的解析器将非常简单,只支持开放型标签 –一个标签设置后面文本的样式,直到出现一个新的标签,这种标签化文本看起来就像这样:
These are red and blue words.

These are red and blue

对于本教程的目的,这样的标签就足够了。对于您的项目,如果需要,你可以进一步完善。

开始解析!

在 attrStringFromMarkup: 方法中添加:

1
2
3
4
5
6
7
8
9
10
NSMutableAttributedString * aString =
    [ [ NSMutableAttributedStringalloc ]initWithString : @ "" ]; //1
 
NSRegularExpression * regex = [ [NSRegularExpression alloc ]
    initWithPattern : @ "(.*?)(<[^>]+>|\\Z)"
    options :NSRegularExpressionCaseI nsensitive|NSRegularExpressionDotMa tchesLineSeparators
    error : nil ]; //2
NSArray * chunks = [regex matchesInString :markup options : 0
    range :NSMakeRange ( 0, [markup length ] ) ];
[regex release ];

在这里介绍两部分:

  1. 首先,设置一个用于增加文本的空返回文本。
  2. 接着,创建一个匹配文本与标签的正则表达式。 这个正则表达式将匹配一段文本跟着一个标签。这个正则表达式基本上可以说是“查找任何数量的字符,直到你遇到一个左括号。然后匹配任何数量的字符,直到你找到一个右括号。 或停止处理当你到了结束的字符串。”

为什么我们要创建这样的正则表达式?我们将用它来搜索的字符串相匹配的每一部分1)绘制找到的文本块,2)按找到的标签更改当前样式。重复这个过程直到文本结束。

非常简单的解析器不是吗?

现在您有全部文本和格式化标签分块的 “chunks” 数组, 你需要使用它的文字与标签循环创造属性化文本。
在方法体里添加:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
for (NSTextCheckingResult * b in chunks ) {
    NSArray * parts = [ [markup substringWithRange :b.range ]
componentsSeparatedByStr ing : @ "<" ]; //1
 
    CTFontRef fontRef =CTFontCreateWithName ( (CFStringRef )self.font,
24.0f, NULL );
 
    //apply the current textstyle //2
    NSDictionary * attrs = [ NSDictionarydictionaryWithObjectsAnd Keys :
                            ( id )self.color.CGColor,kCTForegroundColorAttrib uteName,
                            ( id )fontRef, kCTFontAttributeName,
                            ( id )self.strokeColor.CGColor, ( NSString * )kCTStrokeColorAttributeN ame,
                            ( id ) [ NSNumber numberWithFloat : self.strokeWidth ], ( NSString * )kCTStrokeWidthAttributeN ame,
                            nil ];
    [aStringappendAttributedString : [ [ [ NSAttributedStringalloc ]initWithString : [parts objectAtIndex : 0 ]attributes :attrs ] autorelease ] ];
 
    CFRelease (fontRef );
 
    //handle new formatting tag//3
    if ( [parts count ]> 1 ) {
        NSString * tag = ( NSString * ) [parts objectAtIndex : 1 ];
        if ( [tag hasPrefix : @ "font" ] ) {
            //strokecolor
            NSRegularExpression *scolorRegex = [ [ [NSRegularExpression alloc ] initWithPattern : @ "(?<=strokeColor=")\\w+" options : 0error : NULL ]autorelease ];
            [scolorRegexenumerateMatchesInString :tagoptions : 0 range :NSMakeRange ( 0, [tag length ] )usingBlock :^ (NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop ) {
                if ( [ [tagsubstringWithRange :match.range ] isEqualToString : @ "none" ] ) {
                    self.strokeWidth = 0.0;
                } else {
                    self.strokeWidth = - 3.0;
                    SELcolorSel =NSSelectorFromString ( [ NSString stringWithFormat : @ "%@Color", [tag substringWithRange :match.range ] ] );
                    self.strokeColor = [UIColor performSelector :colorSel ];
                }
            } ];
 
            //color
            NSRegularExpression *colorRegex = [ [ [NSRegularExpression alloc ] initWithPattern : @ "(?<=color=")\\w+" options : 0error : NULL ]autorelease ];
            [colorRegexenumerateMatchesInString :tagoptions : 0 range :NSMakeRange ( 0, [tag length ] )usingBlock :^ (NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop ) {
                SEL colorSel = NSSelectorFromString ( [ NSString stringWithFormat : @ "%@Color", [tag substringWithRange :match.range ] ] );
                self.color = [UIColor performSelector :colorSel ];
            } ];
 
            //face
            NSRegularExpression * faceRegex = [ [ [NSRegularExpression alloc ] initWithPattern : @ "(?<=face=")[^"]+" options : 0error : NULL ]autorelease ];
            [faceRegexenumerateMatchesInString :tagoptions : 0 range :NSMakeRange ( 0, [tag length ] )usingBlock :^ (NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop ) {
                self.font = [tag substringWithRange :match.range ];
            } ];
        } //end of fontparsing
    }
}
 
return ( NSAttributedString * )aString;

呼, 这段代码不少!不过别担心,我们一段一段来看。

  1. 您遍历由之前的正则表达式匹配的块,用 “<"字符(标签开始)分割块。在返回的 parts[0]中你得到了需要追加的文本内容,在 parts[1] 中包括了接下去的文字格式标签。
  2. 接下来创建一个保存格式化选项的字典 – 用它来给 NSAttributedString 设置格式化属性。来看看这些键名 –不用说它们是苹果定义的常量 (您可以查看苹果 Core Text String Attributes Reference 了解详情)。通过调用 appendAttributedString: 下一个文本块将应用这些属性到结果文本中。
  3. 最后,您检查是否文本段后找到了一个标签;如果标签名是 “font” ,进一步使用正则读取标签属性。 对于 “face”属性,将保存字体名称到 self.font, 对于 “color” 我们使用了个小花招:如
    1
     

    通过 colorRegex 找到的 “red” 通过选择器直接在 UIColor 类执行 “redColor” – 这(嘿嘿)返回一个红色的 UIColor 实例。 注意:此招只适用于 UIColor预定义的颜色的(如果你传递一个不存在的方法选择,甚至可以导致你的代码崩溃),但在本教程中是足够的。画笔颜色属性与颜色属性很类似,特殊的是当strokecolor 为 “none”时,只是设置画笔大小为 0.0,这样画笔就不会应用于文本。

注意: 如果您对这个段落中所使用的正则表达式还不是太明白,它们基本上可以称为 “查找任何 color=” 打头的文本”。匹配所有一般字符(不包括引号),直到找到关闭引号。更多详情,查看苹果的 NSRegularExpression classreference.
很好!已经完成了渲染格式化文本的一半工作了 – 现在 attrStringFromMarkup: 可以把标记化的内容解析放置到NSAttributedString 中为 Core Text 使用它做好了准备。
那么让我们先试试!
打开 CTView.m and 在 @implementation: 之前添加:

1
#import "MarkupParser.h"

找到 attString 定义的位置 – 使用下面的代码替换:

1
2
MarkupParser * p = [ [ [MarkupParser alloc ] init ] autorelease ];
NSAttributedString * attString = [pattrStringFromMarkup : @ "Hello "red">core text "blue">world!" ];

上面的代码实例化了一个新的解析器,并通过解析一段标记文本获取了格式化文本。

就是这样 – 点击 Run 试试看!
helloWorldFormatted

真是太棒了! 感谢 50行的解析代码让我们没有在字符范围与格式上处理大量的代码任何,我们的杂志应用只需要使用一个简单的文本文件保存其内容。同时您刚刚完成的这个简单的解析器可以根据您的杂志应用的需要无限制的扩展。

一个基本的杂志布局

到现在为止,我们已经能够把文字显示出来了,这是一个好的开端。但是对一个杂志来说,我们最好有多栏显示-在这里CoreText要大展身手了。
开始编写布局代码之前,我们先加载一个更长的字符串到应用中,这样我们就有足够长的文章需要回绕多行显示。
打开菜单栏 File\New\New File, 选择 iOS\Other\Empty,然后点击下一步(Next)。命名新的文件为test.txt, 然后点击保存。
下来把这个文件中的文本拷贝到test.txt并保存。
打开 CTView.m 并找到我们创建MarkupParser 和NSAttributedString的那两行,然后删除他们。我们把加载文本文件的代码从drawRect: 方法中移出来,因为他们实际上不应该在那里。drawRect:方法的真正工作是画UIView里的内容-而不是加载内容。我们等会将会把attString变量重构成实例变量,变成这个类的属性。

接下来打开CoreTextMagazineViewController.m, 删除所有存在的内容,添加下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#import"CoreTextMagazineViewController.h"
#import "CTView.h"
#import "MarkupParser.h"
 
@implementationCoreTextMagazineViewCont roller
 
- ( void )viewDidLoad
{
    [super viewDidLoad ];
 
    NSString *path = [ [ NSBundle mainBundle ] pathForResource : @ "test" ofType : @ "txt" ];
    NSString * text = [ NSStringstringWithContentsOfFile :pathencoding :NSUTF8StringEncodingerror : NULL ];
    MarkupParser * p = [ [ [MarkupParser alloc ] init ] autorelease ];
    NSAttributedString * attString = [pattrStringFromMarkup :text ];
    [ (CTView * )self.view setAttString : attString ];
}
 
@end

当这个应用的view加载完毕,这个应用读取test.txt的文本,转换为属性字符串(attributedstring)然后设置到窗口的view的attString属性中。我们还没有在添加CTView中添加这个属性,现在让我们开始添加吧!

在CTView.h中定义3个实例变量:

1
2
3
4
float frameXOffset;
float frameYOffset;
 
NSAttributedString * attString;

然后在CTView.h和CTView.m中添加相应的代码来定义attString属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
//CTView.h
@property (retain, nonatomic ) NSAttributedString * attString;
 
//CTView.m
//just below@implementation ...
@synthesize attString;
 
//at the bottomof the file
- ( void )dealloc
{
    self.attString = nil;
    [super dealloc ];
}

现在我们点击”Run”来看看view是否显示了文本文件的内容。酷!
WallOfText

如何给这些文本创建列(columns)? 很幸运,Core Text 提供了一个很方便的函数 –CTFrameGetVisibleStringRange。这个函数告诉你在指定的矩形框里可以显示多少文本。所以想法就是-创建列,看看多少文本可以显示下,如果有更多文本没显示,再创建新的列,如此循环,知道所有文本都可以显示完。(这里列是个CTFrame实例,因为列只是更高一点的矩形)
首先我们创建列,然后页,然后整个杂志。所以…让我们使CTView继承UIScrollView,这样就继承了分页和滚动的功能了,而不用自己写!
打开 CTView.h ,修改@interface这样代码为:

1
@interface CTView : UIScrollView {

好,我们得到免费的滚动和分页功能了。我们下面会轻松的开启分页功能。
到现在为止,我们在创建了drawRect:中创建了framesetter和frame实例。有多个栏而且有不同的格式,最好的我们在一次把所有的计算做完。所以我们准备创建新的类 “CTColumnView”,这个类只是呈现(render)传给他的CT内容,在我们的CTView类中我们准备一次创建所有的CTColumnView的实例,并把他们作为subviews加入到CTView中。

总结一下:CTView会处理滚动,分页,创建所有的列;CTColumnView实际呈现内容到屏幕上。
打开菜单栏 File\New\New File, 选择 iOS\Cocoa Touch\Objective-C class,点击下一步。在”Subclass of”输入框中输入, 点击下一步,把新类命名为 CTColumnView.m,然后点击保存。下面就是CTColumnView类的初始代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//insideCTColumnView.h
 
#import
#import
 
@interface CTColumnView : UIView {
    id ctFrame;
}
 
- ( void )setCTFrame : ( id )f;
@end
 
//insideCTColumnView.m
#import "CTColumnView.h"
 
@implementationCTColumnView
- ( void )setCTFrame : ( id )f
{
    ctFrame = f;
}
 
- ( void )drawRect : (CGRect )rect
{
    CGContextRef context =UIGraphicsGetCurrentCont ext ( );
 
    // Flip the coordinatesystem
    CGContextSetTextMatrix (context,CGAffineTransformIdentit y );
    CGContextTranslateCTM (context, 0,self.bounds.size.height );
    CGContextScaleCTM (context, 1.0, - 1.0 );
 
    CTFrameDraw ( (CTFrameRef )ctFrame, context );
}
@end

这个类包含是我们到现在位置写的所有功能-只是呈现一个CTFrame。我们会为杂志的每个列创建一个这个类的实例。

让我们先添加一个数组属性来保存我们的CTView的CTframes,然后声明 buildFrames方法,这个方法会创建所有列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//CTView.h - atthe top
#import "CTColumnView.h"
 
//CTView.h - asan ivar
NSMutableArray * frames;
 
//CTView.h -declare property
@property (retain, nonatomic ) NSMutableArray * frames;
 
//CTView.h - inmethod declarations
- ( void )buildFrames;
 
//CTView.m - justbelow @implementation
@synthesize frames;
 
//CTView.m -inside dealloc
self.frames = nil;

现在 buildFrames 可以创建所有文本框(frames)然后存在在”frames”数组。让我们代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
- ( void )buildFrames
{
    frameXOffset = 20; //1
    frameYOffset = 20;
    self.pagingEnabled = YES;
    self.delegate = self;
    self.frames = [ NSMutableArray array ];
 
    CGMutablePathRef path =CGPathCreateMutable ( ); //2
    CGRect textFrame = CGRectInset (self.bounds, frameXOffset,frameYOffset );
    CGPathAddRect (path, NULL, textFrame );
 
    CTFramesetterRef framesetter =CTFramesetterCreateWithA ttributedString ( (CFAttributedStringRef )attString );
 
    int textPos = 0; //3
    int columnIndex = 0;
 
    while (textPos < [attString length ] ) { //4
        CGPoint colOffset = CGPointMake ( (columnIndex + 1 ) *frameXOffset + columnIndex * (textFrame.size.width / 2 ), 20 );
        CGRect colRect = CGRectMake ( 0, 0 ,textFrame.size.width / 2 - 10,textFrame.size.height - 40 );
 
        CGMutablePathRef path = CGPathCreateMutable ( );
        CGPathAddRect (path, NULL, colRect );
 
        //use the columnpath
        CTFrameRef frame = CTFramesetterCreateFrame (framesetter, CFRangeMake (textPos, 0 ),path, NULL );
        CFRange frameRange = CTFrameGetVisibleStringR ange (frame ); //5
 
        //create an empty columnview
        CTColumnView * content = [ [ [CTColumnView alloc ] initWithFrame : CGRectMake ( 0, 0, self.contentSize.width,self.contentSize.height ) ]autorelease ];
        content.backgroundColor = [UIColor clearColor ];
        content.frame = CGRectMake (colOffset.x, colOffset.y,colRect.size.width, colRect.size.height ) ;
 
        //set the column viewcontents and add it as subview
        [contentsetCTFrame : ( id )frame ];   //6 
        [self.frames addObject : ( id )frame ];
        [selfaddSubview :content ];
 
        //prepare for nextframe
        textPos +=frameRange.length;
 
        //CFRelease(frame);
        CFRelease (path );
 
        columnIndex ++;
    }
 
    //set the total width of thescroll view
    int totalPages = (columnIndex + 1 ) / 2; //7
    self.contentSize = CGSizeMake (totalPages *self.bounds.size.width,textFrame.size.height );
}

让我们来解释一下代码。

  1. 这一步我们做一下设置 – 定义 x 和 y 偏移,开启分页功能,创建空的框数组。
  2. buildFrames 接下来创建一个路径( path)和一个矩形变量等于view的边框,然后稍微偏移一点作为边距(margin).
  3. 这段声明了textPos, 这个变量用来保存在当前文本的位置,又声明了columnIndex,这个变量用来计算我们已经创建了多少列。
  4. 这个while循环直到到达文本结束退出。在循环里面我们创建一个列边界:colRect 是个 CGRect类型用来保存当前列的起点和尺寸,每个循环它会根据columnIndex重新计算起点。注意列的排列是一直向右而不是回绕向下。
  5. 这里使用CTFrameGetVisibleStringRange函数找出字符串的那些部分可以完全显示在框里(在这里指的是文本列)。textPos每次以这个范围的长度递增,所以下一个循环可以构建下一个列(如果还有多余的文本没有显示).
  6. 这里,不是像以前那样画框,我们把它传递给新创建的CTColumnView,并且我们把它保存在self.frames以供以后使用,然后把CTColumnView作为子View添加到scrollview中。
  7. 最后,totalPages保存了创建的总页数,然后设置contentSize属性,这样如果有多页的时候,我们就可以滚动了!

现在,让我在所有的CT设置完成后,调用buildFrames。在CoreTextMagazineViewController.m中的viewDidLoad的结尾添加下面的代码

1
[ (CTView * ) [self view ] buildFrames ];

在我们试运行新代码前还需要做一件事:在 CTView.m中删除drawRect:。我们现在在CTColumnView类中做所有的显示,所以要保留CTView的drawRect: 方法为标准的UIScrollView 实现。
好的…点击运行(Run)然后你可以看到文本以列的方式排列了!左右拖拽页面看一下…太棒了!
CTColumns

我们有列,很棒排版的文字,但是没有图片。在Core Text显示图片不是那么容易-毕竟这个是文本框架阿。
但是,由于我们已经有个了小的标记(markup)解析器,我们很快速的可以添加文本中显示图片的功能!

使用 Core Text 绘图

一般来说,Core Text并没有绘制图像的能力。然而,因为它是一个布局引擎,它所能做的是保留一个空间让你在其中绘制图像。同时,因为你的代码中已经有了drawRect: 方法,绘制一个图像很容易。
让我们看看在文本中保留一个空间是如何工作的: 还记得所有的文本块实际上是 CTRun 的实例吗?你只需为所给的 CTRun设置委托,委托对象会负责将 CTRun 的上升空间、下降空间和宽度告知 Core Text。如下图:
CTRunDelegate

当 Core Text 获知一个拥有 CTRunDelegate 委托的 CTRun 时,它会询问委托对象 ——我需要为这些块数据保留多少宽度和高度?这样你就在文本中建造了一个洞,然后你把图像在那里绘制出来。

让我们从为词法分析器添加对 “img” 标签的支持开始!打开 MarkupParser.m 并找到 “} //end offont parsing”;在此行之后紧接着添加支持“img”标签的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
if ( [taghasPrefix : @ "img" ] ) {
 
    __block NSNumber * width = [ NSNumber numberWithInt : 0 ];
    __block NSNumber * height = [ NSNumber numberWithInt : 0 ];
    __block NSString * fileName = @ "";
 
    //width
    NSRegularExpression *widthRegex = [ [ [NSRegularExpression alloc ] initWithPattern : @ "(?<=width=")[^"]+" options : 0error : NULL ]autorelease ];
    [widthRegexenumerateMatchesInString :tagoptions : 0 range :NSMakeRange ( 0, [tag length ] )usingBlock :^ (NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop ) {
        width = [ NSNumber numberWithInt : [ [tagsubstringWithRange :match.range ]intValue ] ];
    } ];
 
    //height
    NSRegularExpression * faceRegex = [ [ [NSRegularExpression alloc ] initWithPattern : @ "(?<=height=")[^"]+" options : 0error : NULL ]autorelease ];
    [faceRegexenumerateMatchesInString :tagoptions : 0 range :NSMakeRange ( 0, [tag length ] )usingBlock :^ (NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop ) {
        height = [ NSNumber numberWithInt : [ [tagsubstringWithRange :match.range ] intValue ] ];
    } ];
 
    //image
    NSRegularExpression * srcRegex = [ [ [NSRegularExpression alloc ] initWithPattern : @ "(?<=src=")[^"]+" options : 0error : NULL ]autorelease ];
    [srcRegexenumerateMatchesInString :tagoptions : 0 range :NSMakeRange ( 0, [tag length ] )usingBlock :^ (NSTextCheckingResult *match, NSMatchingFlags flags, BOOL *stop ) {
        fileName = [tagsubstringWithRange :match.range ];
    } ];
 
    //add the image fordrawing
    [self.images addObject :
      [ NSDictionarydictionaryWithObjectsAnd Keys :
      width, @ "width",
      height, @ "height",
      fileName, @ "fileName",
      [ NSNumber numberWithInt : [aString length ] ], @ "location",
      nil ]
      ];
 
    //render empty space fordrawing the image in the text //1
    CTRunDelegateCallbackscallbacks;
    callbacks.version =kCTRunDelegateVersion1;
    callbacks.getAscent = ascentCallback;
    callbacks.getDescent = descentCallback;
    callbacks.getWidth = widthCallback;
    callbacks.dealloc = deallocCallback;
 
    NSDictionary * imgAttr = [ [ NSDictionarydictionaryWithObjectsAnd Keys : //2
                              width, @ "width",
                              height, @ "height",
                              nil ] retain ];
 
    CTRunDelegateRef delegate =CTRunDelegateCreate ( &callbacks, imgAttr ); //3
    NSDictionary *attrDictionaryDelegate = [ NSDictionarydictionaryWithObjectsAnd Keys :
                                            //set thedelegate
                                            ( id )delegate, ( NSString * )kCTRunDelegateAttributeN ame,
                                            nil ];
 
    //add a space to the text sothat it can call the delegate
    [aStringappendAttributedString : [ [ [ NSAttributedStringalloc ]initWithString : @ " "attributes :attrDictionaryDelegate ] autorelease ] ];
}

让我们看看新代码——实际解析“img”标签同解析 font 标签不尽相同。通过3个正则表达式,你有效的获取了 img 标签的width、height 和 src 属性。当这些完成后——你在 self.images 上添加了一个新的 NSDictionary对象用以保存刚刚解析出来的信息,在文本中添加图片。

现在我们来看看第一部分 —— CTRunDelegateCallbacks 是一个保存指向函数的引用的 C语言结构体,这个结构体提供了你想要传递给 CTRunDelegate 的信息。正如你已经猜到的那样,getWidth方法提供一个宽度参数给 CTRun,getAscent 方法提供高度参数给CTRun,等等。在上面的代码中你为那些处理提供了函数名称,马上我们也会添加上函数具体实现。
第二部分非常重要 —— imgAttr 字典保存了图像的维度信息,我们向这个对象发送了 retain 消息因为它将会被传递给函数处理—— 因此,当 getAscent 触发时,它将作为参数被获得并从中读取出图像的高度并将其提供给CTRun。干净利落是吧?(马上我们就会谈谈这个。)

第三部分中通过关联与绑定回调和数据使用 CTRunDelegateCreate 创建委托实例。
下一步你需要创建一个属性字典 (和之前字体格式相同的方式),但在格式化属性中放入委托实例。最后你向属性化文本里加入一个包括委托属性的空格用于之后使用图片绘制。
下一步,你可能已经预料到了,提供用于委托回调的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//insideMarkupParser.m, just above @implementation
 

static void deallocCallback ( void *ref ) {
    [ ( id )ref release ];
}
static CGFloatascentCallback ( void *ref ) {
    return [ ( NSString * ) [ ( NSDictionary * )refobjectForKey : @ "height" ] floatValue ];
}
static CGFloatdescentCallback ( void *ref ) {
    return [ ( NSString * ) [ ( NSDictionary * )refobjectForKey : @ "descent" ] floatValue ];
}
static CGFloatwidthCallback ( void *ref ) {
    return [ ( NSString * ) [ ( NSDictionary * )refobjectForKey : @ "width" ] floatValue ];
}

ascentCallback, descentCallback 与 widthCallback 只是读取了之前放在 CT 的NSDictionary 里的值。deallocCallback 中为什么要释放保存图片信息的字典呢,因为它在CTRunDelegate 释放时调用,(译者释:Core Text 中大部分都是 C 函数集实现,它不会主动释放你 ObjC的对象)所以这里您要管理好你使用的内存。

现在您的解析器能处理 “img” 标签了,那么让 CTView 能渲染它们。 我们需要一个方法将图片数组发给这个视图,让我们将设置属性化文本与图片合成到一个方法中。 添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//CTView.h - 在@interface 声明段添加
NSArray * images;
 
//CTView.h -定义图片属性
@property (retain, nonatomic ) NSArray * images;
 
//CTView.h -添加一个方法声明
- ( void )setAttString : ( NSAttributedString * )attString withImages : ( NSArray * )imgs;
 
//CTView.m - 在@implementation 之后
@synthesize images;
 
//CTView.m - 在dealloc 方法内
self.images = nil;
 
//CTView.m -实现段的任意位置
- ( void )setAttString : ( NSAttributedString * ) string withImages : ( NSArray * )imgs
{
    self.attString = string;
    self.images = imgs;
}

现在 CTView 已经准备好接受一个图片数组,让我们从解析器中设置它们然后绘制!
转到 CoreTextMagazineViewController.m 找到 “[(CTView*)self.viewsetAttString: attString];” 行,使用下面的代码替换它:

1
[ (CTView * ) [self view ] setAttString :attString withImages : p.images ];

你可能看到 MarkupParser 类中的 attrStringFromMarkup: 方法,它保存了所有图片标签数据到self.images 中。这时你可以直接设置到 CTView。

要呈现图片,你需要明确的知道图片将显示在应用中的哪个框架。要找到这个原点,我们需要一系列的值:

  • 当内容被滚动时:contentOffset
  • CTView 的框架的偏移 (frameXOffset,frameYOffset)
  • CTLine 的原点坐标 (CTLine 在段落的开始可能由偏移)
  • 最后是 CTRun 和 CTLine 两者原点之间的距离。

runBounds

现在开始呈现图片!首先我们需要更新 CTColumnView 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//insideCTColumnView.h
//as anivar
NSMutableArray * images;
 
//as aproperty
@property (retain, nonatomic ) NSMutableArray * images;
 
//insideCTColumnView.m
//after@implementation...
@synthesize images;
 
- ( id )initWithFrame : (CGRect )frame
{
    if ( [super initWithFrame :frame ] != nil ) {
        self.images = [ NSMutableArray array ];
    }
    return self;
}
 
- ( void )dealloc
{
    self.images = nil;
    [super dealloc ];
}
 
//at the end ofdrawRect:
for ( NSArray * imageData in self.images ) {
    UIImage * img = [imageData objectAtIndex : 0 ];
    CGRect imgBounds = CGRectFromString ( [imageData objectAtIndex : 1 ] );
    CGContextDrawImage (context,imgBounds, img.CGImage );
}

通过这些代码,我们添加了一个 ivar 和一个被称为 images的属性,用以保存每个文本列中要显示的图片列表。为了避免声明另一个用来保存 images 中图片信息的类,我们将使用 NSArray对象,用来保存:

  1. 一个 UIImage 实例
  2. 图像的边界——即文本的原点和图片的大小

现在,计算图像的位置,并将其附加到相应的文本列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
//insideCTView.h
- ( void )attachImagesWithFrame : (CTFrameRef )f inColumnView : (CTColumnView * )col;
 
//insideCTView.m
- ( void )attachImagesWithFrame : (CTFrameRef )f inColumnView : (CTColumnView * )col
{
    //drawing images
    NSArray *lines = ( NSArray * )CTFrameGetLines (f ); //1
 
    CGPoint origins [ [lines count ] ];
    CTFrameGetLineOrigins (f,CFRangeMake ( 0, 0 ),origins ); //2
 
    int imgIndex = 0; //3
    NSDictionary * nextImage = [self.images objectAtIndex :imgIndex ];
    int imgLocation = [ [nextImage objectForKey : @ "location" ] intValue ];
 
    //find images for the currentcolumn
    CFRange frameRange =CTFrameGetVisibleStringR ange (f ); //4
    while ( imgLocation < frameRange.location ) {
        imgIndex ++;
        if (imgIndex> = [self.images count ] ) return; //quit if no images for thiscolumn
        nextImage = [self.imagesobjectAtIndex :imgIndex ];
        imgLocation = [ [nextImage objectForKey : @ "location" ] intValue ];
    }
 
    NSUInteger lineIndex = 0;
    for ( idlineObj in lines ) { //5
        CTLineRef line = (CTLineRef )lineObj;
 
        for ( id runObj in ( NSArray * )CTLineGetGlyphRuns (line ) ) { //6
            CTRunRefrun = (CTRunRef )runObj;
            CFRangerunRange =CTRunGetStringRange (run );
 
            if ( runRange.location <<spanstyle="color: #002200;">= imgLocation && runRange.location +runRange.length > imgLocation ) { //7
                CGRect runBounds;
                CGFloat ascent; //height above thebaseline
                CGFloat descent; //height below thebaseline
                runBounds.size.width =CTRunGetTypographicBound s (run,CFRangeMake ( 0, 0 ), &ascent, &descent, NULL ); //8
                runBounds.size.height = ascent + descent;
 
                CGFloat xOffset =CTLineGetOffsetForString Index (line,CTRunGetStringRange (run ).location, NULL ); //9
                runBounds.origin.x = origins [lineIndex ].x +self.frame.origin.x + xOffset + frameXOffset;
                runBounds.origin.y = origins [lineIndex ].y +self.frame.origin.y +frameYOffset;
                runBounds.origin.y -= descent;
 
                UIImage *img = [UIImage imageNamed : [nextImage objectForKey : @ "fileName" ] ];
                CGPathRef pathRef = CTFrameGetPath (f ); //10
                CGRect colRect = CGPathGetBoundingBox (pathRef );
 
                CGRect imgBounds = CGRectOffset (runBounds, colRect.origin.x - frameXOffset - self.contentOffset.x, colRect.origin.y - frameYOffset - self.frame.origin.y );
                [col.images addObject : //11
                    [ NSArray arrayWithObjects :img, NSStringFromCGRect (imgBounds ) , nil ]
                  ];
                //load the next image//12
                imgIndex ++;
                if (imgIndex < [self.images count ] ) {
                    nextImage = [self.imagesobjectAtIndex :imgIndex ];
                    imgLocation = [ [nextImage objectForKey : @ "location" ] intValue ];
                }
 
            }
        }
        lineIndex ++;
    }
}

注意:这个代码基础最终归功于大卫·贝克提供的循环示例。
我知道刚看到这些代码感觉非常低级,但和我一起忍受一下,我们已经到了本教程结尾,这是最后的冲刺!
一段段来说:

  1. CTFrameGetLines 获取 CTLine 数组对象。
  2. 获取当前帧中所有行的原点 – 简单说,你得到一个所有文本行的左上点坐标的列表。
  3. 从 self.images 得到第一个图片的属性数据并在 imgLocation 保存此图片所在文本中的位置。
  4. CTFrameGetVisibleStringRange 获取在当前帧中可见的文本范围 –通过对图片数组的循环对比图片在文本中的位置找到将要渲染的第一个可见图片 – 换一种说法 –你快进到有关您目前呈现的文本块的图像。
  5. 循环每一行并把行放置到 line 变量。
  6. 循环行中的每个分段(run) (通过 CTLineGetGlyphRuns 得到 runs)。
  7. 核查下个图片是否在当前分段(run)内 – 如果是你要进一步确定图片所在的精确位置。
  8. 使用 CTRunGetTypographicBounds 方法获取此分段(run)的大小。
  9. 使用 CTLineGetOffsetForStringIndex 加上其它偏移获取分段的起点。
  10. 通过图片名称加载图片到 “img” 变量的图片并计算图片要渲染的矩形区域。
  11. 创建一个包括 UIImage 和对应矩形的 NSArray 并把它添加到 CTColumnView 的图片列表。
  12. 最后读取列表中的下一个图片,直到文本行循环结束。

很棒!就快好了!- 还有很小的一步:找到 CTView.m 中的 “[contentsetCTFrame:(id)frame];” 行添加如下代码:

1
[selfattachImagesWithFrame :frameinColumnView :content ];

现在您完成了全部代码工作,不过还没有好的杂志内容…
没关系,我已经准备下一期的僵尸月刊 – 每月的流行僵尸杂志 – 你需要做的只是将下面内容导入:

  1. 在项目浏览器里删除早先创建的 test.txt
  2. 下载并解压 Zombie mag materials备用下载地址).
  3. 把所有文件拖到 Xcode 项目上导入它们。 确定 “Copy items into destination group’sfolder (if needed)” 选中,点击 Finish 。

然后切换到 CoreTextMagazineViewController.m 找到使用文件路径的地方切换为使用新的zombies.txt 文件:

1
NSString *path = [ [ NSBundle mainBundle ] pathForResource : @ "zombies" ofType : @ "txt" ];

完成了 – 编译并运行,享受最新一期的僵尸月刊!:)

zombieMag1

最后一步。假如我们想合理的分配列中的文本,使它们匹配列的宽度。添加下列代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//insideCTView.m
//at the end ofthe setAttString:withImages: method
 
CTTextAlignment alignment =kCTJustifiedTextAlignmen t;
 
CTParagraphStyleSetting settings [ ] = {
    {kCTParagraphStyleSpecifi erAlignment, sizeof (alignment ), &alignment },
};
CTParagraphStyleRef paragraphStyle = CTParagraphStyleCreate (settings, sizeof (settings ) / sizeof (settings [ 0 ] ) );
NSDictionary *attrDictionary = [ NSDictionarydictionaryWithObjectsAnd Keys :
                                ( id )paragraphStyle, ( NSString * )kCTParagraphStyleAttribu teName,
                                nil ];
 
NSMutableAttributedString * stringCopy = [ [ [ NSMutableAttributedStringalloc ]initWithAttributedString :self.attString ] autorelease ];
[stringCopyaddAttributes :attrDictionaryrange :NSMakeRange ( 0, [attString length ] ) ];
self.attString = ( NSAttributedString * )stringCopy;

这样你就可以控制段落式样了;查阅苹果的 Core Text 文档中的kCTParagraphStyleSpecifierAlignment,你将获得你可以控制的所有段落式样的列表。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值