您的位置:首页 > 移动开发

如何使用Core Text创建一个简单的杂志图书App

2014-11-15 17:04 776 查看
原始地址: http://www.raywenderlich.com/4147/how-to-create-a-simple-magazine-app-with-core-text

博文的作者是"iOS Tutorial Team"成员 Marin Todorov, 有着12年的软件开发经验,是一个独立开发者,是Touch
Code Magazine 的作者。

Core Text是iOS 3.2+和OSX 10.5+的文本渲染引擎,可以让你自由的控制文本格式和排版。

Core Text不同于UIKit和Core Graphics/Quartz(虽然通过后两者你也可以进行文字渲染):

在UIKit中,你可以使用UILabel来显示文字,它的操作非常简单,但是你没有办法控制UILabel中单独一个字符的字体颜色。也就是说,没有办法进行富文本的显示。
在Core Graphics/Quartz中你可以非常漂亮的做系统所能做的每一件事,但是你必须要自己计算每个字符的位置,然后再把它渲染到屏幕上。也就是说它无法进行文字排版。

Core Text 正是以上两点的结合。你既可以控制文字的位置、布局、颜色、大小等等属性,又不需要实际操心字符位置、文字断行等琐事。

这篇教程会通过创建一个简单的杂志应用来教你使用Core Text。你将学到:

排版文字并渲染到屏幕上;
调整文字外观;

在文本里嵌入图片;

最终创建一个杂志应用。通过简单的操作控制文本格式。

这篇教程的读者应该具有基本的iOS开发知识,否则可能需要先google一下入门教程。

废话不多说了,让我们开始吧。


创建一个工程,把Core Text加入到工程中

通过XCode创建一个工程,取名叫 CoreTextMagazine, 选择iPad Device family。Core Text默认是没有加入到工程中的,你需要手动把Core Text.framework加入到工程中。步骤就不多说了,可以参考下图所示。



基本工作做完了,下面我们开始写代码。


添加一个Core Text View

为了方便的使用Core Text,你需要先创建一个自定义UIView,我们将在drawRect函数里面使用Core Text。

我们要添加一个继承自UIView的新文件,取名较CTView,在其头文件里面添加:

[objc] view
plaincopy

<span style="color:#6e371a">#import <CoreText/CoreText.h></span>

下一步,你要把CTView设置为应用的main view(或者你也可以new一个新的CTView,然后addSubview到当前viewController里面),如下图所示:





Now your application will show your custom Core Text view when started, but we’ll do that in a moment – let’s first add the code to draw some text so we have what to test.

让我们添加一些测试代码,打开CTView.m and 用下面的代码替换原来的drawRect,我们将在view中显示"Hello core text world!":

[cpp] view
plaincopy

- (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);

}

让我们对应注释中的数字解释一下上面的代码:

我们创建了一个CGPath,来做完绘制文字的区域。Core Text在Mac下支持不同的形状比如矩形、环形,但是在iOS里只支持矩形。在这个示例中,我们用整个屏幕来做完显示区域,所以通过self.bounds来创建一个CGPath。
在Core Text我们要渲染的文字不是用NSString而是用NSAttributedString。 NSAttributedString是非常强力的类,它允许你对文字设置不同的样式。然而暂时我们还用不到这些,现在仅仅通过纯文本创建一个NSAttributedString。
CTFramesetter是Core Text中非常重要的一个类。 它管理了你的字体和文本渲染块(CTFrame). 暂时你需要知道的是CTFramesetterCreateWithAttributedString 会通过一个NSAttributedString创建一个 CTFramesetter。下面通过刚刚创建的framesetter来创建一个frame, CTFramesetterCreateFrame的参数分别是:一个framesetter、将显示的文字的range(这里我们使用这个文字长度)、文本将要显示的区域(刚刚创建的CGPath).
CTFrameDraw在给定context里面绘制frame。
最后别忘记清理资源

你可能注意到了,Core Text提供的都是c形式的api二不是Objective-C对象。许多iOS上系统级别的库为了效率和通用都是用纯c写的。别担心,你会发现Core Text函数用起来都非常简单。最重要的事情是别忘记CFRelease掉所有你Create出来的引用。

点击Run来看看我们的成果吧>_<



额,好吧,看起来不太对。那是因为Core Text使用的是Y-flipped坐标系统,所以在UIKit中使用Core Text绘制出来的结果都是翻转的。解决方法是,我们需要在

“CGContextRef context = UIGraphicsGetCurrentContext();”之后添加如下代码:

[cpp] view
plaincopy

// Flip the coordinate system

CGContextSetTextMatrix(context, CGAffineTransformIdentity);

CGContextTranslateCTM(context, 0, self.bounds.size.height);

CGContextScaleCTM(context, 1.0, -1.0);

代码非常简单,就是将context给翻转一下,好了,点击Run,来看看我们第一个Core Text应用:




Core Text对象模型

如果你对 CTFramesetter 和 CTFrame的概念还有些模糊。那我们简单解释一下Core Text渲染文本的一些基本概念。

下图是Core Text对象模型:



我们通过NSAttributedString创建一个CTFramesetter,这时候会自动创建一个 CTTypesetter实例,它负责管理字体,下面通过CTFramesetter来创建一个或多个frame来渲染文字。然后Core Text会根据frame的大小自动创建CTLine(每行对应一个CTLine)和CTRun(相同格式的一个或多个相邻字符组成一个CTRun)。

举例来说,Core Text将创建一个CTRun来绘制一些红色文字,然后创建一个CTRun来绘制纯文本,然后再创建一个CTRun来绘制加粗文字等等。要注意,你不需要自己创建CTRun,Core Text将根据NSAttributedString的属性来自动创建CTRun。每个CTRun对象对应不同的属性,正因此,你可以自由的控制字体、颜色、字间距等等信息。


开始创建杂志应用

在一个杂志应用中,我们的文字可能有不同的属性。我们可以直接使用 NSAttributedString 的函数 setAttributes:range来设置属性,但是这使用起来并不方便 (除非你想写一大坨恶心的代码)

所以我们将先写一个简单的文本标签分析器,它将负责解析杂志的文本内容。

创建一个新文件名叫MarkupParser 下面分别是头文件和实现文件的代码:

[cpp] view
plaincopy

#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

[cpp] view
plaincopy

#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

如你所见,这个解析器包含了一些文本属性,比如字体,文本颜色,划线宽度,划线颜色,将来我们会在文本中添加图片,所以还有一个图片列表。

这个解析器将非常简单,并且只支持opening标签,一个标签将影响其后的所有文字,直到另一个标签。比如

These are <font color="red">red<font color="black"> and
<font color="blue">blue <font color="black">words.


将会显示成:
These are <span style="color: rgb(255, 0, 0);">red</span> and <span style="color: rgb(0, 0, 255);">blue</span> words.


For the purpose of the tutorial such markup will be quite sufficient. For your projects you can develop it further if you'd like to.

Let's get parsin'!

对于一个教程来说,这就足够了。对于你自己的项目,你应该做的更多。

在attrStringFromMarkup函数中添加如下代码:

[cpp] view
plaincopy

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];

让我们来解释一下:

首先创建了一个空的字符串;
Next, you create a regex to match chunks of text and tags. This regex will basically match a string of text and a following tag. The regular expression basically says "Look for any number of characters, until you come across an opening bracket. Then match
any number of characters until you hit a closing bracket. Or - stop processing when you hit the end of the string."

Why are we creating this regular expression? We're going to use it to search the string for every place it matches, and then 1) render the text chunk found; then 2) change the current styles according to what's found in the tag. This will be repeated until
the text is over.

Very simple parser indeed, eh?

Now that you have the chunks of text and all the formatting tags (like the font tag you see a bit above) in the "chunks" array, you'll need to loop trough them and build the attributed string from the text and tags.

Add this to the method body:

[objc] view
plaincopy

<span style="color:#a61390">for</span> <span style="color:#002200">(</span>NSTextCheckingResult<span style="color:#002200">*</span> b <span style="color:#a61390">in</span> chunks<span style="color:#002200">)</span> <span style="color:#002200">{</span>

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color:#400080">NSArray</span></a><span style="color:#002200">*</span> parts <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span>markup substringWithRange<span style="color:#002200">:</span>b.range<span style="color:#002200">]</span>

componentsSeparatedByString<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"<"</span><span style="color:#002200">]</span>; <span style="color:#11740a; font-style:italic">//1</span>

CTFontRef fontRef <span style="color:#002200">=</span> CTFontCreateWithName<span style="color:#002200">(</span><span style="color:#002200">(</span>CFStringRef<span style="color:#002200">)</span>self.font,

24.0f, <span style="color:#a61390">NULL</span><span style="color:#002200">)</span>;

<span style="color:#11740a; font-style:italic">//apply the current text style //2</span>

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color:#400080">NSDictionary</span></a><span style="color:#002200">*</span> attrs <span style="color:#002200">=</span> <span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color:#400080">NSDictionary</span></a> dictionaryWithObjectsAndKeys<span style="color:#002200">:</span>

<span style="color:#002200">(</span><span style="color:#a61390">id</span><span style="color:#002200">)</span>self.color.CGColor, kCTForegroundColorAttributeName,

<span style="color:#002200">(</span><span style="color:#a61390">id</span><span style="color:#002200">)</span>fontRef, kCTFontAttributeName,

<span style="color:#002200">(</span><span style="color:#a61390">id</span><span style="color:#002200">)</span>self.strokeColor.CGColor, <span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color:#400080">NSString</span></a> <span style="color:#002200">*</span><span style="color:#002200">)</span> kCTStrokeColorAttributeName,

<span style="color:#002200">(</span><span style="color:#a61390">id</span><span style="color:#002200">)</span><span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color:#400080">NSNumber</span></a> numberWithFloat<span style="color:#002200">:</span> self.strokeWidth<span style="color:#002200">]</span>, <span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color:#400080">NSString</span></a> <span style="color:#002200">*</span><span style="color:#002200">)</span>kCTStrokeWidthAttributeName,

<span style="color:#a61390">nil</span><span style="color:#002200">]</span>;

