原文:http://www.raywenderlich.com/4147/how-to-create-a-simple-magazine-app-with-core-text
本文作者Marin Todorov,拥有12年以上的软件开发经历,是一位独立的iOS开发者,同时是TouchCode 杂志的创建者。
Core Text是iOS 3.2和OSX10.5之后的文本引擎,能够精确控制文本的布局及样式。
它位于UIKit和CoreGraphics/Quartz之间:
在创建一个杂志或书刊类的应用时,Core Text尤其便利——而且在iPad上也工作得很好。
本文通过创建一本非常简单的杂志应用教你如何使用CoreText。你将学到:
在阅读本文之前,你需要有一点iOS开发基础。否则,你可以先阅读这个站点的其他教程。
让我们立即开始iPad杂志之旅。
打开Xcode,点击 File\New\New Project, 选择 iOS\Application\View-basedApplication, 点击 Next。项目名称命名为 CoreTextMagazine, Device family选择iPad, 点击 Next, 选择项目存放路径,然后点击 Create。
接下来将Core Text框架加到项目中。
Next thing you have to do is add the Core Text framework to theproject:
接下来编写一些代码。
为了便于使用Core Text,我们应当创建一个定制的UIView,然后在它的drawRect:方法中使用Core Text。
打开File\New\New File菜单,选择 iOS\Cocoa Touch\Objective-C class, 点击 Next。选择UIView的子类, 点击 Next, 类名命名为 CTView, 然后点击 Save。
在CTView.h 的 @interface 加入一下代码已包含 Core Text 框架:
#import <CoreText/CoreText.h> |
接下来,设置新的定制的UIView作为应用程序的主视图。
在项目导航窗口中选择 “CoreTextMagazineViewController.xib”文件,转到Xcode的 Utilities 工具条,选择第3个按钮即 Identity 栏.
现在界面编辑器中选择view对象,在Utilities窗口的Class栏,可以看到UIView字样,将它改为 “CTView” 然后回车。
现在,应用程序启动时将显示定制的Core Text视图了。接下来,我们编写一点代码作为测试。
打开CTView.m 删除所有方法。输入以下代码,以便在视图中绘制一个内容为 “Hello world” 的文本:
- (void)drawRect:(CGRect)rect { [super drawRect:rect]; CGContextRef context = UIGraphicsGetCurrentContext(); CGMutablePathRef path = CGPathCreateMutable(); //1 CGPathAddRect(path, NULL, self.bounds ); NSAttributedString* attString = [[[NSAttributedString alloc] initWithString:@"Hello core text world!"] autorelease]; //2 CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString); //3 CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [attString length]), path, NULL); CTFrameDraw(frame, context); //4 CFRelease(frame); //5 CFRelease(path); CFRelease(framesetter); } |
让我们逐句讨论以上代码(序号和代码后面注释的行号对应):
注意,在使用Core text时,请使用一系列函数如 CTFramesetterCreateWithAttributedString 和CTFramesetterCreateFrame 而尽量避免直接使用 Objective-C 对象。
你可能会奇怪“为什么又要用C,为什么不用Objective-C?”
这是因为在iOS中的许多低级类库,为了高效和简洁起见,都是用C写的。不必担心,你会发现CoreText函数其实蛮简单的。
此外,还有一件值得注意的事情:在用“Create”函数创建对象之后,不要忘记同时要用CFRelease函数释放它们。
无论如何,用Core Text绘制一段简单文本就是这么多了。点击Run,查看运行结果。
出现了什么问题?就好比许多低级APIs,CoreText使用的是反Y轴坐标系。因此文本内容被渲染成倒立的。我们需要记住一点,如果我们把UIKit和Core Text的绘制方向搞混了,你会得到奇怪的结果。让我们来解决这个问题。在
“CGContextRef context =UIGraphicsGetCurrentContext();”一行后加入一下代码:
// Flip the coordinate system CGContextSetTextMatrix(context, CGAffineTransformIdentity); CGContextTranslateCTM(context, 0, self.bounds.size.height); CGContextScaleCTM(context, 1.0, -1.0); |
代码很简单,仅仅是在当前视图的上下文中应用了一个转换(上下颠倒坐标系)。在每次绘制Core Text的时候复制粘贴这段代码。
点击Run——庆祝你的第一个CoreText程序吧!如果你搞不清什么是 CTFramesetter 和 CTFrame – 那么,我会在这里简单地解释一下Core Text是如何渲染文本内容的。
Core Text的对象模型如下图所示:首先用一个NSAttributedString做为参数创建一个CTFramesetter引用。这将自动创建一个CTTypesetter实例,它管理着你的字体。接下来使用这个CTFramesetter创建一个(或多个)文本块(CTFrame)。CTFrame用于渲染文本。
在创建文本块时,需要告诉它一个范围(NSRange),指定文本将在某个矩形内进行渲染。Core Text为每行文字自动创建一个CTLine,同时(注意这里)为每个CTLine中的每一段格式相同的文字创建一个CTRun。
例如,如果在一行中,有几个字是红色,另外几个字是黑色,剩余的字则为粗体,则Core Text将总共为它们创建3个CTRun。特别注意:不需要你来创建CTRun属性,Core Text会基于你提供的NSAttributedString来创建它们。
每个CTRun对象都采用各自的属性,因此你可以精确地控制诸如字距、连体、字宽、字重等属性。
创建杂志应用程序,我们需要标记出每段文字的属性。我们可以用NSAttributedString的setAttributes:range方法,但未免不够灵活(除非你愿意老老实实地写上成打的代码)。
更简单地方法,是创建一种简单标记语法的解析器,通过它对杂志内容进行格式化。
点击File\New\New File, 选择 iOS\Cocoa Touch\Objective-C class, 然后点击Next。选择 NSObject for Subclass of, 点击 Next,为类命名为MarkupParser.m, 然后点击 Save。
在MarkupParser.h 中删除所有代码,编辑代码,声明属性和方法如下:
#import <Foundation/Foundation.h> #import <CoreText/CoreText.h> @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 将其中内容替换为以下代码:
#import "MarkupParser.h" @implementation MarkupParser @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 <font color="red">red<font color="black"> and <font color="blue">blue <font color="black">words.
将产生如下输出:
These are red and blue words.
对于本文而言,这就足够了。在你自己的项目中,你可以扩展它。
让我们开始吧!
在attrStringFromMarkup: 方法中,加入:
NSMutableAttributedString* aString = [[NSMutableAttributedString alloc] initWithString:@""]; //1 NSRegularExpression* regex = [[NSRegularExpression alloc] initWithPattern:@"(.*?)(<[^>]+>|\\Z)" options:NSRegularExpressionCaseInsensitive| NSRegularExpressionDotMatchesLineSeparators error:nil]; //2 NSArray* chunks = [regex matchesInString:markup options:0 range:NSMakeRange(0, [markup length])]; [regex release]; |
以上代码分为两步:
为什么要创建这个正则表达式?我们用它来搜索字符串中每个匹配的位置,然后:
1)渲染所找到的文本部分;
2)根据所找到标签改变当前样式。
整个过程重复进行,直至文本结束。
确实很简单,不是吗?