读书人

用Core Text创办简单杂志应用(3)

发布时间: 2013-04-09 16:45:09 作者: rapoo

用Core Text创建简单杂志应用(3)

有栏、有可以样式化的文字,但没有图片。用Core Text来绘制图片不太容易——它毕竟只是一个文本绘制框架。

幸运的是我们已经有一个简单的标签解析器。我们可以容易地从字符串中取出图片。

在 Core Text中绘制图片

本质上,Core Text是不能绘制图形的。但是,它同时还是一个布局引擎,因此我们可以让它流出一些空白以便我们有绘图的空间。在drawRect:方法中绘图是一件简单的事。

首先看我们如何在文本绘制时保留足够的空白空间。还记得文本块其实都是一些CTRun实例吗?简单地为某个CTRun指定一个委托,然后让委托对象告诉Core Text要该 CTRun 上升/下降多少空间,以及空间的宽度。如下图所示:

用Core Text创办简单杂志应用(3)

当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=\")[^\"]+" 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的东西。

渲染图片,首先要算出图片将要显示的准确位置。计算过程如下:

读书人网 >移动开发

热点推荐