<span style="color:#002200">[</span>aString appendAttributedString<span style="color:#002200">:</span><span style="color:#002200">[</span><span style="color:#002200">[</span><span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color:#400080">NSAttributedString</span></a> alloc<span style="color:#002200">]</span> initWithString<span style="color:#002200">:</span><span style="color:#002200">[</span>parts objectAtIndex<span style="color:#002200">:</span><span style="color:#2400d9">0</span><span style="color:#002200">]</span> attributes<span style="color:#002200">:</span>attrs<span style="color:#002200">]</span> autorelease<span style="color:#002200">]</span><span style="color:#002200">]</span>;

CFRelease<span style="color:#002200">(</span>fontRef<span style="color:#002200">)</span>;

<span style="color:#11740a; font-style:italic">//handle new formatting tag //3</span>

<span style="color:#a61390">if</span> <span style="color:#002200">(</span><span style="color:#002200">[</span>parts count<span style="color:#002200">]</span>><span style="color:#2400d9">1</span><span style="color:#002200">)</span> <span style="color:#002200">{</span>

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color:#400080">NSString</span></a><span style="color:#002200">*</span> tag <span style="color:#002200">=</span> <span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color:#400080">NSString</span></a><span style="color:#002200">*</span><span style="color:#002200">)</span><span style="color:#002200">[</span>parts objectAtIndex<span style="color:#002200">:</span><span style="color:#2400d9">1</span><span style="color:#002200">]</span>;

<span style="color:#a61390">if</span> <span style="color:#002200">(</span><span style="color:#002200">[</span>tag hasPrefix<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"font"</span><span style="color:#002200">]</span><span style="color:#002200">)</span> <span style="color:#002200">{</span>

<span style="color:#11740a; font-style:italic">//stroke color</span>

NSRegularExpression<span style="color:#002200">*</span> scolorRegex <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span><span style="color:#002200">[</span>NSRegularExpression alloc<span style="color:#002200">]</span> initWithPattern<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"(?<=strokeColor=<span style="color:#2400d9">\"</span>)<span style="color:#2400d9">\\</span>w+"</span> options<span style="color:#002200">:</span><span style="color:#2400d9">0</span> error<span style="color:#002200">:</span><span style="color:#a61390">NULL</span><span style="color:#002200">]</span> autorelease<span style="color:#002200">]</span>;

<span style="color:#002200">[</span>scolorRegex enumerateMatchesInString<span style="color:#002200">:</span>tag options<span style="color:#002200">:</span><span style="color:#2400d9">0</span> range<span style="color:#002200">:</span>NSMakeRange<span style="color:#002200">(</span><span style="color:#2400d9">0</span>, <span style="color:#002200">[</span>tag length<span style="color:#002200">]</span><span style="color:#002200">)</span> usingBlock<span style="color:#002200">:^</span><span style="color:#002200">(</span>NSTextCheckingResult <span style="color:#002200">*</span>match, NSMatchingFlags flags, <span style="color:#a61390">BOOL</span> <span style="color:#002200">*</span>stop<span style="color:#002200">)</span><span style="color:#002200">{</span>

<span style="color:#a61390">if</span> <span style="color:#002200">(</span><span style="color:#002200">[</span><span style="color:#002200">[</span>tag substringWithRange<span style="color:#002200">:</span>match.range<span style="color:#002200">]</span> isEqualToString<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"none"</span><span style="color:#002200">]</span><span style="color:#002200">)</span> <span style="color:#002200">{</span>

self.strokeWidth <span style="color:#002200">=</span> <span style="color:#2400d9">0.0</span>;

<span style="color:#002200">}</span> <span style="color:#a61390">else</span> <span style="color:#002200">{</span>

self.strokeWidth <span style="color:#002200">=</span> <span style="color:#002200">-</span><span style="color:#2400d9">3.0</span>;

<span style="color:#a61390">SEL</span> colorSel <span style="color:#002200">=</span> NSSelectorFromString<span style="color:#002200">(</span><span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color:#400080">NSString</span></a> stringWithFormat<span style="color:#002200">:</span> <span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"%@Color"</span>, <span style="color:#002200">[</span>tag substringWithRange<span style="color:#002200">:</span>match.range<span style="color:#002200">]</span><span style="color:#002200">]</span><span style="color:#002200">)</span>;

self.strokeColor <span style="color:#002200">=</span> <span style="color:#002200">[</span>UIColor performSelector<span style="color:#002200">:</span>colorSel<span style="color:#002200">]</span>;

<span style="color:#002200">}</span>

<span style="color:#002200">}</span><span style="color:#002200">]</span>;

<span style="color:#11740a; font-style:italic">//color</span>

NSRegularExpression<span style="color:#002200">*</span> colorRegex <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span><span style="color:#002200">[</span>NSRegularExpression alloc<span style="color:#002200">]</span> initWithPattern<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"(?<=color=<span style="color:#2400d9">\"</span>)<span style="color:#2400d9">\\</span>w+"</span> options<span style="color:#002200">:</span><span style="color:#2400d9">0</span> error<span style="color:#002200">:</span><span style="color:#a61390">NULL</span><span style="color:#002200">]</span> autorelease<span style="color:#002200">]</span>;

<span style="color:#002200">[</span>colorRegex enumerateMatchesInString<span style="color:#002200">:</span>tag options<span style="color:#002200">:</span><span style="color:#2400d9">0</span> range<span style="color:#002200">:</span>NSMakeRange<span style="color:#002200">(</span><span style="color:#2400d9">0</span>, <span style="color:#002200">[</span>tag length<span style="color:#002200">]</span><span style="color:#002200">)</span> usingBlock<span style="color:#002200">:^</span><span style="color:#002200">(</span>NSTextCheckingResult <span style="color:#002200">*</span>match, NSMatchingFlags flags, <span style="color:#a61390">BOOL</span> <span style="color:#002200">*</span>stop<span style="color:#002200">)</span><span style="color:#002200">{</span>

<span style="color:#a61390">SEL</span> colorSel <span style="color:#002200">=</span> NSSelectorFromString<span style="color:#002200">(</span><span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color:#400080">NSString</span></a> stringWithFormat<span style="color:#002200">:</span> <span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"%@Color"</span>, <span style="color:#002200">[</span>tag substringWithRange<span style="color:#002200">:</span>match.range<span style="color:#002200">]</span><span style="color:#002200">]</span><span style="color:#002200">)</span>;

self.color <span style="color:#002200">=</span> <span style="color:#002200">[</span>UIColor performSelector<span style="color:#002200">:</span>colorSel<span style="color:#002200">]</span>;

<span style="color:#002200">}</span><span style="color:#002200">]</span>;

<span style="color:#11740a; font-style:italic">//face</span>

NSRegularExpression<span style="color:#002200">*</span> faceRegex <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span><span style="color:#002200">[</span>NSRegularExpression alloc<span style="color:#002200">]</span> initWithPattern<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"(?<=face=<span style="color:#2400d9">\"</span>)[^<span style="color:#2400d9">\"</span>]+"</span> options<span style="color:#002200">:</span><span style="color:#2400d9">0</span> error<span style="color:#002200">:</span><span style="color:#a61390">NULL</span><span style="color:#002200">]</span> autorelease<span style="color:#002200">]</span>;

<span style="color:#002200">[</span>faceRegex enumerateMatchesInString<span style="color:#002200">:</span>tag options<span style="color:#002200">:</span><span style="color:#2400d9">0</span> range<span style="color:#002200">:</span>NSMakeRange<span style="color:#002200">(</span><span style="color:#2400d9">0</span>, <span style="color:#002200">[</span>tag length<span style="color:#002200">]</span><span style="color:#002200">)</span> usingBlock<span style="color:#002200">:^</span><span style="color:#002200">(</span>NSTextCheckingResult <span style="color:#002200">*</span>match, NSMatchingFlags flags, <span style="color:#a61390">BOOL</span> <span style="color:#002200">*</span>stop<span style="color:#002200">)</span><span style="color:#002200">{</span>

self.font <span style="color:#002200">=</span> <span style="color:#002200">[</span>tag substringWithRange<span style="color:#002200">:</span>match.range<span style="color:#002200">]</span>;

<span style="color:#002200">}</span><span style="color:#002200">]</span>;

<span style="color:#002200">}</span> <span style="color:#11740a; font-style:italic">//end of font parsing</span>

<span style="color:#002200">}</span>

<span style="color:#002200">}</span>

<span style="color:#a61390">return</span> <span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color:#400080">NSAttributedString</span></a><span style="color:#002200">*</span><span style="color:#002200">)</span>aString;

Phew, this is a lot of code! But don't worry, we'll go over it here section by section.

You iterate over the chunks matched by the prior regular expression, and in this section you split the chunks by the "<" character (the tag opener). As a result, in parts[0] you have the text to add to the result and in parts[1] you have the content of
the tag that changes the formatting for the text that follows.
Next you create a dictionary holding a number of formatting options - this is the way you can pass formatting attributes to a NSAttributedString. Have a look at the key names - they are Apple defined constants which are pretty self-explanatory (of you can
check out Apple's Core Text String Attributes Reference for
full details). By calling appendAttributedString: the new text chunk with applied formatting is added to the result string.
Finally, you check if there's a tag found after the text; if it starts with "font" a regex is ran for every possible tag attribute. For the "face" attribute the name of the font is saved in self.font, for "color" I and you did a little trickery: for <font
color="red"> the text value "red" is taken by the colorRegex and then a selector "redColor" is created and performed on the UIColor

class - this (hehe) returns a UIColor instance of a red color. Note this trick works only for the predefined colors of UIColor (and can even cause your code to crash if you pass in a selector that does not exist!) but this is sufficient for this tutorial. The
stroke color attribute works much like the color attribute, but if the value of strokecolor is "none" just sets the stroke widht to 0.0, so no stroke will be applied to the text.

Note: If you're unsatiably curious how the regular expressions in this section work, they are basically saying ("Use the look-behind assertion to look for any text that is preceded by color=". Then match any normal word character (which does not include
a quote), which basically keeps matching until the close quote is found. For more details, check out Apple'sNSRegularExpression
class reference.

Right! Half the work of rendering formatted text is done - now attrStringFromMarkup: can take markup in and spit a NSAttributedString out ready to be fed to Core Text.

So let's pass in a string to render, and try it out!

Open CTView.m and add this just before @implementation:

[objc] view
plaincopy

<span style="color:#6e371a">#import "MarkupParser.h"</span>

Find the line where attString is defined - replace it with the following code:

[objc] view
plaincopy

MarkupParser<span style="color:#002200">*</span> p <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span><span style="color:#002200">[</span>MarkupParser alloc<span style="color:#002200">]</span> init<span style="color:#002200">]</span> autorelease<span style="color:#002200">]</span>;

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color:#400080">NSAttributedString</span></a><span style="color:#002200">*</span> attString <span style="color:#002200">=</span> <span style="color:#002200">[</span>p attrStringFromMarkup<span style="color:#002200">:</span> <span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"Hello <font color=<span style="color:#2400d9">\"</span>red<span style="color:#2400d9">\"</span>>core text <font color=<span style="color:#2400d9">\"</span>blue<span style="color:#2400d9">\"</span>>world!"</span><span style="color:#002200">]</span>;

Above you make a new parser, feed it a piece of markup and it gives you back formatted text.

That's it - hit Run and try it for yourself!



Ain't that just awesome? Thanks to 50 lines of parsing we don't have to deal with text ranges and code heavy text formatting, we can just use now a simple text file to hold the contents of our magazine app. Also the simple parser you just wrote can be extended
infinitely to support everything you'd need in your magazine app.


A Basic Magazine Layout

So far we have text showing up, which is a good first step. But for a magazine we'd like to have columns - and this is where Core Text becomes particularly handy.

Before proceeding with the layout code, let's first load a longer string into the app so we have something long enough to wrap across multiple lines.

Go to File\New\New File, choose iOS\Other\Empty, and click Next. Name the new file test.txt, and click Save.

Then add the text from this file into test.txt and save.

Open CTView.m and find the 2 lines where you create MarkupParser and NSAttributedString and delete them. We're taking the loading of the text file out of the drawRect: method, because that sort of code doesn't really belong there. It's the job of a UIView to
display content given to it - not load content. We'll move the attString variable to an instance variable and property in this class later.

Next open CoreTextMagazineViewController.m, delete all the existing content, and add the following instead:

[objc] view
plaincopy

<span style="color:#6e371a">#import "CoreTextMagazineViewController.h"</span>

<span style="color:#6e371a">#import "CTView.h"</span>

<span style="color:#6e371a">#import "MarkupParser.h"</span>

<span style="color:#a61390">@implementation</span> CoreTextMagazineViewController

<span style="color:#002200">-</span> <span style="color:#002200">(</span><span style="color:#a61390">void</span><span style="color:#002200">)</span>viewDidLoad

<span style="color:#002200">{</span>

<span style="color:#002200">[</span>super viewDidLoad<span style="color:#002200">]</span>;

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color:#400080">NSString</span></a> <span style="color:#002200">*</span>path <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSBundle_Class/"><span style="color:#400080">NSBundle</span></a> mainBundle<span style="color:#002200">]</span> pathForResource<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"test"</span> ofType<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"txt"</span><span style="color:#002200">]</span>;

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color:#400080">NSString</span></a><span style="color:#002200">*</span> text <span style="color:#002200">=</span> <span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color:#400080">NSString</span></a> stringWithContentsOfFile<span style="color:#002200">:</span>path encoding<span style="color:#002200">:</span>NSUTF8StringEncoding error<span style="color:#002200">:</span><span style="color:#a61390">NULL</span><span style="color:#002200">]</span>;

MarkupParser<span style="color:#002200">*</span> p <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span><span style="color:#002200">[</span>MarkupParser alloc<span style="color:#002200">]</span> init<span style="color:#002200">]</span> autorelease<span style="color:#002200">]</span>;

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color:#400080">NSAttributedString</span></a><span style="color:#002200">*</span> attString <span style="color:#002200">=</span> <span style="color:#002200">[</span>p attrStringFromMarkup<span style="color:#002200">:</span> text<span style="color:#002200">]</span>;

