现在,我们已经获得所有的文本块和格式标签(如同前面见到的<font>标签)。需要做的仅仅是遍历文本块数组然后构建NSAttributedString .
在方法体中加入以下代码:
for (NSTextCheckingResult* b in chunks) {
NSArray* parts = [[markup substringWithRange:b.range]
componentsSeparatedByString:@"<"]; //1
CTFontRef fontRef = CTFontCreateWithName(
(CFStringRef)self.font,
24.0f, NULL); //apply the current text style //2
NSDictionary* attrs=[NSDictionary dictionaryWithObjectsAndKeys:
(id)self.color.CGColor, kCTForegroundColorAttributeName,
(id)fontRef, kCTFontAttributeName,
(id)self.strokeColor.CGColor,kCTStrokeColorAttributeName,
(id)[NSNumber numberWithFloat: self.strokeWidth],
(NSString *)kCTStrokeWidthAttributeName, nil];
[aString appendAttributedString:[[[NSAttributedString alloc]
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"]) {
//stroke color
NSRegularExpression* scolorRegex = [[[NSRegularExpression
alloc] initWithPattern:@"(?<=strokeColor=\")\\w+"
options:0 error:NULL] autorelease];
[scolorRegex enumerateMatchesInString:tag options:0
range:NSMakeRange(0, [tag length])
usingBlock:^(NSTextCheckingResult *match,
NSMatchingFlags flags, BOOL *stop){
if ([[tag substringWithRange:match.range]
isEqualToString:@"none"]) {
self.strokeWidth = 0.0;
} else {
self.strokeWidth = -3.0;
SEL colorSel = NSSelectorFromString([NSString stringWithFormat: @"%@Color", [tag substringWithRange:match.range]]);
self.strokeColor = [UIColor
performSelector:colorSel];
}
}];
//color
NSRegularExpression* colorRegex = [[[NSRegularExpression
alloc] initWithPattern:@"(?<=color=\")\\w+"
options:0 error:NULL] autorelease];
[colorRegex enumerateMatchesInString:tag options: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:0 error:NULL] autorelease];
[faceRegex enumerateMatchesInString:tag options:0
range:NSMakeRange(0, [tag length])
usingBlock:^(NSTextCheckingResult *match,
NSMatchingFlags flags, BOOL *stop){
self.font = [tag substringWithRange:match.range];
}];
} //end of font parsing
} }
return (NSAttributedString*)aString;
代码稍有点多,但不要担心,我会逐句进行讲解。
注: 关于代码中正则式的具体含义,可以理解为“查找color="之后的字符串(由普通字符数字、字母和下划线组成,不包括标点符号),一直到右尖括号”。更多请参考苹果的“NSRegularExpression类参考”。
译者注:正则式“(?<=color=\")\\w+”可以分为两部分,第一部分“(?<=color=\")”使用了零宽度正向回查(非捕获组)模板“(?<=pattern)”,这部分匹配结果不捕获,第二部分“\\w+”匹配1或多个普通字符(数字、字母和下划线),这部分匹配结果会被捕获。
嗨,我们已经完成了一半的工作了!现在attrStringFromMarkup:方法会对markup进行分割,形成NSAttributedString,并准备传递给CoreText。
打开CTView.m,在 @implementation前面加入:
#import "MarkupParser.h"
将定义attString对象的语句替换为:
the line whereattString is defined - replace it with the following code:
MarkupParser* p = [[[MarkupParser alloc] init] autorelease]; NSAttributedString* attString = [p attrStringFromMarkup: @"Hello <font color=\"red\">core text <font color=\"blue\">world!"];
这里,我们创建了一个新的解析器对象,并传入一个使用了标记语法的字符串给attrStringFromMarkup:方法,已得到一个NSAttributeString字符串。
运行程序。很酷吧?仅仅50行的代码,没有计算文本位置、没有硬编码文本样式,仅仅用一个简单文本文件就可以编辑出杂志的内容!
只要你愿意,这个简单解析器可以无限制地扩展下去。
杂志的基本布局我们可以显示文本,这是一个好的开端。但作为杂志而言,我们希望能够分栏显示——使用Core Text很容易做到这点。
在这样做之前,首先让我们将长文本加载进应用程序,以便我们有足够的文本显示成多行。
点击File\New\New File,选择iOS\Other\Empty,点击 Next。将文件命名为 test.txt, 然后点击Save。
然后编辑文件内容,如 这个文件所示。
打开CTView.m ,找到创建 MarkupParser 和 NSAttributedString 的两句代码,删除它们。我们将在drawRect:方法之外加载text文件,这个功能不应该由drawRect:方法来实现。将attString修改为实例变量和类的属性。
打开 CoreTextMagazineViewController.m,删除所有内容,加入以下内容:
#import "CoreTextMagazineViewController.h"
#import "CTView.h"
#import "MarkupParser.h"
@implementation CoreTextMagazineViewController
- (void)viewDidLoad {
[super viewDidLoad];
NSString *path = [[NSBundle mainBundle] pathForResource:@"test"
ofType:@"txt"];
NSString* text = [NSString stringWithContentsOfFile:path
encoding:NSUTF8StringEncoding error:NULL];
MarkupParser* p = [[[MarkupParser alloc] init] autorelease];
NSAttributedString* attString = [p attrStringFromMarkup: text];
[(CTView*)self.view setAttString: attString];
}
@end
当应用程序的视图一被加载,会读取test.txt文件的内容,将其转换为NSAttributedString,然后设置到CTView的attString属性。当然,我们需要为CTView增加相应的属性。
在CTView.h中定义 3 个实例变量:
float frameXOffset;
float frameYOffset;
NSAttributedString* attString;
在CTView.h 和 CTView.m 中定义 attString属性:
//CTView.h
@property (retain, nonatomic) NSAttributedString* attString;
//CTView.m
//just below @implementation ...
@synthesize attString;
//at the bottom of the file
-(void)dealloc {
self.attString = nil;
[super dealloc];
}
运行程序,文本文件的内容被显示到了屏幕上。
如何将这些文本分栏?CoreText提供了一个便利函数 - CTFrameGetVisibleStringRange。该函数能够告诉你在一个固定的框内能够放下多少文字。基本思路是——创建栏,判断它适合放入多少文字,如果放不下——创建新的栏,以此类推(这里的“栏”就是一个CTFrame实例,栏其实就是一个矩形框)。
首先我们应当有栏,然后是页,然后是整本杂志,因此我们让CTView的继承了UIScrollView,以便获得分页和滚动的能力。
打开CTView.h ,将 @interface 一行改为:
@interface CTView : UIScrollView<UIScrollViewDelegate> {
现在,CTView已经继承了UIScrollView。我们要让它能够分页。
我们已经在drawRect:方法中创建了framesetter和frame。当存在分栏且样式各不同的情况下,更好的方法是让这个动作只需进行一次。因此我们将创建一个新的类CTColumnView,它仅仅负责渲染指定的CoreText文本,在我们的CTView类中,我们只需创建一次CTColumnView并将它们加到subViews中。
简单而言:CTView负责滚动、分页和创建栏;而CTColumnView将实际上负责将文本内容渲染在屏幕上。
点击File\New\New File, 选择 iOS\Cocoa Touch\Objective-C class, 然后点击Next。选择 UIView 作为父类,点击 Next, 将新类命名为 CTColumnView.m, 然后点击Save. 这是CTColumnView类代码:
//inside CTColumnView.h
#import <UIKit/UIKit.h>
#import <CoreText/CoreText.h>
@interface CTColumnView : UIView {
id ctFrame;
}
-(void)setCTFrame:(id)f;
@end
//inside CTColumnView.m
#import "CTColumnView.h"
@implementation CTColumnView
-(void)setCTFrame: (id) f {
ctFrame = f;
}
-(void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
// Flip the coordinate system
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CTFrameDraw((CTFrameRef)ctFrame, context);
}
@end
这个类仅仅负责渲染一个CTFrame。在这本杂志中,我们应当为每个栏单独创建一个实例。
首先,要在CTView中加入一个属性,用于保存所有CTFrame,同时声明一个buildFrames方法,用于创建所有栏:
//CTView.h - at the top
#import "CTColumnView.h"
//CTView.h - as an ivar
NSMutableArray* frames;
//CTView.h - declare property
@property (retain, nonatomic) NSMutableArray* frames;
//CTView.h - in method declarations
- (void)buildFrames;
//CTView.m - just below
@implementation @synthesize frames;
//CTView.m - inside dealloc
self.frames = nil;
在buildFrames 方法中创建CTFrame并将它们添加到frames数组。
- (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 = CTFramesetterCreateWithAttributedString((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 column path
CTFrameRef frame = CTFramesetterCreateFrame(framesetter, CFRangeMake(textPos, 0), path, NULL);
CFRange frameRange=CTFrameGetVisibleStringRange(frame); //5
//create an empty column view
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 view contents and add it as subview
[content setCTFrame:(id)frame]; //6
[self.frames addObject: (id)frame];
[self addSubview: content];
//prepare for next frame
textPos += frameRange.length;
//CFRelease(frame);
CFRelease(path);
columnIndex++;
}
//set the total width of the scroll view
int totalPages = (columnIndex+1) / 2; //7
self.contentSize = CGSizeMake(totalPages*self.bounds.size.width, textFrame.size.height);
}
先来看看代码。
现在,当Coret Text准备妥当后调用buildFrames方法。在CoreTextMagazineViewController.m的viewDidLoad:方法最后加入代码:
[(CTView *)[self view] buildFrames];
在此之前,将CTView.m的drawRect:方法删除。现在,我们通过CTColumnView来渲染文字,因此不需要在CTView的drawRect:方法中做任何额外的工作。
点击Run,iPad屏幕显示如下图所示!左右滑动以进行翻页……简直是酷极了!
1:yiic执行出现不是内部或外部命令的解决办法
yii/framework/yiic.bat,
修改if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 为 if "%PHP_COMMAND%" == "" set PHP_COMMAND=D:\wamp\php\php.exe(后面php.exe的路径根据自己的安装环境而定)
在cmd.exe窗口中执行:
创建一个网站(yiicms) yiic webapp d:/wamp/www/yiicms
创建一个模块(admin)
yiic shell d:\wamp\www\yiicms\index.php
>> module admin
>> exit 退出shell 配置数据库和模块 先在配置文件main.php中设置连接mysql return array(.....
// 模块设置
'modules'=>array( //必须在 components 配置上面
'admin'=>array(),
),
// application components
'components'=>array(
......
'db'=>array(
'connectionString' => 'sqlite:protected/data/testdrive.db',
),
// uncomment the following to use a MySQL database
'db'=>array(
'connectionString' => 'mysql:host=localhost;dbname=yiicms',
'emulatePrepare' => true,
'username' => 'root',
'password' => '',
'charset' => 'utf8',
'tablePrefix' => 't_', // 指定表前缀
),
......
),
......
);
创建模型(Category.php)
yiic shell d:\wamp\www\yiicms\index.php
>> model Category
通过crud在模块admin下创建与Category.php模型对应的CategoryController.php和视图
>> crud Category admin/category
>> exit
有栏、有可以样式化的文字,但没有图片。用Core Text来绘制图片不太容易——它毕竟只是一个文本绘制框架。
幸运的是我们已经有一个简单的标签解析器。我们可以容易地从字符串中取出图片。
在 Core Text中绘制图片本质上,Core Text是不能绘制图形的。但是,它同时还是一个布局引擎,因此我们可以让它流出一些空白以便我们有绘图的空间。在drawRect:方法中绘图是一件简单的事。
首先看我们如何在文本绘制时保留足够的空白空间。还记得文本块其实都是一些CTRun实例吗?简单地为某个CTRun指定一个委托,然后让委托对象告诉Core Text要该 CTRun 上升/下降多少空间,以及空间的宽度。如下图所示:
当Core Text“到达”某个被设置了委托的CTRun时,它会询问委托“要预留给这个CTRun多少宽度和高度?”。这样,就可以在文本中挖出一个洞——用于绘制图片。
让我们在我们的标签解析器中增加对<img>的支持。打开MarkupParser.m找到"} //end of font parsing"一行,在后面加入一下代码:
if ([tag hasPrefix:@"img"]) {
__block NSNumber* width = [NSNumbernumberWithInt:0];
__block NSNumber* height = [NSNumbernumberWithInt:0];
__block NSString* fileName = @"";
//width
NSRegularExpression* widthRegex =[[[NSRegularExpression alloc]initWithPattern:@"(?<=width=\")[^\"]+" options:0error:NULL] autorelease];
[widthRegex enumerateMatchesInString:tagoptions:0 range:NSMakeRange(0, [tag length]) usingBlock:^(NSTextCheckingResult*match, NSMatchingFlags flags, BOOL *stop){
width = [NSNumbernumberWithInt: [[tag substringWithRange: match.range] intValue] ];
}];
//height
NSRegularExpression* faceRegex =[[[NSRegularExpression alloc]initWithPattern:@"(?<=height=\")[^\"]+" options:0error:NULL] autorelease];
[faceRegex enumerateMatchesInString:tagoptions:0 range:NSMakeRange(0, [tag length]) usingBlock:^(NSTextCheckingResult*match, NSMatchingFlags flags, BOOL *stop){
height = [NSNumbernumberWithInt: [[tag substringWithRange:match.range] intValue]];
}];
//image
NSRegularExpression* srcRegex =[[[NSRegularExpression alloc]initWithPattern:@"(?<=src=/index.html")[^\"]+" options:0error:NULL] autorelease];
[srcRegex enumerateMatchesInString:tag options:0range:NSMakeRange(0, [tag length]) usingBlock:^(NSTextCheckingResult *match,NSMatchingFlags flags, BOOL *stop){
fileName = [tagsubstringWithRange: match.range];
}];
//add the image for drawing
[self.images addObject:
[NSDictionarydictionaryWithObjectsAndKeys:
width, @"width",
height, @"height",
fileName, @"fileName",
[NSNumber numberWithInt: [aStringlength]], @"location",
nil]
];
//render empty space for drawing the image inthe text //1
CTRunDelegateCallbacks callbacks;
callbacks.version = kCTRunDelegateVersion1;
callbacks.getAscent = ascentCallback;
callbacks.getDescent = descentCallback;
callbacks.getWidth = widthCallback;
callbacks.dealloc = deallocCallback;
NSDictionary* imgAttr = [[NSDictionarydictionaryWithObjectsAndKeys: //2
width, @"width",
height, @"height",
nil] retain];
CTRunDelegateRef delegate = CTRunDelegateCreate(&callbacks,imgAttr); //3
NSDictionary *attrDictionaryDelegate =[NSDictionary dictionaryWithObjectsAndKeys:
//set the delegate
(id)delegate, (NSString*)kCTRunDelegateAttributeName,
nil];
//add a space to the text so that it can callthe delegate
[aStringappendAttributedString:[[[NSAttributedString alloc] initWithString:@"" attributes:attrDictionaryDelegate] autorelease]];
}
阅读这段代码——实际上,<img>标签并不像<font>标签那么好解析。需要使用3个正则式,才能读取到<img>的三个属性:width、height和src。然后,用一个NSDictionar保存这些信息(另外再加上图片在文字中出现的位置)并添加到self.images中。
来到第1个代码块——CTRunDelegateCallbacks 是一个结构体,结构体中包含了一些函数指针。该结构体包含了你想告诉给CTRunDelegate的一些信息。你也许猜到了,getWith调用将告诉CTRun的宽度,getAscent调用将告诉CTRun的高度等等。在这段代码中,你为这些handler提供了函数名,随后我们将为这些函数提供实现。
代码块2非常重要——imgAttr字典保存了图片的尺寸;同时这个对象会被传递给处理函数——因此,当getAscent函数被触发时,它将收到一个imgAttr参数,同时读取图片的高度,以便传递给CoreText。
在代码块3,CTRunDelegateCreate函数创建了委托实例并将imgAttr和指定的CTRunDelgateCallBacks进行绑定。
下一步,我们需要创建一个字典(和前面创建字体属性是一样的),但将字体样式属性替代以委托对象。最后,我们加入了一个空格字符,以便触发委托方法和创建文本“空洞”用于绘制图片。
接下来,你可能想到了,我们要让委托对象提供回调函数的实现。
//inside MarkupParser.m, just above@implementation
/* Callbacks */
static void deallocCallback( void* ref ){
[(id)ref release];
}
static CGFloat ascentCallback( void *ref){
return [(NSString*)[(NSDictionary*)refobjectForKey:@"height"] floatValue];
}
static CGFloat descentCallback( void *ref){
return [(NSString*)[(NSDictionary*)refobjectForKey:@"descent"] floatValue];
}
static CGFloat widthCallback( void* ref){
return [(NSString*)[(NSDictionary*)refobjectForKey:@"width"] floatValue];
}
ascentCallback, descentCallback 和 widthCallback仅仅读取了字典中的对应内容返回给Core Text。deallocCallback则释放字典——当CTRunDelegate被释放时调用,因此这里是让你进行内存管理的地方。
在为解析器增加对<img>标签的处理之后,我们还需要修改CTView。首先需要定义一个方法,将images数组传递给视图,我们可以将NSAttributedString和imgAttr一起传递给这个方法。
//CTView.h - inside @interfacedeclaration as an ivar
NSArray* images;
//CTView.h - declare property for images
@property (retain, nonatomic) NSArray*images;
//CTView.h - add a method declaration
-(void)setAttString:(NSAttributedString*)attString withImages:(NSArray*)imgs;
//CTView.m - just below @implementation
@synthesize images;
//CTView.m - inside the dealloc method
self.images = nil;
//CTView.m - anywhere inside theimplementation
-(void)setAttString:(NSAttributedString*)string withImages:(NSArray*)imgs
{
self.attString = string;
self.images = imgs;
}
好了,CTView已经能够接收一个图片数组了,让我们从解析器将图片传递给它就可以了。
转到CoreTextMagazineViewController.m,找到这行“[(CTView*)self.view setAttString: attString];”,修改为:
[(CTView *)[self view] setAttString:attString withImages: p.images];
MarkupParser的attrStringFromMarkup:方法将所有的图片标签解析为数据放入了self.images,也就是你现在传给CTView的东西。
渲染图片,首先要算出图片将要显示的准确位置。计算过程如下:
- contentView滚动时的contentOffset
- 相对于CTView的偏移 (frameXOffset,frameYOffset)
- CTLine 的原点坐标(CTLine和这段文本的起始位置有一个偏移量)
- CTLine起点和CTRun起点之间的差距
现在开始绘制图片!首先修改 CTColumnView 类:
/inside CTColumnView.h
//as an ivar
NSMutableArray* images;
//as a property
@property (retain, nonatomic)NSMutableArray* images;
//inside CTColumnView.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 of drawRect:
for (NSArray* imageData in self.images) {
UIImage* img = [imageData objectAtIndex:0];
CGRectimgBounds = CGRectFromString([imageData objectAtIndex:1]);
CGContextDrawImage(context, imgBounds,img.CGImage);
}
我们修改了几个地方:增加了本地变量images和对应的属性,用于持有在每个文本栏中的图片列表。为求简便,我们没有声明新的类,而是直接在images数组中存放了:
现在,我们来计算图片的位置并将它们添加到对应的文本栏中:
//inside CTView.h
-(void)attachImagesWithFrame:(CTFrameRef)finColumnView:(CTColumnView*)col;
//inside CTView.m
-(void)attachImagesWithFrame:(CTFrameRef)finColumnView:(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.imagesobjectAtIndex:imgIndex];
int imgLocation = [[nextImageobjectForKey:@"location"] intValue];
//find images for the current column
CFRange frameRange =CTFrameGetVisibleStringRange(f); //4
while ( imgLocation < frameRange.location ) {
imgIndex++;
if(imgIndex>=[self.images count]) return; //quit if no images for this column
nextImage = [self.imagesobjectAtIndex:imgIndex];
imgLocation =[[nextImage objectForKey:@"location"] intValue];
}
NSUInteger lineIndex = 0;
for (id lineObj inlines) { //5
CTLineRef line =(CTLineRef)lineObj;
for (id runObj in(NSArray *)CTLineGetGlyphRuns(line)) { //6
CTRunRef run = (CTRunRef)runObj;
CFRange runRange = CTRunGetStringRange(run);
if (runRange.location <= imgLocation &&runRange.location+runRange.length > imgLocation ) { //7
CGRect runBounds;
CGFloat ascent;//height above the baseline
CGFloat descent;//height below the baseline
runBounds.size.width= CTRunGetTypographicBounds(run, CFRangeMake(0, 0), &ascent, &descent,NULL); //8
runBounds.size.height = ascent + descent;
CGFloat xOffset = CTLineGetOffsetForStringIndex(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: [nextImageobjectForKey:@"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.images objectAtIndex: imgIndex];
imgLocation = [[nextImage objectForKey: @"location"]intValue];
}
}
}
lineIndex++;
}
}
注:这段代码基于DavidBeck的提供的代码而来,非常感谢David Beck!
这段代码不是很好阅读,但你千万再忍耐一下——本文即将结束,我们都即将解脱!
这段代码解说如下:
好了!还有一个小地方:在 CTView.m 中找到行 “[content setCTFrame:(id)frame];”,在前面插入:
[self attachImagesWithFrame:frame inColumnView: content];
最后一件事,你需要提供一些大文本以供测试。
不用担心,我已经为你准备好了下一期的“僵尸月刊”——一个关于僵尸的大众月刊,你只需要:
打开CoreTextMagazineViewController.m ,找到获取test.txt文件路径的一行,替换为:
NSString *path = [[NSBundle mainBundle] pathForResource:@"zombies" ofType:@"txt"];
编译运行,开始享受最新一期的僵尸月刊吧!
还剩最后一环节。就是使文本自动适应栏宽。加入代码:
//在CTView.m文件
// setAttString:withImages: 方法最后
CTTextAlignment alignment =kCTJustifiedTextAlignment;
CTParagraphStyleSetting settings[] = {
{kCTParagraphStyleSpecifierAlignment,sizeof(alignment), &alignment},
};
CTParagraphStyleRef paragraphStyle =CTParagraphStyleCreate(settings, sizeof(settings) / sizeof(settings[0]));
NSDictionary *attrDictionary =[NSDictionary dictionaryWithObjectsAndKeys:
(id)paragraphStyle, (NSString*)kCTParagraphStyleAttributeName,
nil];
NSMutableAttributedString* stringCopy =[[[NSMutableAttributedString alloc] initWithAttributedString:self.attString]autorelease];
[stringCopy addAttributes:attrDictionaryrange:NSMakeRange(0, [attString length])];
self.attString =(NSAttributedString*)stringCopy;
这会让你开始使用段落格式, 在苹果Core Text文档中查看 kCTParagraphStyleSpecifierAlignment ,可以找到你能使用的所有段落样式。
什么时候使用 Core Text ?为什么要用 Core Text?现在你的Core Text杂志应用程序已经完成了,你可能会问自己:为什么要用CoreText而不是UIWebView呢?
CT和UIWebView有各自的应用场景。
UIWebView是一个成熟的web浏览器,仅仅为了显示一行由多个颜色组成文字未免有点杀鸡用牛刀了。
如果你的UI上有10个多色标签,那你岂不是要用10个Safari?那会占用多少内存?
请记住:UIWebView是一个好的浏览器,而CoreText是一个高效的文本渲染引擎。
Core Text还能做些什么?这个教程中使用的完整示例在这里下载: CoreText example project 。
如果你想在这个项目上进行扩展,并了解更多Core Text的内容,请阅读苹果的 CoreText Reference Collection 。然后考虑在这个程序中加入以下特性:
- 为解析器添加更多标签的支持
- 为CTRun增加更多的格式
- 增加更多的段落格式
- 为单词、段落和句子增加自适应格式
- 连体字及字距支持
我想,你可能已经想到如何去扩展解析器了,我有两个建议:
- 解析HTML可以使用并扩展 Ben Reeves的Obj-C HTML Parser;