<span style="color:#002200">[</span><span style="color:#002200">(</span>CTView<span style="color:#002200">*</span><span style="color:#002200">)</span>self.view setAttString<span style="color:#002200">:</span> attString<span style="color:#002200">]</span>;

<span style="color:#002200">}</span>

<span style="color:#a61390">@end</span>

When the view of the application is loaded, the app reads the text from test.txt, converts it to an attributed string and then sets the attString property on the window's view. We haven't added that property to CTView yet though, so let's add that next!

In CTView.h define these 3 instance variables:

[objc] view
plaincopy

<span style="color:#a61390">float</span> frameXOffset;

<span style="color:#a61390">float</span> frameYOffset;

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color:#400080">NSAttributedString</span></a><span style="color:#002200">*</span> attString;

Then add the corresponding code in CTView.h and CTView.m to define a property for attString:

[objc] view
plaincopy

<span style="color:#11740a; font-style:italic">//CTView.h</span>

<span style="color:#a61390">@property</span> <span style="color:#002200">(</span>retain, nonatomic<span style="color:#002200">)</span> <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color:#400080">NSAttributedString</span></a><span style="color:#002200">*</span> attString;

<span style="color:#11740a; font-style:italic">//CTView.m</span>

<span style="color:#11740a; font-style:italic">//just below @implementation ...</span>

<span style="color:#a61390">@synthesize</span> attString;

<span style="color:#11740a; font-style:italic">//at the bottom of the file</span>

<span style="color:#002200">-</span><span style="color:#002200">(</span><span style="color:#a61390">void</span><span style="color:#002200">)</span>dealloc

<span style="color:#002200">{</span>

self.attString <span style="color:#002200">=</span> <span style="color:#a61390">nil</span>;

<span style="color:#002200">[</span>super dealloc<span style="color:#002200">]</span>;

<span style="color:#002200">}</span>

Now you can hit Run again to see the view showing the contents of the text file. Cool!



How to make columns out of this text? Luckily Core Text provides a handy function - CTFrameGetVisibleStringRange. This function tells you how much text will fit into a given frame. So the idea is - create column, check how much text fits inside, if there's
more - create another column, etc. etc. (columns here will be CTFrame instances, since columns are just taller rectangles)

First of all - we are going to have columns, then pages, then a whole magazine, so... let's make our CTView subclass UIScrollView to get free paging and scrolling!

Open up CTView.h and change the @interface line to:

[objc] view
plaincopy

<span style="color:#a61390">@interface</span> CTView <span style="color:#002200">:</span> UIScrollView<UIScrollViewDelegate> <span style="color:#002200">{</span>

OK! We've got free scrolling and paging now available. We're going to enable the paging in a minute.

Up to now we were creating our framesetter and frame inside the drawRect: method. When you have columns and different formatting it's better to do all those calculations only once. So what we are going to do is have a new class "CTColumnView" which will only
render CT content passed to it, and in our CTView class we're going to only once create instances of CTColumnView and add them as subviews.

So to summarize: CTView is going to take care of scrolling, paging and building the columns; CTColumnView will actually render the content on the screen.

Go to File\New\New File, choose iOS\Cocoa Touch\Objective-C class, and click Next. Enter UIView for "Subclass of", click Next, name the new class CTColumnView.m, and click Save. Here's the initial code for the CTColumnView class:

[objc] view
plaincopy

<span style="color:#11740a; font-style:italic">//inside CTColumnView.h</span>

<span style="color:#6e371a">#import <UIKit/UIKit.h></span>

<span style="color:#6e371a">#import <CoreText/CoreText.h></span>

<span style="color:#a61390">@interface</span> CTColumnView <span style="color:#002200">:</span> UIView <span style="color:#002200">{</span>

<span style="color:#a61390">id</span> ctFrame;

<span style="color:#002200">}</span>

<span style="color:#002200">-</span><span style="color:#002200">(</span><span style="color:#a61390">void</span><span style="color:#002200">)</span>setCTFrame<span style="color:#002200">:</span><span style="color:#002200">(</span><span style="color:#a61390">id</span><span style="color:#002200">)</span>f;

<span style="color:#a61390">@end</span>

<span style="color:#11740a; font-style:italic">//inside CTColumnView.m</span>

<span style="color:#6e371a">#import "CTColumnView.h"</span>

<span style="color:#a61390">@implementation</span> CTColumnView

<span style="color:#002200">-</span><span style="color:#002200">(</span><span style="color:#a61390">void</span><span style="color:#002200">)</span>setCTFrame<span style="color:#002200">:</span> <span style="color:#002200">(</span><span style="color:#a61390">id</span><span style="color:#002200">)</span> f

<span style="color:#002200">{</span>

ctFrame <span style="color:#002200">=</span> f;

<span style="color:#002200">}</span>

<span style="color:#002200">-</span><span style="color:#002200">(</span><span style="color:#a61390">void</span><span style="color:#002200">)</span>drawRect<span style="color:#002200">:</span><span style="color:#002200">(</span>CGRect<span style="color:#002200">)</span>rect

<span style="color:#002200">{</span>

CGContextRef context <span style="color:#002200">=</span> UIGraphicsGetCurrentContext<span style="color:#002200">(</span><span style="color:#002200">)</span>;

<span style="color:#11740a; font-style:italic">// Flip the coordinate system</span>

CGContextSetTextMatrix<span style="color:#002200">(</span>context, CGAffineTransformIdentity<span style="color:#002200">)</span>;

CGContextTranslateCTM<span style="color:#002200">(</span>context, <span style="color:#2400d9">0</span>, self.bounds.size.height<span style="color:#002200">)</span>;

CGContextScaleCTM<span style="color:#002200">(</span>context, <span style="color:#2400d9">1.0</span>, <span style="color:#002200">-</span><span style="color:#2400d9">1.0</span><span style="color:#002200">)</span>;

CTFrameDraw<span style="color:#002200">(</span><span style="color:#002200">(</span>CTFrameRef<span style="color:#002200">)</span>ctFrame, context<span style="color:#002200">)</span>;

<span style="color:#002200">}</span>

<span style="color:#a61390">@end</span>

This class does pretty much what we've been doing up to now - it just renders a CTFrame. We're going to create an instance of it for each text column in the magazine.

Let's first add a property to hold our CTView's text frames and declare the buildFrames method, which will do the columns setup:

[objc] view
plaincopy

<span style="color:#11740a; font-style:italic">//CTView.h - at the top</span>

<span style="color:#6e371a">#import "CTColumnView.h"</span>

<span style="color:#11740a; font-style:italic">//CTView.h - as an ivar</span>

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/"><span style="color:#400080">NSMutableArray</span></a><span style="color:#002200">*</span> frames;

<span style="color:#11740a; font-style:italic">//CTView.h - declare property</span>

<span style="color:#a61390">@property</span> <span style="color:#002200">(</span>retain, nonatomic<span style="color:#002200">)</span> <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/"><span style="color:#400080">NSMutableArray</span></a><span style="color:#002200">*</span> frames;

<span style="color:#11740a; font-style:italic">//CTView.h - in method declarations</span>

<span style="color:#002200">-</span> <span style="color:#002200">(</span><span style="color:#a61390">void</span><span style="color:#002200">)</span>buildFrames;

<span style="color:#11740a; font-style:italic">//CTView.m - just below @implementation</span>

<span style="color:#a61390">@synthesize</span> frames;

<span style="color:#11740a; font-style:italic">//CTView.m - inside dealloc</span>

self.frames <span style="color:#002200">=</span> <span style="color:#a61390">nil</span>;

Now buildFrames can create the text frames once and store them in the "frames" array. Let's add the code to do so.

[objc] view
plaincopy

<span style="color:#002200">-</span> <span style="color:#002200">(</span><span style="color:#a61390">void</span><span style="color:#002200">)</span>buildFrames

<span style="color:#002200">{</span>

frameXOffset <span style="color:#002200">=</span> <span style="color:#2400d9">20</span>; <span style="color:#11740a; font-style:italic">//1</span>

frameYOffset <span style="color:#002200">=</span> <span style="color:#2400d9">20</span>;

self.pagingEnabled <span style="color:#002200">=</span> <span style="color:#a61390">YES</span>;

self.delegate <span style="color:#002200">=</span> self;

self.frames <span style="color:#002200">=</span> <span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/"><span style="color:#400080">NSMutableArray</span></a> array<span style="color:#002200">]</span>;

CGMutablePathRef path <span style="color:#002200">=</span> CGPathCreateMutable<span style="color:#002200">(</span><span style="color:#002200">)</span>; <span style="color:#11740a; font-style:italic">//2</span>

CGRect textFrame <span style="color:#002200">=</span> CGRectInset<span style="color:#002200">(</span>self.bounds, frameXOffset, frameYOffset<span style="color:#002200">)</span>;

CGPathAddRect<span style="color:#002200">(</span>path, <span style="color:#a61390">NULL</span>, textFrame <span style="color:#002200">)</span>;

CTFramesetterRef framesetter <span style="color:#002200">=</span> CTFramesetterCreateWithAttributedString<span style="color:#002200">(</span><span style="color:#002200">(</span>CFAttributedStringRef<span style="color:#002200">)</span>attString<span style="color:#002200">)</span>;

<span style="color:#a61390">int</span> textPos <span style="color:#002200">=</span> <span style="color:#2400d9">0</span>; <span style="color:#11740a; font-style:italic">//3</span>

<span style="color:#a61390">int</span> columnIndex <span style="color:#002200">=</span> <span style="color:#2400d9">0</span>;

<span style="color:#a61390">while</span> <span style="color:#002200">(</span>textPos < <span style="color:#002200">[</span>attString length<span style="color:#002200">]</span><span style="color:#002200">)</span> <span style="color:#002200">{</span> <span style="color:#11740a; font-style:italic">//4</span>

CGPoint colOffset <span style="color:#002200">=</span> CGPointMake<span style="color:#002200">(</span> <span style="color:#002200">(</span>columnIndex<span style="color:#002200">+</span><span style="color:#2400d9">1</span><span style="color:#002200">)</span><span style="color:#002200">*</span>frameXOffset <span style="color:#002200">+</span> columnIndex<span style="color:#002200">*</span><span style="color:#002200">(</span>textFrame.size.width<span style="color:#002200">/</span><span style="color:#2400d9">2</span><span style="color:#002200">)</span>, <span style="color:#2400d9">20</span> <span style="color:#002200">)</span>;

CGRect colRect <span style="color:#002200">=</span> CGRectMake<span style="color:#002200">(</span><span style="color:#2400d9">0</span>, <span style="color:#2400d9">0</span> , textFrame.size.width<span style="color:#002200">/</span><span style="color:#2400d9">2</span><span style="color:#002200">-</span><span style="color:#2400d9">10</span>, textFrame.size.height<span style="color:#002200">-</span><span style="color:#2400d9">40</span><span style="color:#002200">)</span>;

CGMutablePathRef path <span style="color:#002200">=</span> CGPathCreateMutable<span style="color:#002200">(</span><span style="color:#002200">)</span>;

CGPathAddRect<span style="color:#002200">(</span>path, <span style="color:#a61390">NULL</span>, colRect<span style="color:#002200">)</span>;

<span style="color:#11740a; font-style:italic">//use the column path</span>

CTFrameRef frame <span style="color:#002200">=</span> CTFramesetterCreateFrame<span style="color:#002200">(</span>framesetter, CFRangeMake<span style="color:#002200">(</span>textPos, <span style="color:#2400d9">0</span><span style="color:#002200">)</span>, path, <span style="color:#a61390">NULL</span><span style="color:#002200">)</span>;

CFRange frameRange <span style="color:#002200">=</span> CTFrameGetVisibleStringRange<span style="color:#002200">(</span>frame<span style="color:#002200">)</span>; <span style="color:#11740a; font-style:italic">//5</span>

<span style="color:#11740a; font-style:italic">//create an empty column view</span>

CTColumnView<span style="color:#002200">*</span> content <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span><span style="color:#002200">[</span>CTColumnView alloc<span style="color:#002200">]</span> initWithFrame<span style="color:#002200">:</span> CGRectMake<span style="color:#002200">(</span><span style="color:#2400d9">0</span>, <span style="color:#2400d9">0</span>, self.contentSize.width, self.contentSize.height<span style="color:#002200">)</span><span style="color:#002200">]</span> autorelease<span style="color:#002200">]</span>;

content.backgroundColor <span style="color:#002200">=</span> <span style="color:#002200">[</span>UIColor clearColor<span style="color:#002200">]</span>;

content.frame <span style="color:#002200">=</span> CGRectMake<span style="color:#002200">(</span>colOffset.x, colOffset.y, colRect.size.width, colRect.size.height<span style="color:#002200">)</span> ;

<span style="color:#11740a; font-style:italic">//set the column view contents and add it as subview</span>

<span style="color:#002200">[</span>content setCTFrame<span style="color:#002200">:</span><span style="color:#002200">(</span><span style="color:#a61390">id</span><span style="color:#002200">)</span>frame<span style="color:#002200">]</span>; <span style="color:#11740a; font-style:italic">//6 </span>

<span style="color:#002200">[</span>self.frames addObject<span style="color:#002200">:</span> <span style="color:#002200">(</span><span style="color:#a61390">id</span><span style="color:#002200">)</span>frame<span style="color:#002200">]</span>;

<span style="color:#002200">[</span>self addSubview<span style="color:#002200">:</span> content<span style="color:#002200">]</span>;

<span style="color:#11740a; font-style:italic">//prepare for next frame</span>

textPos <span style="color:#002200">+=</span> frameRange.length;

<span style="color:#11740a; font-style:italic">//CFRelease(frame);</span>

CFRelease<span style="color:#002200">(</span>path<span style="color:#002200">)</span>;

columnIndex<span style="color:#002200">++</span>;

<span style="color:#002200">}</span>

<span style="color:#11740a; font-style:italic">//set the total width of the scroll view</span>

<span style="color:#a61390">int</span> totalPages <span style="color:#002200">=</span> <span style="color:#002200">(</span>columnIndex<span style="color:#002200">+</span><span style="color:#2400d9">1</span><span style="color:#002200">)</span> <span style="color:#002200">/</span> <span style="color:#2400d9">2</span>; <span style="color:#11740a; font-style:italic">//7</span>

self.contentSize <span style="color:#002200">=</span> CGSizeMake<span style="color:#002200">(</span>totalPages<span style="color:#002200">*</span>self.bounds.size.width, textFrame.size.height<span style="color:#002200">)</span>;

<span style="color:#002200">}</span>

Let's look at the code.

here we do some setup - define the x & y offsets, enable paging and create an empty frames array
buildFrames continues by creating a path and a frame for the view's bounds (offset slightly so we have a margin).
This section declares textPos, which will hold the current position in the text. It also declares columnIndex, which will count how many columns are already created.
The while loop here runs until we've reached the end of the text. Inside the loop we create a column bounds: colRect is a CGRect which depending on columnIndex holds the origin and size of the current column. Note that we are building columns continuously
to the right (not across and then down).
This makes use of CTFrameGetVisibleStringRange function to figure out what portion of the string can fit in the frame (a text column in this case). textPos is incremented by the length of this range, and so the building of the next column can begin on the
next loop (if there's more text remaining).
Here, instead of drawing the frame like before, we pass it to the newly created CTColumnView, we store it in the self.frames array for later usage and we add it as subview to the scrollview
Finally, totalPages holds the total number of pages generated, and the contentSize property of the CTView is set so when there's more than one page of content we get scrolling for free!

Now let's also call buildFrames when all the CT setup is done. Inside CoreTextMagazineViewController.m add at the end of viewDidLoad :

[objc] view
plaincopy

<span style="color:#002200">[</span><span style="color:#002200">(</span>CTView <span style="color:#002200">*</span><span style="color:#002200">)</span><span style="color:#002200">[</span>self view<span style="color:#002200">]</span> buildFrames<span style="color:#002200">]</span>;

One more thing to do before giving the new code a try: in the file CTView.m find the method drawRect: and remove it. We do now all the rendering in the CTColumnView class, so we'll leave the CTView drawRect: method to be the standard UIScrollView implementation.

Alright... hit Run and you'll see text floating in columns! Drag right and left to go between pages ... awesome!



We have columns, formatted text, but we miss images. Turns out drawing images with Core Text is not that easy - it's a text framework after all.

But thanks to the fact we already have a little markup parser we're going to get images inside the text pretty quick!


Drawing Images in Core Text

Basically Core Text does not have possibility to draw images. However, since it's a layout engine, what it can do is leave an empty space where you want to draw a picture. And since your code is already inside a drawRect: method, drawing an image yourself is
easy.

Let's look at how leaving an empty space in the text works. Remember all the text chunks are CTRun instances? You simply set a delegate for a given CTRun and the delegate object is responsible to let know Core Text what is the CTRun ascent space, descent space
and width. Like so:



When Core Text "reaches" a CTRun which has a CTRunDelegate it asks the delegate - how much width should I leave for this chunk of data, how high should it be? This way you build a hole in the text - then you draw your image in that very spot.

Let's start by adding support for an "img" tag in our little markup parser! Open up MarkupParser.m and find "} //end of font parsing"; add the following code immediately after this line to add support for "img" tag:

[objc] view
plaincopy

<span style="color:#a61390">if</span> <span style="color:#002200">(</span><span style="color:#002200">[</span>tag hasPrefix<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"img"</span><span style="color:#002200">]</span><span style="color:#002200">)</span> <span style="color:#002200">{</span>

__block <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color:#400080">NSNumber</span></a><span style="color:#002200">*</span> width <span style="color:#002200">=</span> <span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color:#400080">NSNumber</span></a> numberWithInt<span style="color:#002200">:</span><span style="color:#2400d9">0</span><span style="color:#002200">]</span>;

__block <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color:#400080">NSNumber</span></a><span style="color:#002200">*</span> height <span style="color:#002200">=</span> <span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color:#400080">NSNumber</span></a> numberWithInt<span style="color:#002200">:</span><span style="color:#2400d9">0</span><span style="color:#002200">]</span>;

__block <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color:#400080">NSString</span></a><span style="color:#002200">*</span> fileName <span style="color:#002200">=</span> <span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">""</span>;

<span style="color:#11740a; font-style:italic">//width</span>

NSRegularExpression<span style="color:#002200">*</span> widthRegex <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span><span style="color:#002200">[</span>NSRegularExpression alloc<span style="color:#002200">]</span> initWithPattern<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"(?<=width=<span style="color:#2400d9">\"</span>)[^<span style="color:#2400d9">\"</span>]+"</span> options<span style="color:#002200">:</span><span style="color:#2400d9">0</span> error<span style="color:#002200">:</span><span style="color:#a61390">NULL</span><span style="color:#002200">]</span> autorelease<span style="color:#002200">]</span>;

<span style="color:#002200">[</span>widthRegex enumerateMatchesInString<span style="color:#002200">:</span>tag options<span style="color:#002200">:</span><span style="color:#2400d9">0</span> range<span style="color:#002200">:</span>NSMakeRange<span style="color:#002200">(</span><span style="color:#2400d9">0</span>, <span style="color:#002200">[</span>tag length<span style="color:#002200">]</span><span style="color:#002200">)</span> usingBlock<span style="color:#002200">:^</span><span style="color:#002200">(</span>NSTextCheckingResult <span style="color:#002200">*</span>match, NSMatchingFlags flags, <span style="color:#a61390">BOOL</span> <span style="color:#002200">*</span>stop<span style="color:#002200">)</span><span style="color:#002200">{</span>

width <span style="color:#002200">=</span> <span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color:#400080">NSNumber</span></a> numberWithInt<span style="color:#002200">:</span> <span style="color:#002200">[</span><span style="color:#002200">[</span>tag substringWithRange<span style="color:#002200">:</span> match.range<span style="color:#002200">]</span> intValue<span style="color:#002200">]</span> <span style="color:#002200">]</span>;

<span style="color:#002200">}</span><span style="color:#002200">]</span>;

<span style="color:#11740a; font-style:italic">//height</span>

NSRegularExpression<span style="color:#002200">*</span> faceRegex <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span><span style="color:#002200">[</span>NSRegularExpression alloc<span style="color:#002200">]</span> initWithPattern<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"(?<=height=<span style="color:#2400d9">\"</span>)[^<span style="color:#2400d9">\"</span>]+"</span> options<span style="color:#002200">:</span><span style="color:#2400d9">0</span> error<span style="color:#002200">:</span><span style="color:#a61390">NULL</span><span style="color:#002200">]</span> autorelease<span style="color:#002200">]</span>;

<span style="color:#002200">[</span>faceRegex enumerateMatchesInString<span style="color:#002200">:</span>tag options<span style="color:#002200">:</span><span style="color:#2400d9">0</span> range<span style="color:#002200">:</span>NSMakeRange<span style="color:#002200">(</span><span style="color:#2400d9">0</span>, <span style="color:#002200">[</span>tag length<span style="color:#002200">]</span><span style="color:#002200">)</span> usingBlock<span style="color:#002200">:^</span><span style="color:#002200">(</span>NSTextCheckingResult <span style="color:#002200">*</span>match, NSMatchingFlags flags, <span style="color:#a61390">BOOL</span> <span style="color:#002200">*</span>stop<span style="color:#002200">)</span><span style="color:#002200">{</span>

height <span style="color:#002200">=</span> <span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color:#400080">NSNumber</span></a> numberWithInt<span style="color:#002200">:</span> <span style="color:#002200">[</span><span style="color:#002200">[</span>tag substringWithRange<span style="color:#002200">:</span>match.range<span style="color:#002200">]</span> intValue<span style="color:#002200">]</span><span style="color:#002200">]</span>;

<span style="color:#002200">}</span><span style="color:#002200">]</span>;

<span style="color:#11740a; font-style:italic">//image</span>

NSRegularExpression<span style="color:#002200">*</span> srcRegex <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span><span style="color:#002200">[</span>NSRegularExpression alloc<span style="color:#002200">]</span> initWithPattern<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"(?<=src=<span style="color:#2400d9">\"</span>)[^<span style="color:#2400d9">\"</span>]+"</span> options<span style="color:#002200">:</span><span style="color:#2400d9">0</span> error<span style="color:#002200">:</span><span style="color:#a61390">NULL</span><span style="color:#002200">]</span> autorelease<span style="color:#002200">]</span>;

<span style="color:#002200">[</span>srcRegex enumerateMatchesInString<span style="color:#002200">:</span>tag options<span style="color:#002200">:</span><span style="color:#2400d9">0</span> range<span style="color:#002200">:</span>NSMakeRange<span style="color:#002200">(</span><span style="color:#2400d9">0</span>, <span style="color:#002200">[</span>tag length<span style="color:#002200">]</span><span style="color:#002200">)</span> usingBlock<span style="color:#002200">:^</span><span style="color:#002200">(</span>NSTextCheckingResult <span style="color:#002200">*</span>match, NSMatchingFlags flags, <span style="color:#a61390">BOOL</span> <span style="color:#002200">*</span>stop<span style="color:#002200">)</span><span style="color:#002200">{</span>

fileName <span style="color:#002200">=</span> <span style="color:#002200">[</span>tag substringWithRange<span style="color:#002200">:</span> match.range<span style="color:#002200">]</span>;

<span style="color:#002200">}</span><span style="color:#002200">]</span>;

<span style="color:#11740a; font-style:italic">//add the image for drawing</span>

<span style="color:#002200">[</span>self.images addObject<span style="color:#002200">:</span>

<span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color:#400080">NSDictionary</span></a> dictionaryWithObjectsAndKeys<span style="color:#002200">:</span>

width, <span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"width"</span>,

height, <span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"height"</span>,

fileName, <span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"fileName"</span>,

<span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color:#400080">NSNumber</span></a> numberWithInt<span style="color:#002200">:</span> <span style="color:#002200">[</span>aString length<span style="color:#002200">]</span><span style="color:#002200">]</span>, <span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"location"</span>,

<span style="color:#a61390">nil</span><span style="color:#002200">]</span>

<span style="color:#002200">]</span>;

<span style="color:#11740a; font-style:italic">//render empty space for drawing the image in the text //1</span>

CTRunDelegateCallbacks callbacks;

callbacks.version <span style="color:#002200">=</span> kCTRunDelegateVersion1;

callbacks.getAscent <span style="color:#002200">=</span> ascentCallback;

callbacks.getDescent <span style="color:#002200">=</span> descentCallback;

callbacks.getWidth <span style="color:#002200">=</span> widthCallback;

callbacks.dealloc <span style="color:#002200">=</span> deallocCallback;

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color:#400080">NSDictionary</span></a><span style="color:#002200">*</span> imgAttr <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color:#400080">NSDictionary</span></a> dictionaryWithObjectsAndKeys<span style="color:#002200">:</span> <span style="color:#11740a; font-style:italic">//2</span>

width, <span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"width"</span>,

height, <span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"height"</span>,

<span style="color:#a61390">nil</span><span style="color:#002200">]</span> retain<span style="color:#002200">]</span>;

CTRunDelegateRef delegate <span style="color:#002200">=</span> CTRunDelegateCreate<span style="color:#002200">(</span><span style="color:#002200">&</span>callbacks, imgAttr<span style="color:#002200">)</span>; <span style="color:#11740a; font-style:italic">//3</span>

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color:#400080">NSDictionary</span></a> <span style="color:#002200">*</span>attrDictionaryDelegate <span style="color:#002200">=</span> <span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color:#400080">NSDictionary</span></a> dictionaryWithObjectsAndKeys<span style="color:#002200">:</span>

<span style="color:#11740a; font-style:italic">//set the delegate</span>

<span style="color:#002200">(</span><span style="color:#a61390">id</span><span style="color:#002200">)</span>delegate, <span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color:#400080">NSString</span></a><span style="color:#002200">*</span><span style="color:#002200">)</span>kCTRunDelegateAttributeName,

<span style="color:#a61390">nil</span><span style="color:#002200">]</span>;

<span style="color:#11740a; font-style:italic">//add a space to the text so that it can call the delegate</span>

<span style="color:#002200">[</span>aString appendAttributedString<span style="color:#002200">:</span><span style="color:#002200">[</span><span style="color:#002200">[</span><span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color:#400080">NSAttributedString</span></a> alloc<span style="color:#002200">]</span> initWithString<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">" "</span> attributes<span style="color:#002200">:</span>attrDictionaryDelegate<span style="color:#002200">]</span> autorelease<span style="color:#002200">]</span><span style="color:#002200">]</span>;

<span style="color:#002200">}</span>

Let's get a look at all that new code - actually parsing the "img" tag does pretty much the same as what you did already for the font tag. By using 3 regexes you effectively read the width, height and src attributes of the img tag. When done - you add a new
NSDictionary to self.images holding the information you just parsed out plus the location of the image in the text.

Now look at section 1 - CTRunDelegateCallbacks is a C struct which holds references to functions. This struct provides the information you want to pass to the CTRunDelegate. As you can guess already getWidth is called to provide a width for the CTRun, getAscent
provides the height of the CTRun, etc. In the code above you provide function names for those handlers; in a minute we're gonna add the function bodies too.

Section 2 is very important - imgAttr dictionary holds the dimentions of the image; this object is also retained as it is going to be passed to the function handlers - so, when getAscent handler triggers it'll get as a parameter imgAttr and will just read out
the height of the image and provide the value to CT. Neat! (we'll get to this in a moment)

CTRunDelegateCreate in section 3 creates a delegate instance reference and binds the callbacks and the data parameter together.

In the next step you need to create the attributes dictionary (the same way as for the font formatting above), but instead of formatting attributes you pass the delegate instance. In the end you add a single space which will trigger the delegate and create
the hole in the text where the image will be rendered later on.

Next step, which you already anticipated, is to provide the callback functions for the delegate:

[objc] view
plaincopy

<span style="color:#11740a; font-style:italic">//inside MarkupParser.m, just above @implementation</span>

<span style="color:#11740a; font-style:italic">/* Callbacks */</span>

<span style="color:#a61390">static</span> <span style="color:#a61390">void</span> deallocCallback<span style="color:#002200">(</span> <span style="color:#a61390">void</span><span style="color:#002200">*</span> ref <span style="color:#002200">)</span><span style="color:#002200">{</span>

<span style="color:#002200">[</span><span style="color:#002200">(</span><span style="color:#a61390">id</span><span style="color:#002200">)</span>ref release<span style="color:#002200">]</span>;

<span style="color:#002200">}</span>

<span style="color:#a61390">static</span> CGFloat ascentCallback<span style="color:#002200">(</span> <span style="color:#a61390">void</span> <span style="color:#002200">*</span>ref <span style="color:#002200">)</span><span style="color:#002200">{</span>

<span style="color:#a61390">return</span> <span style="color:#002200">[</span><span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color:#400080">NSString</span></a><span style="color:#002200">*</span><span style="color:#002200">)</span><span style="color:#002200">[</span><span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color:#400080">NSDictionary</span></a><span style="color:#002200">*</span><span style="color:#002200">)</span>ref objectForKey<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"height"</span><span style="color:#002200">]</span> floatValue<span style="color:#002200">]</span>;

<span style="color:#002200">}</span>

<span style="color:#a61390">static</span> CGFloat descentCallback<span style="color:#002200">(</span> <span style="color:#a61390">void</span> <span style="color:#002200">*</span>ref <span style="color:#002200">)</span><span style="color:#002200">{</span>

<span style="color:#a61390">return</span> <span style="color:#002200">[</span><span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color:#400080">NSString</span></a><span style="color:#002200">*</span><span style="color:#002200">)</span><span style="color:#002200">[</span><span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color:#400080">NSDictionary</span></a><span style="color:#002200">*</span><span style="color:#002200">)</span>ref objectForKey<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"descent"</span><span style="color:#002200">]</span> floatValue<span style="color:#002200">]</span>;

<span style="color:#002200">}</span>

<span style="color:#a61390">static</span> CGFloat widthCallback<span style="color:#002200">(</span> <span style="color:#a61390">void</span><span style="color:#002200">*</span> ref <span style="color:#002200">)</span><span style="color:#002200">{</span>

<span style="color:#a61390">return</span> <span style="color:#002200">[</span><span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color:#400080">NSString</span></a><span style="color:#002200">*</span><span style="color:#002200">)</span><span style="color:#002200">[</span><span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color:#400080">NSDictionary</span></a><span style="color:#002200">*</span><span style="color:#002200">)</span>ref objectForKey<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"width"</span><span style="color:#002200">]</span> floatValue<span style="color:#002200">]</span>;

<span style="color:#002200">}</span>

ascentCallback, descentCallback and widthCallback only read the respective properties from the passed NSDictionary and provide them to CT. What deallocCallback does is that it releases the dictionary holding the image information - it's called when the CTRunDelegate
gets deallocated, so you have chance to do your memory management there.

Now that your parser is handling "img" tags, let's adjust also the CTView to render them. We need a method to send the images array to the view, let's combine setting the attributed string and the images into one method. Add the code:

[objc] view
plaincopy

<span style="color:#11740a; font-style:italic">//CTView.h - inside @interface declaration as an ivar</span>

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color:#400080">NSArray</span></a><span style="color:#002200">*</span> images;

<span style="color:#11740a; font-style:italic">//CTView.h - declare property for images</span>

<span style="color:#a61390">@property</span> <span style="color:#002200">(</span>retain, nonatomic<span style="color:#002200">)</span> <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color:#400080">NSArray</span></a><span style="color:#002200">*</span> images;

<span style="color:#11740a; font-style:italic">//CTView.h - add a method declaration</span>

<span style="color:#002200">-</span><span style="color:#002200">(</span><span style="color:#a61390">void</span><span style="color:#002200">)</span>setAttString<span style="color:#002200">:</span><span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color:#400080">NSAttributedString</span></a> <span style="color:#002200">*</span><span style="color:#002200">)</span>attString withImages<span style="color:#002200">:</span><span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color:#400080">NSArray</span></a><span style="color:#002200">*</span><span style="color:#002200">)</span>imgs;

<span style="color:#11740a; font-style:italic">//CTView.m - just below @implementation</span>

<span style="color:#a61390">@synthesize</span> images;

<span style="color:#11740a; font-style:italic">//CTView.m - inside the dealloc method</span>

self.images <span style="color:#002200">=</span> <span style="color:#a61390">nil</span>;

<span style="color:#11740a; font-style:italic">//CTView.m - anywhere inside the implementation</span>

<span style="color:#002200">-</span><span style="color:#002200">(</span><span style="color:#a61390">void</span><span style="color:#002200">)</span>setAttString<span style="color:#002200">:</span><span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color:#400080">NSAttributedString</span></a> <span style="color:#002200">*</span><span style="color:#002200">)</span><span style="color:#a61390">string</span> withImages<span style="color:#002200">:</span><span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color:#400080">NSArray</span></a><span style="color:#002200">*</span><span style="color:#002200">)</span>imgs

<span style="color:#002200">{</span>

self.attString <span style="color:#002200">=</span> <span style="color:#a61390">string</span>;

self.images <span style="color:#002200">=</span> imgs;

<span style="color:#002200">}</span>

Now that CTView is prepared to accept an array with images, let's pass those from the parser to the view and render them!

Go to CoreTextMagazineViewController.m and find the line "[(CTView*)self.view setAttString: attString];" - replace it with the following:

[objc] view
plaincopy

<span style="color:#002200">[</span><span style="color:#002200">(</span>CTView <span style="color:#002200">*</span><span style="color:#002200">)</span><span style="color:#002200">[</span>self view<span style="color:#002200">]</span> setAttString<span style="color:#002200">:</span>attString withImages<span style="color:#002200">:</span> p.images<span style="color:#002200">]</span>;

If you lookup attrStringFromMarkup: in the MarkupParser class you'll see it saves all the image tags data into self.images. This is what you are passing now directly to the CTView.

To render images we'll have to know the exact frame of the run where the image should appear. To find the origin of that spot we need to take in account several values:

contentOffset when the content is scrolled
the offset of CTView's frame (frameXOffset,frameYOffset)
the CTLine origin coordinates (CTLine might have offset in the beginning of paragraphs for example)
finally the distance between CTRun's origin and the origin of the CTLine





Let's render those images! First of all we need to update the CTColumnView class:

[objc] view
plaincopy

<span style="color:#11740a; font-style:italic">//inside CTColumnView.h</span>

<span style="color:#11740a; font-style:italic">//as an ivar</span>

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/"><span style="color:#400080">NSMutableArray</span></a><span style="color:#002200">*</span> images;

<span style="color:#11740a; font-style:italic">//as a property</span>

<span style="color:#a61390">@property</span> <span style="color:#002200">(</span>retain, nonatomic<span style="color:#002200">)</span> <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/"><span style="color:#400080">NSMutableArray</span></a><span style="color:#002200">*</span> images;

<span style="color:#11740a; font-style:italic">//inside CTColumnView.m</span>

<span style="color:#11740a; font-style:italic">//after @implementation...</span>

<span style="color:#a61390">@synthesize</span> images;

<span style="color:#002200">-</span><span style="color:#002200">(</span><span style="color:#a61390">id</span><span style="color:#002200">)</span>initWithFrame<span style="color:#002200">:</span><span style="color:#002200">(</span>CGRect<span style="color:#002200">)</span>frame

<span style="color:#002200">{</span>

<span style="color:#a61390">if</span> <span style="color:#002200">(</span><span style="color:#002200">[</span>super initWithFrame<span style="color:#002200">:</span>frame<span style="color:#002200">]</span><span style="color:#002200">!=</span><span style="color:#a61390">nil</span><span style="color:#002200">)</span> <span style="color:#002200">{</span>

self.images <span style="color:#002200">=</span> <span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/"><span style="color:#400080">NSMutableArray</span></a> array<span style="color:#002200">]</span>;

<span style="color:#002200">}</span>

<span style="color:#a61390">return</span> self;

<span style="color:#002200">}</span>

<span style="color:#002200">-</span><span style="color:#002200">(</span><span style="color:#a61390">void</span><span style="color:#002200">)</span>dealloc

<span style="color:#002200">{</span>

self.images<span style="color:#002200">=</span> <span style="color:#a61390">nil</span>;

<span style="color:#002200">[</span>super dealloc<span style="color:#002200">]</span>;

<span style="color:#002200">}</span>

<span style="color:#11740a; font-style:italic">//at the end of drawRect:</span>

<span style="color:#a61390">for</span> <span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color:#400080">NSArray</span></a><span style="color:#002200">*</span> imageData <span style="color:#a61390">in</span> self.images<span style="color:#002200">)</span> <span style="color:#002200">{</span>

UIImage<span style="color:#002200">*</span> img <span style="color:#002200">=</span> <span style="color:#002200">[</span>imageData objectAtIndex<span style="color:#002200">:</span><span style="color:#2400d9">0</span><span style="color:#002200">]</span>;

CGRect imgBounds <span style="color:#002200">=</span> CGRectFromString<span style="color:#002200">(</span><span style="color:#002200">[</span>imageData objectAtIndex<span style="color:#002200">:</span><span style="color:#2400d9">1</span><span style="color:#002200">]</span><span style="color:#002200">)</span>;

CGContextDrawImage<span style="color:#002200">(</span>context, imgBounds, img.CGImage<span style="color:#002200">)</span>;

<span style="color:#002200">}</span>

So with this code update we add an ivar and a property called images where we will keep a list of the images appearing in each text column. To avoid declaring yet another new class to hold the image data insideimageswe're going to store NSArray
objects which will hold:

a UIImage instance
the image bounds - i.e. origin inside the text and size of the image

And now the code that calculates the images' position and attaches them to the respective text columns:

[objc] view
plaincopy

<span style="color:#11740a; font-style:italic">//inside CTView.h</span>

<span style="color:#002200">-</span><span style="color:#002200">(</span><span style="color:#a61390">void</span><span style="color:#002200">)</span>attachImagesWithFrame<span style="color:#002200">:</span><span style="color:#002200">(</span>CTFrameRef<span style="color:#002200">)</span>f inColumnView<span style="color:#002200">:</span><span style="color:#002200">(</span>CTColumnView<span style="color:#002200">*</span><span style="color:#002200">)</span>col;

<span style="color:#11740a; font-style:italic">//inside CTView.m</span>

<span style="color:#002200">-</span><span style="color:#002200">(</span><span style="color:#a61390">void</span><span style="color:#002200">)</span>attachImagesWithFrame<span style="color:#002200">:</span><span style="color:#002200">(</span>CTFrameRef<span style="color:#002200">)</span>f inColumnView<span style="color:#002200">:</span><span style="color:#002200">(</span>CTColumnView<span style="color:#002200">*</span><span style="color:#002200">)</span>col

<span style="color:#002200">{</span>

<span style="color:#11740a; font-style:italic">//drawing images</span>

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color:#400080">NSArray</span></a> <span style="color:#002200">*</span>lines <span style="color:#002200">=</span> <span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color:#400080">NSArray</span></a> <span style="color:#002200">*</span><span style="color:#002200">)</span>CTFrameGetLines<span style="color:#002200">(</span>f<span style="color:#002200">)</span>; <span style="color:#11740a; font-style:italic">//1</span>

CGPoint origins<span style="color:#002200">[</span><span style="color:#002200">[</span>lines count<span style="color:#002200">]</span><span style="color:#002200">]</span>;

CTFrameGetLineOrigins<span style="color:#002200">(</span>f, CFRangeMake<span style="color:#002200">(</span><span style="color:#2400d9">0</span>, <span style="color:#2400d9">0</span><span style="color:#002200">)</span>, origins<span style="color:#002200">)</span>; <span style="color:#11740a; font-style:italic">//2</span>

<span style="color:#a61390">int</span> imgIndex <span style="color:#002200">=</span> <span style="color:#2400d9">0</span>; <span style="color:#11740a; font-style:italic">//3</span>

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color:#400080">NSDictionary</span></a><span style="color:#002200">*</span> nextImage <span style="color:#002200">=</span> <span style="color:#002200">[</span>self.images objectAtIndex<span style="color:#002200">:</span>imgIndex<span style="color:#002200">]</span>;

<span style="color:#a61390">int</span> imgLocation <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span>nextImage objectForKey<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"location"</span><span style="color:#002200">]</span> intValue<span style="color:#002200">]</span>;

<span style="color:#11740a; font-style:italic">//find images for the current column</span>

CFRange frameRange <span style="color:#002200">=</span> CTFrameGetVisibleStringRange<span style="color:#002200">(</span>f<span style="color:#002200">)</span>; <span style="color:#11740a; font-style:italic">//4</span>

<span style="color:#a61390">while</span> <span style="color:#002200">(</span> imgLocation < frameRange.location <span style="color:#002200">)</span> <span style="color:#002200">{</span>

imgIndex<span style="color:#002200">++</span>;

<span style="color:#a61390">if</span> <span style="color:#002200">(</span>imgIndex><span style="color:#002200">=</span><span style="color:#002200">[</span>self.images count<span style="color:#002200">]</span><span style="color:#002200">)</span> <span style="color:#a61390">return</span>; <span style="color:#11740a; font-style:italic">//quit if no images for this column</span>

nextImage <span style="color:#002200">=</span> <span style="color:#002200">[</span>self.images objectAtIndex<span style="color:#002200">:</span>imgIndex<span style="color:#002200">]</span>;

imgLocation <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span>nextImage objectForKey<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"location"</span><span style="color:#002200">]</span> intValue<span style="color:#002200">]</span>;

<span style="color:#002200">}</span>

NSUInteger lineIndex <span style="color:#002200">=</span> <span style="color:#2400d9">0</span>;

<span style="color:#a61390">for</span> <span style="color:#002200">(</span><span style="color:#a61390">id</span> lineObj <span style="color:#a61390">in</span> lines<span style="color:#002200">)</span> <span style="color:#002200">{</span> <span style="color:#11740a; font-style:italic">//5</span>

CTLineRef line <span style="color:#002200">=</span> <span style="color:#002200">(</span>CTLineRef<span style="color:#002200">)</span>lineObj;

<span style="color:#a61390">for</span> <span style="color:#002200">(</span><span style="color:#a61390">id</span> runObj <span style="color:#a61390">in</span> <span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color:#400080">NSArray</span></a> <span style="color:#002200">*</span><span style="color:#002200">)</span>CTLineGetGlyphRuns<span style="color:#002200">(</span>line<span style="color:#002200">)</span><span style="color:#002200">)</span> <span style="color:#002200">{</span> <span style="color:#11740a; font-style:italic">//6</span>

CTRunRef run <span style="color:#002200">=</span> <span style="color:#002200">(</span>CTRunRef<span style="color:#002200">)</span>runObj;

CFRange runRange <span style="color:#002200">=</span> CTRunGetStringRange<span style="color:#002200">(</span>run<span style="color:#002200">)</span>;

<span style="color:#a61390">if</span> <span style="color:#002200">(</span> runRange.location <<span style="color:#002200">=</span> imgLocation <span style="color:#002200">&&</span> runRange.location<span style="color:#002200">+</span>runRange.length > imgLocation <span style="color:#002200">)</span> <span style="color:#002200">{</span> <span style="color:#11740a; font-style:italic">//7</span>

CGRect runBounds;

CGFloat ascent;<span style="color:#11740a; font-style:italic">//height above the baseline</span>

CGFloat descent;<span style="color:#11740a; font-style:italic">//height below the baseline</span>

runBounds.size.width <span style="color:#002200">=</span> CTRunGetTypographicBounds<span style="color:#002200">(</span>run, CFRangeMake<span style="color:#002200">(</span><span style="color:#2400d9">0</span>, <span style="color:#2400d9">0</span><span style="color:#002200">)</span>, <span style="color:#002200">&</span>ascent, <span style="color:#002200">&</span>descent, <span style="color:#a61390">NULL</span><span style="color:#002200">)</span>; <span style="color:#11740a; font-style:italic">//8</span>

runBounds.size.height <span style="color:#002200">=</span> ascent <span style="color:#002200">+</span> descent;

CGFloat xOffset <span style="color:#002200">=</span> CTLineGetOffsetForStringIndex<span style="color:#002200">(</span>line, CTRunGetStringRange<span style="color:#002200">(</span>run<span style="color:#002200">)</span>.location, <span style="color:#a61390">NULL</span><span style="color:#002200">)</span>; <span style="color:#11740a; font-style:italic">//9</span>

runBounds.origin.x <span style="color:#002200">=</span> origins<span style="color:#002200">[</span>lineIndex<span style="color:#002200">]</span>.x <span style="color:#002200">+</span> self.frame.origin.x <span style="color:#002200">+</span> xOffset <span style="color:#002200">+</span> frameXOffset;

runBounds.origin.y <span style="color:#002200">=</span> origins<span style="color:#002200">[</span>lineIndex<span style="color:#002200">]</span>.y <span style="color:#002200">+</span> self.frame.origin.y <span style="color:#002200">+</span> frameYOffset;

runBounds.origin.y <span style="color:#002200">-=</span> descent;

UIImage <span style="color:#002200">*</span>img <span style="color:#002200">=</span> <span style="color:#002200">[</span>UIImage imageNamed<span style="color:#002200">:</span> <span style="color:#002200">[</span>nextImage objectForKey<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"fileName"</span><span style="color:#002200">]</span> <span style="color:#002200">]</span>;

CGPathRef pathRef <span style="color:#002200">=</span> CTFrameGetPath<span style="color:#002200">(</span>f<span style="color:#002200">)</span>; <span style="color:#11740a; font-style:italic">//10</span>

CGRect colRect <span style="color:#002200">=</span> CGPathGetBoundingBox<span style="color:#002200">(</span>pathRef<span style="color:#002200">)</span>;

CGRect imgBounds <span style="color:#002200">=</span> CGRectOffset<span style="color:#002200">(</span>runBounds, colRect.origin.x <span style="color:#002200">-</span> frameXOffset <span style="color:#002200">-</span> self.contentOffset.x, colRect.origin.y <span style="color:#002200">-</span> frameYOffset <span style="color:#002200">-</span> self.frame.origin.y<span style="color:#002200">)</span>;

<span style="color:#002200">[</span>col.images addObject<span style="color:#002200">:</span> <span style="color:#11740a; font-style:italic">//11</span>

<span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color:#400080">NSArray</span></a> arrayWithObjects<span style="color:#002200">:</span>img, NSStringFromCGRect<span style="color:#002200">(</span>imgBounds<span style="color:#002200">)</span> , <span style="color:#a61390">nil</span><span style="color:#002200">]</span>

<span style="color:#002200">]</span>;

<span style="color:#11740a; font-style:italic">//load the next image //12</span>

imgIndex<span style="color:#002200">++</span>;

<span style="color:#a61390">if</span> <span style="color:#002200">(</span>imgIndex < <span style="color:#002200">[</span>self.images count<span style="color:#002200">]</span><span style="color:#002200">)</span> <span style="color:#002200">{</span>

nextImage <span style="color:#002200">=</span> <span style="color:#002200">[</span>self.images objectAtIndex<span style="color:#002200">:</span> imgIndex<span style="color:#002200">]</span>;

imgLocation <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span>nextImage objectForKey<span style="color:#002200">:</span> <span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"location"</span><span style="color:#002200">]</span> intValue<span style="color:#002200">]</span>;

<span style="color:#002200">}</span>

<span style="color:#002200">}</span>

<span style="color:#002200">}</span>

lineIndex<span style="color:#002200">++</span>;

<span style="color:#002200">}</span>

<span style="color:#002200">}</span>

Note: Ultimate credit goes to David Beck for his example on looping over runs, on which this code builds upon.

I know at first this code looks really hard-core, but bear with me - we're at the end of the tutorial; this is the final push for glory!

Let's go section by section:

CTFrameGetLines gives you back an array of CTLine objects.
You get the origins of all the current frame's lines - in short: you get a list of the top-left coordinates of all the text lines.
You load nextImage with the attribute data of the first image in the text and imgLocation holds the string position of that same image.
CTFrameGetVisibleStringRange gives you the visible text range for the frame you're rendering - i.e. you get which part of the text you render at present, then you loop trough the images array until you find the first image, which is in the frame you render
at present. In other words - you fast-forward to the images relevant to the piece of text you are rendering at the moment.
You loop trough the text's lines and loads each line into the "line" variable.
You loop trough all the runs of that line (you get them by calling CTLineGetGlyphRuns).
You check whether the next image is located inside the range of the present run - if so then you have to go on and proceed to rendering the image at this precise spot.
You figure out the width and height of the run, by using the CTRunGetTypographicBounds method.
You figure out the origin of the run, by using the CTLineGetOffsetForStringIndex and other offsets.
You load the image with the given name in the variable "img" and get the rectangle of the current column and eventually the rectangle where the image needs to be rendered.
You create an NSArray with the UIImage and the calculated frame for it, then you add it to the CTColumnView's image list
In the end the next image in the list is loaded if one exists and the loop goes on over the text's lines.

OK! Great! Almost there - one tiny final step: find in CTView.m the line "[content setCTFrame:(id)frame];" and add below it:

[objc] view
plaincopy

<span style="color:#002200">[</span>self attachImagesWithFrame<span style="color:#002200">:</span>frame inColumnView<span style="color:#002200">:</span> content<span style="color:#002200">]</span>;

Now you have all the code working, but you don't have great content to visualize...

No fear, I've prepared the next issue of Zombie Monthly - a montly popular magazine for zombies - for you; just do the following to import the content:

In the project navigator delete test.txt that you created earlier
Download and unarchive this file Zombie mag materials.
Drag all the files over your Xcode project to import them. Make sure that "Copy items into destination group's folder (if needed)" is selected, and click Finish.

Then switch to CoreTextMagazineViewController.m and switch the line that gets the path to the file to use the new zombies.txt instead:

[objc] view
plaincopy

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color:#400080">NSString</span></a> <span style="color:#002200">*</span>path <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSBundle_Class/"><span style="color:#400080">NSBundle</span></a> mainBundle<span style="color:#002200">]</span> pathForResource<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"zombies"</span> ofType<span style="color:#002200">:</span><span style="color:#bf1d1a">@</span><span style="color:#bf1d1a">"txt"</span><span style="color:#002200">]</span>;

That's it - compile and run, and enjoy the latest issue of Zombie Monthly! :)



Just one final touch. Say we want to make the text in the columns justified so they fill the entire width of the column. Add the following code to make this happen:

[objc] view
plaincopy

<span style="color:#11740a; font-style:italic">//inside CTView.m</span>

<span style="color:#11740a; font-style:italic">//at the end of the setAttString:withImages: method</span>

CTTextAlignment alignment <span style="color:#002200">=</span> kCTJustifiedTextAlignment;

CTParagraphStyleSetting settings<span style="color:#002200">[</span><span style="color:#002200">]</span> <span style="color:#002200">=</span> <span style="color:#002200">{</span>

<span style="color:#002200">{</span>kCTParagraphStyleSpecifierAlignment, <a href="http://www.opengroup.org/onlinepubs/009695399/functions/sizeof.html"><span style="color:#a61390">sizeof</span></a><span style="color:#002200">(</span>alignment<span style="color:#002200">)</span>, <span style="color:#002200">&</span>alignment<span style="color:#002200">}</span>,

<span style="color:#002200">}</span>;

CTParagraphStyleRef paragraphStyle <span style="color:#002200">=</span> CTParagraphStyleCreate<span style="color:#002200">(</span>settings, <a href="http://www.opengroup.org/onlinepubs/009695399/functions/sizeof.html"><span style="color:#a61390">sizeof</span></a><span style="color:#002200">(</span>settings<span style="color:#002200">)</span> <span style="color:#002200">/</span> <a href="http://www.opengroup.org/onlinepubs/009695399/functions/sizeof.html"><span style="color:#a61390">sizeof</span></a><span style="color:#002200">(</span>settings<span style="color:#002200">[</span><span style="color:#2400d9">0</span><span style="color:#002200">]</span><span style="color:#002200">)</span><span style="color:#002200">)</span>;

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color:#400080">NSDictionary</span></a> <span style="color:#002200">*</span>attrDictionary <span style="color:#002200">=</span> <span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color:#400080">NSDictionary</span></a> dictionaryWithObjectsAndKeys<span style="color:#002200">:</span>

<span style="color:#002200">(</span><span style="color:#a61390">id</span><span style="color:#002200">)</span>paragraphStyle, <span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color:#400080">NSString</span></a><span style="color:#002200">*</span><span style="color:#002200">)</span>kCTParagraphStyleAttributeName,

<span style="color:#a61390">nil</span><span style="color:#002200">]</span>;

<a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSMutableAttributedString_Class/"><span style="color:#400080">NSMutableAttributedString</span></a><span style="color:#002200">*</span> stringCopy <span style="color:#002200">=</span> <span style="color:#002200">[</span><span style="color:#002200">[</span><span style="color:#002200">[</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSMutableAttributedString_Class/"><span style="color:#400080">NSMutableAttributedString</span></a> alloc<span style="color:#002200">]</span> initWithAttributedString<span style="color:#002200">:</span>self.attString<span style="color:#002200">]</span> autorelease<span style="color:#002200">]</span>;

<span style="color:#002200">[</span>stringCopy addAttributes<span style="color:#002200">:</span>attrDictionary range<span style="color:#002200">:</span>NSMakeRange<span style="color:#002200">(</span><span style="color:#2400d9">0</span>, <span style="color:#002200">[</span>attString length<span style="color:#002200">]</span><span style="color:#002200">)</span><span style="color:#002200">]</span>;

self.attString <span style="color:#002200">=</span> <span style="color:#002200">(</span><a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color:#400080">NSAttributedString</span></a><span style="color:#002200">*</span><span style="color:#002200">)</span>stringCopy;

This should get you started with paragraph styling; lookup kCTParagraphStyleSpecifierAlignment in Apple's Core Text documentation to get to the list of all paragraph styles you can control.


When to use Core Text and why?

Now that your Magazine App with Core Text is finished you might be asking yourself: "Why would I use Core Text over UIWebView?"

Well both CT and UIWebView are useful in their own context.

Don't forget UIWebView is a full-blown web browser and using it to visualize a single multicolored line of text is a huge overkill.

Imagine you have 10 multicolor labels in your UI- this means you're going to consume the memory for 10 Safaris (ok, almost, but you get the point).

So, keep in mind: UIWebView is a great web browser for when you need one, while Core Text is an efficient text rendering engine.


Where To Go From Here?

Here's the complete Core Text example project we developed in the above tutorial.

If you want to keep playing around with this project to learn more about Core Text, read up in Apple'sCore
Text Reference Collection and see if you can add some of the following features to the app:

Add more tags to the magazine engine parser
Add support for more run styles
Add more paragraph styles
Add the ability to automatically apply styles to word bounds, paragraphs, sentences
Enable ligatures and kerning - it's actually cool to enable ligatures and have a look at "if" and "fi" ;)

Since I know you are already thinking how to expand the parser engine beyond what I included in this short tutorial I have two recommendations for you:

the fast lane to HTML parsing- use and extend Ben Reeves' Obj-C HTML Parser;
or create your own grammar parser to do any wonder you come up with by using theObj-C ParseKit

If you have any questions, comments, or suggestions, please join in the forum discussion below!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: