Core Text是iOS 3.2+和OSX10.5+的文本渲染引擎,可以让你自由的控制文本格式和排版。
Core Text不同于UIKit和CoreGraphics/Quartz(虽然通过后两者你也可以进行文字渲染):
这篇教程会通过创建一个简单的杂志应用来教你使用Core Text。你将学到:
为了方便的使用Core Text,你需要先创建一个自定义UIView,我们将在drawRect函数里面使用Core Text。
我们要添加一个继承自UIView的新文件,取名较CTView,在其头文件里面添加:
<span style="color: rgb(110, 55, 26);">#import </span> |
下一步,你要把CTView设置为应用的mainview(或者你也可以new一个新的CTView,然后addSubview到当前viewController里面),如下图所示:
Now your application will show your custom Core Text view whenstarted, but we’ll do that in a moment – let’s first add the codeto draw some text so we have what to test.
让我们添加一些测试代码,打开CTView.m and 用下面的代码替换原来的drawRect,我们将在view中显示"Hellocore text world!":
让我们对应注释中的数字解释一下上面的代码:
你可能注意到了,CoreText提供的都是c形式的api二不是Objective-C对象。许多iOS上系统级别的库为了效率和通用都是用纯c写的。别担心,你会发现CoreText函数用起来都非常简单。最重要的事情是别忘记CFRelease掉所有你Create出来的引用。
额,好吧,看起来不太对。那是因为Core Text使用的是Y-flipped坐标系统,所以在UIKit中使用CoreText绘制出来的结果都是翻转的。解决方法是,我们需要在
“CGContextRef context =UIGraphicsGetCurrentContext();”之后添加如下代码:
如果你对 CTFramesetter 和 CTFrame的概念还有些模糊。那我们简单解释一下CoreText渲染文本的一些基本概念。
下图是Core Text对象模型:
我们通过NSAttributedString创建一个CTFramesetter,这时候会自动创建一个CTTypesetter实例,它负责管理字体,下面通过CTFramesetter来创建一个或多个frame来渲染文字。然后CoreText会根据frame的大小自动创建CTLine(每行对应一个CTLine)和CTRun(相同格式的一个或多个相邻字符组成一个CTRun)。
举例来说,CoreText将创建一个CTRun来绘制一些红色文字,然后创建一个CTRun来绘制纯文本,然后再创建一个CTRun来绘制加粗文字等等。要注意,你不需要自己创建CTRun,CoreText将根据NSAttributedString的属性来自动创建CTRun。每个CTRun对象对应不同的属性,正因此,你可以自由的控制字体、颜色、字间距等等信息。
在一个杂志应用中,我们的文字可能有不同的属性。我们可以直接使用 NSAttributedString 的函数setAttributes:range来设置属性,但是这使用起来并不方便 (除非你想写一大坨恶心的代码)
所以我们将先写一个简单的文本标签分析器,它将负责解析杂志的文本内容。
创建一个新文件名叫MarkupParser 下面分别是头文件和实现文件的代码:
如你所见,这个解析器包含了一些文本属性,比如字体,文本颜色,划线宽度,划线颜色,将来我们会在文本中添加图片,所以还有一个图片列表。
这个解析器将非常简单,并且只支持opening标签,一个标签将影响其后的所有文字,直到另一个标签。比如
These are <span style="color:red;">red<span style="color:black;"> and <span style="color:blue;">blue <span style="color:black;">words. </span></span></span></span>
将会显示成:
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 quitesufficient. For your projects you can develop it further if you'dlike to.
Let's get parsin'!
对于一个教程来说,这就足够了。对于你自己的项目,你应该做的更多。
在attrStringFromMarkup函数中添加如下代码:
让我们来解释一下:
Why are we creating this regular expression? We're going to useit to search the string for every place it matches, and then 1)render the text chunk found; then 2) change the current stylesaccording to what's found in the tag. This will be repeated untilthe 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 stringfrom the text and tags.
Add this to the method body:
<span style="color: rgb(166, 19, 144);">for</span> <span style="color: rgb(0, 34, 0);">(</span>NSTextCheckingResult<span style="color: rgb(0, 34, 0);">*</span> b <span style="color: rgb(166, 19, 144);">in</span> chunks<span style="color: rgb(0, 34, 0);">)</span> <span style="color: rgb(0, 34, 0);">{</span> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color: rgb(64, 0, 128);">NSArray</span></a><span style="color: rgb(0, 34, 0);">*</span> parts <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>markup substringWithRange<span style="color: rgb(0, 34, 0);">:</span>b.range<span style="color: rgb(0, 34, 0);">]</span> componentsSeparatedByStr<wbr>ing<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"<"</span><span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(17, 116, 10);"><em>//1</em></span> <wbr> CTFontRef fontRef <span style="color: rgb(0, 34, 0);">=</span> CTFontCreateWithName<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(0, 34, 0);">(</span>CFStringRef<span style="color: rgb(0, 34, 0);">)</span>self.font, 24.0f, <span style="color: rgb(166, 19, 144);">NULL</span><span style="color: rgb(0, 34, 0);">)</span>; <wbr> <span style="color: rgb(17, 116, 10);"><em>//apply the current text style //2</em></span> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color: rgb(64, 0, 128);">NSDictionary</span></a><span style="color: rgb(0, 34, 0);">*</span> attrs <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color: rgb(64, 0, 128);">NSDictionary</span></a> dictionaryWithObjectsAnd<wbr>Keys<span style="color: rgb(0, 34, 0);">:</span> <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">id</span><span style="color: rgb(0, 34, 0);">)</span>self.color.CGColor, kCTForegroundColorAttrib<wbr>uteName, <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">id</span><span style="color: rgb(0, 34, 0);">)</span>fontRef, kCTFontAttributeName, <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">id</span><span style="color: rgb(0, 34, 0);">)</span>self.strokeColor.CGColor, <span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: rgb(64, 0, 128);">NSString</span></a> <span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span> kCTStrokeColorAttributeN<wbr>ame, <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">id</span><span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color: rgb(64, 0, 128);">NSNumber</span></a> numberWithFloat<span style="color: rgb(0, 34, 0);">:</span> self.strokeWidth<span style="color: rgb(0, 34, 0);">]</span>, <span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: rgb(64, 0, 128);">NSString</span></a> <span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span>kCTStrokeWidthAttributeN<wbr>ame, <span style="color: rgb(166, 19, 144);">nil</span><span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">[</span>aString appendAttributedString<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color: rgb(64, 0, 128);">NSAttributedString</span></a> alloc<span style="color: rgb(0, 34, 0);">]</span> initWithString<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(0, 34, 0);">[</span>parts objectAtIndex<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">0</span><span style="color: rgb(0, 34, 0);">]</span> attributes<span style="color: rgb(0, 34, 0);">:</span>attrs<span style="color: rgb(0, 34, 0);">]</span> autorelease<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">]</span>; <wbr> CFRelease<span style="color: rgb(0, 34, 0);">(</span>fontRef<span style="color: rgb(0, 34, 0);">)</span>; <wbr> <span style="color: rgb(17, 116, 10);"><em>//handle new formatting tag //3</em></span> <span style="color: rgb(166, 19, 144);">if</span> <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(0, 34, 0);">[</span>parts count<span style="color: rgb(0, 34, 0);">]</span>><span style="color: rgb(36, 0, 217);">1</span><span style="color: rgb(0, 34, 0);">)</span> <span style="color: rgb(0, 34, 0);">{</span> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: rgb(64, 0, 128);">NSString</span></a><span style="color: rgb(0, 34, 0);">*</span> tag <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: rgb(64, 0, 128);">NSString</span></a><span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">[</span>parts objectAtIndex<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">1</span><span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(166, 19, 144);">if</span> <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(0, 34, 0);">[</span>tag hasPrefix<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"font"</span><span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span> <span style="color: rgb(0, 34, 0);">{</span> <span style="color: rgb(17, 116, 10);"><em>//stroke color</em></span> NSRegularExpression<span style="color: rgb(0, 34, 0);">*</span> scolorRegex <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>NSRegularExpression alloc<span style="color: rgb(0, 34, 0);">]</span> initWithPattern<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"(?<=strokeColor=<span style="color: rgb(36, 0, 217);">"</span>)<span style="color: rgb(36, 0, 217);">\\</span>w+"</span> options<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">0</span> error<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(166, 19, 144);">NULL</span><span style="color: rgb(0, 34, 0);">]</span> autorelease<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">[</span>scolorRegex enumerateMatchesInString<wbr><span style="color: rgb(0, 34, 0);">:</span>tag options<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">0</span> range<span style="color: rgb(0, 34, 0);">:</span>NSMakeRange<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(36, 0, 217);">0</span>, <span style="color: rgb(0, 34, 0);">[</span>tag length<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span> usingBlock<span style="color: rgb(0, 34, 0);">:^</span><span style="color: rgb(0, 34, 0);">(</span>NSTextCheckingResult <span style="color: rgb(0, 34, 0);">*</span>match, NSMatchingFlags flags, <span style="color: rgb(166, 19, 144);">BOOL</span> <span style="color: rgb(0, 34, 0);">*</span>stop<span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">{</span> <span style="color: rgb(166, 19, 144);">if</span> <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>tag substringWithRange<span style="color: rgb(0, 34, 0);">:</span>match.range<span style="color: rgb(0, 34, 0);">]</span> isEqualToString<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"none"</span><span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span> <span style="color: rgb(0, 34, 0);">{</span> self.strokeWidth <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(36, 0, 217);">0.0</span>; <span style="color: rgb(0, 34, 0);">}</span> <span style="color: rgb(166, 19, 144);">else</span> <span style="color: rgb(0, 34, 0);">{</span> self.strokeWidth <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">-</span><span style="color: rgb(36, 0, 217);">3.0</span>; <span style="color: rgb(166, 19, 144);">SEL</span> colorSel <span style="color: rgb(0, 34, 0);">=</span> NSSelectorFromString<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: rgb(64, 0, 128);">NSString</span></a> stringWithFormat<span style="color: rgb(0, 34, 0);">:</span> <span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"%@Color"</span>, <span style="color: rgb(0, 34, 0);">[</span>tag substringWithRange<span style="color: rgb(0, 34, 0);">:</span>match.range<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span>; self.strokeColor <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span>UIColor performSelector<span style="color: rgb(0, 34, 0);">:</span>colorSel<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span> <span style="color: rgb(0, 34, 0);">}</span><span style="color: rgb(0, 34, 0);">]</span>; <wbr> <span style="color: rgb(17, 116, 10);"><em>//color</em></span> NSRegularExpression<span style="color: rgb(0, 34, 0);">*</span> colorRegex <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>NSRegularExpression alloc<span style="color: rgb(0, 34, 0);">]</span> initWithPattern<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"(?<=color=<span style="color: rgb(36, 0, 217);">"</span>)<span style="color: rgb(36, 0, 217);">\\</span>w+"</span> options<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">0</span> error<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(166, 19, 144);">NULL</span><span style="color: rgb(0, 34, 0);">]</span> autorelease<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">[</span>colorRegex enumerateMatchesInString<wbr><span style="color: rgb(0, 34, 0);">:</span>tag options<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">0</span> range<span style="color: rgb(0, 34, 0);">:</span>NSMakeRange<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(36, 0, 217);">0</span>, <span style="color: rgb(0, 34, 0);">[</span>tag length<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span> usingBlock<span style="color: rgb(0, 34, 0);">:^</span><span style="color: rgb(0, 34, 0);">(</span>NSTextCheckingResult <span style="color: rgb(0, 34, 0);">*</span>match, NSMatchingFlags flags, <span style="color: rgb(166, 19, 144);">BOOL</span> <span style="color: rgb(0, 34, 0);">*</span>stop<span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">{</span> <span style="color: rgb(166, 19, 144);">SEL</span> colorSel <span style="color: rgb(0, 34, 0);">=</span> NSSelectorFromString<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: rgb(64, 0, 128);">NSString</span></a> stringWithFormat<span style="color: rgb(0, 34, 0);">:</span> <span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"%@Color"</span>, <span style="color: rgb(0, 34, 0);">[</span>tag substringWithRange<span style="color: rgb(0, 34, 0);">:</span>match.range<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span>; self.color <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span>UIColor performSelector<span style="color: rgb(0, 34, 0);">:</span>colorSel<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span><span style="color: rgb(0, 34, 0);">]</span>; <wbr> <span style="color: rgb(17, 116, 10);"><em>//face</em></span> NSRegularExpression<span style="color: rgb(0, 34, 0);">*</span> faceRegex <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>NSRegularExpression alloc<span style="color: rgb(0, 34, 0);">]</span> initWithPattern<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"(?<=face=<span style="color: rgb(36, 0, 217);">"</span>)[^<span style="color: rgb(36, 0, 217);">"</span>]+"</span> options<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">0</span> error<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(166, 19, 144);">NULL</span><span style="color: rgb(0, 34, 0);">]</span> autorelease<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">[</span>faceRegex enumerateMatchesInString<wbr><span style="color: rgb(0, 34, 0);">:</span>tag options<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">0</span> range<span style="color: rgb(0, 34, 0);">:</span>NSMakeRange<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(36, 0, 217);">0</span>, <span style="color: rgb(0, 34, 0);">[</span>tag length<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span> usingBlock<span style="color: rgb(0, 34, 0);">:^</span><span style="color: rgb(0, 34, 0);">(</span>NSTextCheckingResult <span style="color: rgb(0, 34, 0);">*</span>match, NSMatchingFlags flags, <span style="color: rgb(166, 19, 144);">BOOL</span> <span style="color: rgb(0, 34, 0);">*</span>stop<span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">{</span> self.font <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span>tag substringWithRange<span style="color: rgb(0, 34, 0);">:</span>match.range<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span><span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span> <span style="color: rgb(17, 116, 10);"><em>//end of font parsing</em></span> <span style="color: rgb(0, 34, 0);">}</span> <span style="color: rgb(0, 34, 0);">}</span> <wbr> <span style="color: rgb(166, 19, 144);">return</span> <span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color: rgb(64, 0, 128);">NSAttributedString</span></a><span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span>aString; </wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr> |
Phew, this is a lot of code! But don't worry, we'll go over ithere section by section.
Note: If you're unsatiably curious howthe regular expressions in this section work, they are basicallysaying ("Use the look-behind assertion to look for any text that ispreceded by color=". Then match any normal word character (whichdoes not include a quote), which basically keeps matching until theclose quote is found. For more details, check out Apple'sNSRegularExpressionclass reference.
Right! Half the work of rendering formatted text is done - nowattrStringFromMarkup: can take markup in and spit aNSAttributedString 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:
<span style="color: rgb(110, 55, 26);">#import "MarkupParser.h"</span> |
Find the line where attString is defined - replace it with thefollowing code:
MarkupParser<span style="color: rgb(0, 34, 0);">*</span> p <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>MarkupParser alloc<span style="color: rgb(0, 34, 0);">]</span> init<span style="color: rgb(0, 34, 0);">]</span> autorelease<span style="color: rgb(0, 34, 0);">]</span>; <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color: rgb(64, 0, 128);">NSAttributedString</span></a><span style="color: rgb(0, 34, 0);">*</span> attString <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span>p attrStringFromMarkup<span style="color: rgb(0, 34, 0);">:</span> <span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"Hello <span style="color:<span;color: rgb(36, 0, 217);">"</span>red<span style="color: rgb(36, 0, 217);">"</span>>core text <span style="color:<span;color: rgb(36, 0, 217);">"</span>blue<span style="color: rgb(36, 0, 217);">"</span>>world!"</span><span style="color: rgb(0, 34, 0);">]</span>; |
Above you make a new parser, feed it a piece of markup and itgives 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'thave to deal with text ranges and code heavy text formatting, wecan just use now a simple text file to hold the contents of ourmagazine app. Also the simple parser you just wrote can be extendedinfinitely to support everything you'd need in your magazineapp.
So far we have text showing up, which is a good first step. Butfor a magazine we'd like to have columns - and this is where CoreText becomes particularly handy.
Before proceeding with the layout code, let's first load alonger string into the app so we have something long enough to wrapacross 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 thisfile into test.txt and save.
Open CTView.m and find the 2 lines where you create MarkupParserand NSAttributedString and delete them. We're taking the loading ofthe text file out of the drawRect: method, because that sort ofcode doesn't really belong there. It's the job of a UIView todisplay content given to it - not load content. We'll move theattString variable to an instance variable and property in thisclass later.
Next open CoreTextMagazineViewController.m, delete all theexisting content, and add the following instead:
<span style="color: rgb(110, 55, 26);">#import "CoreTextMagazineViewCont<wbr>roller.h"</wbr></span> <span style="color: rgb(110, 55, 26);">#import "CTView.h"</span> <span style="color: rgb(110, 55, 26);">#import "MarkupParser.h"</span> <wbr> <span style="color: rgb(166, 19, 144);">@implementation</span> CoreTextMagazineViewCont<wbr>roller <wbr> <span style="color: rgb(0, 34, 0);">-</span> <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">void</span><span style="color: rgb(0, 34, 0);">)</span>viewDidLoad <span style="color: rgb(0, 34, 0);">{</span> <span style="color: rgb(0, 34, 0);">[</span>super viewDidLoad<span style="color: rgb(0, 34, 0);">]</span>; <wbr> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: rgb(64, 0, 128);">NSString</span></a> <span style="color: rgb(0, 34, 0);">*</span>path <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSBundle_Class/"><span style="color: rgb(64, 0, 128);">NSBundle</span></a> mainBundle<span style="color: rgb(0, 34, 0);">]</span> pathForResource<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"test"</span> ofType<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"txt"</span><span style="color: rgb(0, 34, 0);">]</span>; <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: rgb(64, 0, 128);">NSString</span></a><span style="color: rgb(0, 34, 0);">*</span> text <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: rgb(64, 0, 128);">NSString</span></a> stringWithContentsOfFile<wbr><span style="color: rgb(0, 34, 0);">:</span>path encoding<span style="color: rgb(0, 34, 0);">:</span>NSUTF8StringEncoding error<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(166, 19, 144);">NULL</span><span style="color: rgb(0, 34, 0);">]</span>; MarkupParser<span style="color: rgb(0, 34, 0);">*</span> p <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>MarkupParser alloc<span style="color: rgb(0, 34, 0);">]</span> init<span style="color: rgb(0, 34, 0);">]</span> autorelease<span style="color: rgb(0, 34, 0);">]</span>; <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color: rgb(64, 0, 128);">NSAttributedString</span></a><span style="color: rgb(0, 34, 0);">*</span> attString <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span>p attrStringFromMarkup<span style="color: rgb(0, 34, 0);">:</span> text<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">(</span>CTView<span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span>self.view setAttString<span style="color: rgb(0, 34, 0);">:</span> attString<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span> <wbr> <span style="color: rgb(166, 19, 144);">@end</span> </wbr></wbr></wbr></wbr></wbr></wbr> |
When the view of the application is loaded, the app reads thetext from test.txt, converts it to an attributed string and thensets the attString property on the window's view. We haven't addedthat property to CTView yet though, so let's add that next!
In CTView.h define these 3 instance variables:
<span style="color: rgb(166, 19, 144);">float</span> frameXOffset; <span style="color: rgb(166, 19, 144);">float</span> frameYOffset; <wbr> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color: rgb(64, 0, 128);">NSAttributedString</span></a><span style="color: rgb(0, 34, 0);">*</span> attString; </wbr> |
Then add the corresponding code in CTView.h and CTView.m todefine a property for attString:
<span style="color: rgb(17, 116, 10);"><em>//CTView.h</em></span> <span style="color: rgb(166, 19, 144);">@property</span> <span style="color: rgb(0, 34, 0);">(</span>retain, nonatomic<span style="color: rgb(0, 34, 0);">)</span> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color: rgb(64, 0, 128);">NSAttributedString</span></a><span style="color: rgb(0, 34, 0);">*</span> attString; <wbr> <span style="color: rgb(17, 116, 10);"><em>//CTView.m</em></span> <span style="color: rgb(17, 116, 10);"><em>//just below @implementation ...</em></span> <span style="color: rgb(166, 19, 144);">@synthesize</span> attString; <wbr> <span style="color: rgb(17, 116, 10);"><em>//at the bottom of the file</em></span> <span style="color: rgb(0, 34, 0);">-</span><span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">void</span><span style="color: rgb(0, 34, 0);">)</span>dealloc <span style="color: rgb(0, 34, 0);">{</span> self.attString <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(166, 19, 144);">nil</span>; <span style="color: rgb(0, 34, 0);">[</span>super dealloc<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span> </wbr></wbr> |
Now you can hit Run again to see the view showing the contentsof the text file. Cool!
How to make columns out of this text? Luckily Core Text providesa handy function - CTFrameGetVisibleStringRange. This functiontells 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 CTFrameinstances, since columns are just taller rectangles)
First of all - we are going to have columns, then pages, then awhole magazine, so... let's make our CTView subclass UIScrollViewto get free paging and scrolling!
Open up CTView.h and change the @interface line to:
<span style="color: rgb(166, 19, 144);">@interface</span> CTView <span style="color: rgb(0, 34, 0);">:</span> UIScrollView <span style="color: rgb(0, 34, 0);">{</span> |
OK! We've got free scrolling and paging now available. We'regoing to enable the paging in a minute.
Up to now we were creating our framesetter and frame inside thedrawRect: method. When you have columns and different formattingit's better to do all those calculations only once. So what we aregoing to do is have a new class "CTColumnView" which will onlyrender CT content passed to it, and in our CTView class we're goingto only once create instances of CTColumnView and add them assubviews.
So to summarize: CTView is going to take care of scrolling,paging and building the columns; CTColumnView will actually renderthe content on the screen.
Go to File\New\New File, choose iOS\Cocoa Touch\Objective-Cclass, and click Next. Enter UIView for "Subclass of", click Next,name the new class CTColumnView.m, and click Save. Here's theinitial code for the CTColumnView class:
<span style="color: rgb(17, 116, 10);"><em>//inside CTColumnView.h</em></span> <wbr> <span style="color: rgb(110, 55, 26);">#import </span> <span style="color: rgb(110, 55, 26);">#import </span> <wbr> <span style="color: rgb(166, 19, 144);">@interface</span> CTColumnView <span style="color: rgb(0, 34, 0);">:</span> UIView <span style="color: rgb(0, 34, 0);">{</span> <span style="color: rgb(166, 19, 144);">id</span> ctFrame; <span style="color: rgb(0, 34, 0);">}</span> <wbr> <span style="color: rgb(0, 34, 0);">-</span><span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">void</span><span style="color: rgb(0, 34, 0);">)</span>setCTFrame<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">id</span><span style="color: rgb(0, 34, 0);">)</span>f; <span style="color: rgb(166, 19, 144);">@end</span> <wbr> <span style="color: rgb(17, 116, 10);"><em>//inside CTColumnView.m</em></span> <span style="color: rgb(110, 55, 26);">#import "CTColumnView.h"</span> <wbr> <span style="color: rgb(166, 19, 144);">@implementation</span> CTColumnView <span style="color: rgb(0, 34, 0);">-</span><span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">void</span><span style="color: rgb(0, 34, 0);">)</span>setCTFrame<span style="color: rgb(0, 34, 0);">:</span> <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">id</span><span style="color: rgb(0, 34, 0);">)</span> f <span style="color: rgb(0, 34, 0);">{</span> ctFrame <span style="color: rgb(0, 34, 0);">=</span> f; <span style="color: rgb(0, 34, 0);">}</span> <wbr> <span style="color: rgb(0, 34, 0);">-</span><span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">void</span><span style="color: rgb(0, 34, 0);">)</span>drawRect<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(0, 34, 0);">(</span>CGRect<span style="color: rgb(0, 34, 0);">)</span>rect <span style="color: rgb(0, 34, 0);">{</span> CGContextRef context <span style="color: rgb(0, 34, 0);">=</span> UIGraphicsGetCurrentCont<wbr>ext<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(0, 34, 0);">)</span>; <wbr> <span style="color: rgb(17, 116, 10);"><em>// Flip the coordinate system</em></span> CGContextSetTextMatrix<span style="color: rgb(0, 34, 0);">(</span>context, CGAffineTransformIdentit<wbr>y<span style="color: rgb(0, 34, 0);">)</span>; CGContextTranslateCTM<span style="color: rgb(0, 34, 0);">(</span>context, <span style="color: rgb(36, 0, 217);">0</span>, self.bounds.size.height<span style="color: rgb(0, 34, 0);">)</span>; CGContextScaleCTM<span style="color: rgb(0, 34, 0);">(</span>context, <span style="color: rgb(36, 0, 217);">1.0</span>, <span style="color: rgb(0, 34, 0);">-</span><span style="color: rgb(36, 0, 217);">1.0</span><span style="color: rgb(0, 34, 0);">)</span>; <wbr> CTFrameDraw<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(0, 34, 0);">(</span>CTFrameRef<span style="color: rgb(0, 34, 0);">)</span>ctFrame, context<span style="color: rgb(0, 34, 0);">)</span>; <span style="color: rgb(0, 34, 0);">}</span> <span style="color: rgb(166, 19, 144);">@end</span> </wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr> |
This class does pretty much what we've been doing up to now - itjust renders a CTFrame. We're going to create an instance of it foreach text column in the magazine.
Let's first add a property to hold our CTView's text frames anddeclare the buildFrames method, which will do the columnssetup:
<span style="color: rgb(17, 116, 10);"><em>//CTView.h - at the top</em></span> <span style="color: rgb(110, 55, 26);">#import "CTColumnView.h"</span> <wbr> <span style="color: rgb(17, 116, 10);"><em>//CTView.h - as an ivar</em></span> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/"><span style="color: rgb(64, 0, 128);">NSMutableArray</span></a><span style="color: rgb(0, 34, 0);">*</span> frames; <wbr> <span style="color: rgb(17, 116, 10);"><em>//CTView.h - declare property</em></span> <span style="color: rgb(166, 19, 144);">@property</span> <span style="color: rgb(0, 34, 0);">(</span>retain, nonatomic<span style="color: rgb(0, 34, 0);">)</span> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/"><span style="color: rgb(64, 0, 128);">NSMutableArray</span></a><span style="color: rgb(0, 34, 0);">*</span> frames; <wbr> <span style="color: rgb(17, 116, 10);"><em>//CTView.h - in method declarations</em></span> <span style="color: rgb(0, 34, 0);">-</span> <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">void</span><span style="color: rgb(0, 34, 0);">)</span>buildFrames; <wbr> <span style="color: rgb(17, 116, 10);"><em>//CTView.m - just below @implementation</em></span> <span style="color: rgb(166, 19, 144);">@synthesize</span> frames; <wbr> <span style="color: rgb(17, 116, 10);"><em>//CTView.m - inside dealloc</em></span> self.frames <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(166, 19, 144);">nil</span>; </wbr></wbr></wbr></wbr></wbr> |
Now buildFrames can create the text frames once and store themin the "frames" array. Let's add the code to do so.
<span style="color: rgb(0, 34, 0);">-</span> <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">void</span><span style="color: rgb(0, 34, 0);">)</span>buildFrames <span style="color: rgb(0, 34, 0);">{</span> frameXOffset <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(36, 0, 217);">20</span>; <span style="color: rgb(17, 116, 10);"><em>//1</em></span> frameYOffset <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(36, 0, 217);">20</span>; self.pagingEnabled <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(166, 19, 144);">YES</span>; self.delegate <span style="color: rgb(0, 34, 0);">=</span> self; self.frames <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/"><span style="color: rgb(64, 0, 128);">NSMutableArray</span></a> array<span style="color: rgb(0, 34, 0);">]</span>; <wbr> CGMutablePathRef path <span style="color: rgb(0, 34, 0);">=</span> CGPathCreateMutable<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(0, 34, 0);">)</span>; <span style="color: rgb(17, 116, 10);"><em>//2</em></span> CGRect textFrame <span style="color: rgb(0, 34, 0);">=</span> CGRectInset<span style="color: rgb(0, 34, 0);">(</span>self.bounds, frameXOffset, frameYOffset<span style="color: rgb(0, 34, 0);">)</span>; CGPathAddRect<span style="color: rgb(0, 34, 0);">(</span>path, <span style="color: rgb(166, 19, 144);">NULL</span>, textFrame <span style="color: rgb(0, 34, 0);">)</span>; <wbr> CTFramesetterRef framesetter <span style="color: rgb(0, 34, 0);">=</span> CTFramesetterCreateWithA<wbr>ttributedString<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(0, 34, 0);">(</span>CFAttributedStringRef<span style="color: rgb(0, 34, 0);">)</span>attString<span style="color: rgb(0, 34, 0);">)</span>; <wbr> <span style="color: rgb(166, 19, 144);">int</span> textPos <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(36, 0, 217);">0</span>; <span style="color: rgb(17, 116, 10);"><em>//3</em></span> <span style="color: rgb(166, 19, 144);">int</span> columnIndex <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(36, 0, 217);">0</span>; <wbr> <span style="color: rgb(166, 19, 144);">while</span> <span style="color: rgb(0, 34, 0);">(</span>textPos < <span style="color: rgb(0, 34, 0);">[</span>attString length<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span> <span style="color: rgb(0, 34, 0);">{</span> <span style="color: rgb(17, 116, 10);"><em>//4</em></span> CGPoint colOffset <span style="color: rgb(0, 34, 0);">=</span> CGPointMake<span style="color: rgb(0, 34, 0);">(</span> <span style="color: rgb(0, 34, 0);">(</span>columnIndex<span style="color: rgb(0, 34, 0);">+</span><span style="color: rgb(36, 0, 217);">1</span><span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">*</span>frameXOffset <span style="color: rgb(0, 34, 0);">+</span> columnIndex<span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">(</span>textFrame.size.width<span style="color: rgb(0, 34, 0);">/</span><span style="color: rgb(36, 0, 217);">2</span><span style="color: rgb(0, 34, 0);">)</span>, <span style="color: rgb(36, 0, 217);">20</span> <span style="color: rgb(0, 34, 0);">)</span>; CGRect colRect <span style="color: rgb(0, 34, 0);">=</span> CGRectMake<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(36, 0, 217);">0</span>, <span style="color: rgb(36, 0, 217);">0</span> , textFrame.size.width<span style="color: rgb(0, 34, 0);">/</span><span style="color: rgb(36, 0, 217);">2</span><span style="color: rgb(0, 34, 0);">-</span><span style="color: rgb(36, 0, 217);">10</span>, textFrame.size.height<span style="color: rgb(0, 34, 0);">-</span><span style="color: rgb(36, 0, 217);">40</span><span style="color: rgb(0, 34, 0);">)</span>; <wbr> CGMutablePathRef path <span style="color: rgb(0, 34, 0);">=</span> CGPathCreateMutable<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(0, 34, 0);">)</span>; CGPathAddRect<span style="color: rgb(0, 34, 0);">(</span>path, <span style="color: rgb(166, 19, 144);">NULL</span>, colRect<span style="color: rgb(0, 34, 0);">)</span>; <wbr> <span style="color: rgb(17, 116, 10);"><em>//use the column path</em></span> CTFrameRef frame <span style="color: rgb(0, 34, 0);">=</span> CTFramesetterCreateFrame<wbr><span style="color: rgb(0, 34, 0);">(</span>framesetter, CFRangeMake<span style="color: rgb(0, 34, 0);">(</span>textPos, <span style="color: rgb(36, 0, 217);">0</span><span style="color: rgb(0, 34, 0);">)</span>, path, <span style="color: rgb(166, 19, 144);">NULL</span><span style="color: rgb(0, 34, 0);">)</span>; CFRange frameRange <span style="color: rgb(0, 34, 0);">=</span> CTFrameGetVisibleStringR<wbr>ange<span style="color: rgb(0, 34, 0);">(</span>frame<span style="color: rgb(0, 34, 0);">)</span>; <span style="color: rgb(17, 116, 10);"><em>//5</em></span> <wbr> <span style="color: rgb(17, 116, 10);"><em>//create an empty column view</em></span> CTColumnView<span style="color: rgb(0, 34, 0);">*</span> content <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>CTColumnView alloc<span style="color: rgb(0, 34, 0);">]</span> initWithFrame<span style="color: rgb(0, 34, 0);">:</span> CGRectMake<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(36, 0, 217);">0</span>, <span style="color: rgb(36, 0, 217);">0</span>, self.contentSize.width, self.contentSize.height<span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">]</span> autorelease<span style="color: rgb(0, 34, 0);">]</span>; content.backgroundColor <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span>UIColor clearColor<span style="color: rgb(0, 34, 0);">]</span>; content.frame <span style="color: rgb(0, 34, 0);">=</span> CGRectMake<span style="color: rgb(0, 34, 0);">(</span>colOffset.x, colOffset.y, colRect.size.width, colRect.size.height<span style="color: rgb(0, 34, 0);">)</span> ; <wbr> <span style="color: rgb(17, 116, 10);"><em>//set the column view contents and add it as subview</em></span> <span style="color: rgb(0, 34, 0);">[</span>content setCTFrame<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">id</span><span style="color: rgb(0, 34, 0);">)</span>frame<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(17, 116, 10);"><em>//6 </em></span> <span style="color: rgb(0, 34, 0);">[</span>self.frames addObject<span style="color: rgb(0, 34, 0);">:</span> <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">id</span><span style="color: rgb(0, 34, 0);">)</span>frame<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">[</span>self addSubview<span style="color: rgb(0, 34, 0);">:</span> content<span style="color: rgb(0, 34, 0);">]</span>; <wbr> <span style="color: rgb(17, 116, 10);"><em>//prepare for next frame</em></span> textPos <span style="color: rgb(0, 34, 0);">+=</span> frameRange.length; <wbr> <span style="color: rgb(17, 116, 10);"><em>//CFRelease(frame);</em></span> CFRelease<span style="color: rgb(0, 34, 0);">(</span>path<span style="color: rgb(0, 34, 0);">)</span>; <wbr> columnIndex<span style="color: rgb(0, 34, 0);">++</span>; <span style="color: rgb(0, 34, 0);">}</span> <wbr> <span style="color: rgb(17, 116, 10);"><em>//set the total width of the scroll view</em></span> <span style="color: rgb(166, 19, 144);">int</span> totalPages <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">(</span>columnIndex<span style="color: rgb(0, 34, 0);">+</span><span style="color: rgb(36, 0, 217);">1</span><span style="color: rgb(0, 34, 0);">)</span> <span style="color: rgb(0, 34, 0);">/</span> <span style="color: rgb(36, 0, 217);">2</span>; <span style="color: rgb(17, 116, 10);"><em>//7</em></span> self.contentSize <span style="color: rgb(0, 34, 0);">=</span> CGSizeMake<span style="color: rgb(0, 34, 0);">(</span>totalPages<span style="color: rgb(0, 34, 0);">*</span>self.bounds.size.width, textFrame.size.height<span style="color: rgb(0, 34, 0);">)</span>; <span style="color: rgb(0, 34, 0);">}</span> </wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr> |
Let's look at the code.
Now let's also call buildFrames when all the CT setup is done.Inside CoreTextMagazineViewController.m add at the end ofviewDidLoad :
<span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">(</span>CTView <span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">[</span>self view<span style="color: rgb(0, 34, 0);">]</span> buildFrames<span style="color: rgb(0, 34, 0);">]</span>; |
One more thing to do before giving the new code a try: in thefile CTView.m find the method drawRect: and remove it. We do nowall the rendering in the CTColumnView class, so we'll leave theCTView drawRect: method to be the standard UIScrollViewimplementation.
Alright... hit Run and you'll see text floating in columns! Dragright and left to go between pages ... awesome!
We have columns, formatted text, but we miss images. Turns outdrawing images with Core Text is not that easy - it's a textframework after all.
But thanks to the fact we already have a little markup parserwe're going to get images inside the text pretty quick!
Basically Core Text does not have possibility to draw images.However, since it's a layout engine, what it can do is leave anempty space where you want to draw a picture. And since your codeis already inside a drawRect: method, drawing an image yourself iseasy.
Let's look at how leaving an empty space in the text works.Remember all the text chunks are CTRun instances? You simply set adelegate for a given CTRun and the delegate object is responsibleto let know Core Text what is the CTRun ascent space, descent spaceand width. Like so:
When Core Text "reaches" a CTRun which has a CTRunDelegate itasks the delegate - how much width should I leave for this chunk ofdata, 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 littlemarkup parser! Open up MarkupParser.m and find "} //end of fontparsing"; add the following code immediately after this line to addsupport for "img" tag:
<span style="color: rgb(166, 19, 144);">if</span> <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(0, 34, 0);">[</span>tag hasPrefix<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"img"</span><span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span> <span style="color: rgb(0, 34, 0);">{</span> <wbr> __block <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color: rgb(64, 0, 128);">NSNumber</span></a><span style="color: rgb(0, 34, 0);">*</span> width <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color: rgb(64, 0, 128);">NSNumber</span></a> numberWithInt<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">0</span><span style="color: rgb(0, 34, 0);">]</span>; __block <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color: rgb(64, 0, 128);">NSNumber</span></a><span style="color: rgb(0, 34, 0);">*</span> height <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color: rgb(64, 0, 128);">NSNumber</span></a> numberWithInt<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">0</span><span style="color: rgb(0, 34, 0);">]</span>; __block <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: rgb(64, 0, 128);">NSString</span></a><span style="color: rgb(0, 34, 0);">*</span> fileName <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">""</span>; <wbr> <span style="color: rgb(17, 116, 10);"><em>//width</em></span> NSRegularExpression<span style="color: rgb(0, 34, 0);">*</span> widthRegex <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>NSRegularExpression alloc<span style="color: rgb(0, 34, 0);">]</span> initWithPattern<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"(?<=width=<span style="color: rgb(36, 0, 217);">"</span>)[^<span style="color: rgb(36, 0, 217);">"</span>]+"</span> options<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">0</span> error<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(166, 19, 144);">NULL</span><span style="color: rgb(0, 34, 0);">]</span> autorelease<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">[</span>widthRegex enumerateMatchesInString<wbr><span style="color: rgb(0, 34, 0);">:</span>tag options<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">0</span> range<span style="color: rgb(0, 34, 0);">:</span>NSMakeRange<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(36, 0, 217);">0</span>, <span style="color: rgb(0, 34, 0);">[</span>tag length<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span> usingBlock<span style="color: rgb(0, 34, 0);">:^</span><span style="color: rgb(0, 34, 0);">(</span>NSTextCheckingResult <span style="color: rgb(0, 34, 0);">*</span>match, NSMatchingFlags flags, <span style="color: rgb(166, 19, 144);">BOOL</span> <span style="color: rgb(0, 34, 0);">*</span>stop<span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">{</span> width <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color: rgb(64, 0, 128);">NSNumber</span></a> numberWithInt<span style="color: rgb(0, 34, 0);">:</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>tag substringWithRange<span style="color: rgb(0, 34, 0);">:</span> match.range<span style="color: rgb(0, 34, 0);">]</span> intValue<span style="color: rgb(0, 34, 0);">]</span> <span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span><span style="color: rgb(0, 34, 0);">]</span>; <wbr> <span style="color: rgb(17, 116, 10);"><em>//height</em></span> NSRegularExpression<span style="color: rgb(0, 34, 0);">*</span> faceRegex <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>NSRegularExpression alloc<span style="color: rgb(0, 34, 0);">]</span> initWithPattern<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"(?<=height=<span style="color: rgb(36, 0, 217);">"</span>)[^<span style="color: rgb(36, 0, 217);">"</span>]+"</span> options<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">0</span> error<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(166, 19, 144);">NULL</span><span style="color: rgb(0, 34, 0);">]</span> autorelease<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">[</span>faceRegex enumerateMatchesInString<wbr><span style="color: rgb(0, 34, 0);">:</span>tag options<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">0</span> range<span style="color: rgb(0, 34, 0);">:</span>NSMakeRange<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(36, 0, 217);">0</span>, <span style="color: rgb(0, 34, 0);">[</span>tag length<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span> usingBlock<span style="color: rgb(0, 34, 0);">:^</span><span style="color: rgb(0, 34, 0);">(</span>NSTextCheckingResult <span style="color: rgb(0, 34, 0);">*</span>match, NSMatchingFlags flags, <span style="color: rgb(166, 19, 144);">BOOL</span> <span style="color: rgb(0, 34, 0);">*</span>stop<span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">{</span> height <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color: rgb(64, 0, 128);">NSNumber</span></a> numberWithInt<span style="color: rgb(0, 34, 0);">:</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>tag substringWithRange<span style="color: rgb(0, 34, 0);">:</span>match.range<span style="color: rgb(0, 34, 0);">]</span> intValue<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span><span style="color: rgb(0, 34, 0);">]</span>; <wbr> <span style="color: rgb(17, 116, 10);"><em>//image</em></span> NSRegularExpression<span style="color: rgb(0, 34, 0);">*</span> srcRegex <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>NSRegularExpression alloc<span style="color: rgb(0, 34, 0);">]</span> initWithPattern<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"(?<=src=<span style="color: rgb(36, 0, 217);">"</span>)[^<span style="color: rgb(36, 0, 217);">"</span>]+"</span> options<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">0</span> error<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(166, 19, 144);">NULL</span><span style="color: rgb(0, 34, 0);">]</span> autorelease<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">[</span>srcRegex enumerateMatchesInString<wbr><span style="color: rgb(0, 34, 0);">:</span>tag options<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">0</span> range<span style="color: rgb(0, 34, 0);">:</span>NSMakeRange<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(36, 0, 217);">0</span>, <span style="color: rgb(0, 34, 0);">[</span>tag length<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span> usingBlock<span style="color: rgb(0, 34, 0);">:^</span><span style="color: rgb(0, 34, 0);">(</span>NSTextCheckingResult <span style="color: rgb(0, 34, 0);">*</span>match, NSMatchingFlags flags, <span style="color: rgb(166, 19, 144);">BOOL</span> <span style="color: rgb(0, 34, 0);">*</span>stop<span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">{</span> fileName <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span>tag substringWithRange<span style="color: rgb(0, 34, 0);">:</span> match.range<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span><span style="color: rgb(0, 34, 0);">]</span>; <wbr> <span style="color: rgb(17, 116, 10);"><em>//add the image for drawing</em></span> <span style="color: rgb(0, 34, 0);">[</span>self.images addObject<span style="color: rgb(0, 34, 0);">:</span> <span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color: rgb(64, 0, 128);">NSDictionary</span></a> dictionaryWithObjectsAnd<wbr>Keys<span style="color: rgb(0, 34, 0);">:</span> width, <span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"width"</span>, height, <span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"height"</span>, fileName, <span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"fileName"</span>, <span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/"><span style="color: rgb(64, 0, 128);">NSNumber</span></a> numberWithInt<span style="color: rgb(0, 34, 0);">:</span> <span style="color: rgb(0, 34, 0);">[</span>aString length<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">]</span>, <span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"location"</span>, <span style="color: rgb(166, 19, 144);">nil</span><span style="color: rgb(0, 34, 0);">]</span> <span style="color: rgb(0, 34, 0);">]</span>; <wbr> <span style="color: rgb(17, 116, 10);"><em>//render empty space for drawing the image in the text //1</em></span> CTRunDelegateCallbacks callbacks; callbacks.version <span style="color: rgb(0, 34, 0);">=</span> kCTRunDelegateVersion1; callbacks.getAscent <span style="color: rgb(0, 34, 0);">=</span> ascentCallback; callbacks.getDescent <span style="color: rgb(0, 34, 0);">=</span> descentCallback; callbacks.getWidth <span style="color: rgb(0, 34, 0);">=</span> widthCallback; callbacks.dealloc <span style="color: rgb(0, 34, 0);">=</span> deallocCallback; <wbr> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color: rgb(64, 0, 128);">NSDictionary</span></a><span style="color: rgb(0, 34, 0);">*</span> imgAttr <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color: rgb(64, 0, 128);">NSDictionary</span></a> dictionaryWithObjectsAnd<wbr>Keys<span style="color: rgb(0, 34, 0);">:</span> <span style="color: rgb(17, 116, 10);"><em>//2</em></span> width, <span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"width"</span>, height, <span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"height"</span>, <span style="color: rgb(166, 19, 144);">nil</span><span style="color: rgb(0, 34, 0);">]</span> retain<span style="color: rgb(0, 34, 0);">]</span>; <wbr> CTRunDelegateRef delegate <span style="color: rgb(0, 34, 0);">=</span> CTRunDelegateCreate<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(0, 34, 0);">&</span>callbacks, imgAttr<span style="color: rgb(0, 34, 0);">)</span>; <span style="color: rgb(17, 116, 10);"><em>//3</em></span> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color: rgb(64, 0, 128);">NSDictionary</span></a> <span style="color: rgb(0, 34, 0);">*</span>attrDictionaryDelegate <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color: rgb(64, 0, 128);">NSDictionary</span></a> dictionaryWithObjectsAnd<wbr>Keys<span style="color: rgb(0, 34, 0);">:</span> <span style="color: rgb(17, 116, 10);"><em>//set the delegate</em></span> <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">id</span><span style="color: rgb(0, 34, 0);">)</span>delegate, <span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: rgb(64, 0, 128);">NSString</span></a><span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span>kCTRunDelegateAttributeN<wbr>ame, <span style="color: rgb(166, 19, 144);">nil</span><span style="color: rgb(0, 34, 0);">]</span>; <wbr> <span style="color: rgb(17, 116, 10);"><em>//add a space to the text so that it can call the delegate</em></span> <span style="color: rgb(0, 34, 0);">[</span>aString appendAttributedString<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color: rgb(64, 0, 128);">NSAttributedString</span></a> alloc<span style="color: rgb(0, 34, 0);">]</span> initWithString<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">" "</span> attributes<span style="color: rgb(0, 34, 0);">:</span>attrDictionaryDelegate<span style="color: rgb(0, 34, 0);">]</span> autorelease<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span> </wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr> |
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 thefont tag. By using 3 regexes you effectively read the width, heightand src attributes of the img tag. When done - you add a newNSDictionary to self.images holding the information you just parsedout plus the location of the image in the text.
Now look at section 1 - CTRunDelegateCallbacks is a C structwhich holds references to functions. This struct provides theinformation you want to pass to the CTRunDelegate. As you can guessalready getWidth is called to provide a width for the CTRun,getAscent provides the height of the CTRun, etc. In the code aboveyou provide function names for those handlers; in a minute we'regonna add the function bodies too.
Section 2 is very important - imgAttr dictionary holds thedimentions of the image; this object is also retained as it isgoing to be passed to the function handlers - so, when getAscenthandler triggers it'll get as a parameter imgAttr and will justread 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 instancereference and binds the callbacks and the data parametertogether.
In the next step you need to create the attributes dictionary(the same way as for the font formatting above), but instead offormatting attributes you pass the delegate instance. In the endyou add a single space which will trigger the delegate and createthe hole in the text where the image will be rendered later on.
Next step, which you already anticipated, is to provide thecallback functions for the delegate:
<span style="color: rgb(17, 116, 10);"><em>//inside MarkupParser.m, just above @implementation</em></span> <wbr> <span style="color: rgb(166, 19, 144);">static</span> <span style="color: rgb(166, 19, 144);">void</span> deallocCallback<span style="color: rgb(0, 34, 0);">(</span> <span style="color: rgb(166, 19, 144);">void</span><span style="color: rgb(0, 34, 0);">*</span> ref <span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">{</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">id</span><span style="color: rgb(0, 34, 0);">)</span>ref release<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span> <span style="color: rgb(166, 19, 144);">static</span> CGFloat ascentCallback<span style="color: rgb(0, 34, 0);">(</span> <span style="color: rgb(166, 19, 144);">void</span> <span style="color: rgb(0, 34, 0);">*</span>ref <span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">{</span> <span style="color: rgb(166, 19, 144);">return</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: rgb(64, 0, 128);">NSString</span></a><span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color: rgb(64, 0, 128);">NSDictionary</span></a><span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span>ref objectForKey<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"height"</span><span style="color: rgb(0, 34, 0);">]</span> floatValue<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span> <span style="color: rgb(166, 19, 144);">static</span> CGFloat descentCallback<span style="color: rgb(0, 34, 0);">(</span> <span style="color: rgb(166, 19, 144);">void</span> <span style="color: rgb(0, 34, 0);">*</span>ref <span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">{</span> <span style="color: rgb(166, 19, 144);">return</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: rgb(64, 0, 128);">NSString</span></a><span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color: rgb(64, 0, 128);">NSDictionary</span></a><span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span>ref objectForKey<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"descent"</span><span style="color: rgb(0, 34, 0);">]</span> floatValue<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span> <span style="color: rgb(166, 19, 144);">static</span> CGFloat widthCallback<span style="color: rgb(0, 34, 0);">(</span> <span style="color: rgb(166, 19, 144);">void</span><span style="color: rgb(0, 34, 0);">*</span> ref <span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">{</span> <span style="color: rgb(166, 19, 144);">return</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: rgb(64, 0, 128);">NSString</span></a><span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color: rgb(64, 0, 128);">NSDictionary</span></a><span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span>ref objectForKey<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"width"</span><span style="color: rgb(0, 34, 0);">]</span> floatValue<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span> </wbr> |
ascentCallback, descentCallback and widthCallback only read therespective properties from the passed NSDictionary and provide themto CT. What deallocCallback does is that it releases the dictionaryholding the image information - it's called when the CTRunDelegategets deallocated, so you have chance to do your memory managementthere.
Now that your parser is handling "img" tags, let's adjust alsothe CTView to render them. We need a method to send the imagesarray to the view, let's combine setting the attributed string andthe images into one method. Add the code:
<span style="color: rgb(17, 116, 10);"><em>//CTView.h - inside @interface declaration as an ivar</em></span> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color: rgb(64, 0, 128);">NSArray</span></a><span style="color: rgb(0, 34, 0);">*</span> images; <wbr> <span style="color: rgb(17, 116, 10);"><em>//CTView.h - declare property for images</em></span> <span style="color: rgb(166, 19, 144);">@property</span> <span style="color: rgb(0, 34, 0);">(</span>retain, nonatomic<span style="color: rgb(0, 34, 0);">)</span> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color: rgb(64, 0, 128);">NSArray</span></a><span style="color: rgb(0, 34, 0);">*</span> images; <wbr> <span style="color: rgb(17, 116, 10);"><em>//CTView.h - add a method declaration</em></span> <span style="color: rgb(0, 34, 0);">-</span><span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">void</span><span style="color: rgb(0, 34, 0);">)</span>setAttString<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color: rgb(64, 0, 128);">NSAttributedString</span></a> <span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span>attString withImages<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color: rgb(64, 0, 128);">NSArray</span></a><span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span>imgs; <wbr> <span style="color: rgb(17, 116, 10);"><em>//CTView.m - just below @implementation</em></span> <span style="color: rgb(166, 19, 144);">@synthesize</span> images; <wbr> <span style="color: rgb(17, 116, 10);"><em>//CTView.m - inside the dealloc method</em></span> self.images <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(166, 19, 144);">nil</span>; <wbr> <span style="color: rgb(17, 116, 10);"><em>//CTView.m - anywhere inside the implementation</em></span> <span style="color: rgb(0, 34, 0);">-</span><span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">void</span><span style="color: rgb(0, 34, 0);">)</span>setAttString<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color: rgb(64, 0, 128);">NSAttributedString</span></a> <span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(166, 19, 144);">string</span> withImages<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color: rgb(64, 0, 128);">NSArray</span></a><span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span>imgs <span style="color: rgb(0, 34, 0);">{</span> self.attString <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(166, 19, 144);">string</span>; self.images <span style="color: rgb(0, 34, 0);">=</span> imgs; <span style="color: rgb(0, 34, 0);">}</span> </wbr></wbr></wbr></wbr></wbr> |
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 withthe following:
<span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">(</span>CTView <span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">[</span>self view<span style="color: rgb(0, 34, 0);">]</span> setAttString<span style="color: rgb(0, 34, 0);">:</span>attString withImages<span style="color: rgb(0, 34, 0);">:</span> p.images<span style="color: rgb(0, 34, 0);">]</span>; |
If you lookup attrStringFromMarkup: in the MarkupParser classyou'll see it saves all the image tags data into self.images. Thisis what you are passing now directly to the CTView.
To render images we'll have to know the exact frame of the runwhere the image should appear. To find the origin of that spot weneed to take in account several values:
Let's render those images! First of all we need to update theCTColumnView class:
<span style="color: rgb(17, 116, 10);"><em>//inside CTColumnView.h</em></span> <span style="color: rgb(17, 116, 10);"><em>//as an ivar</em></span> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/"><span style="color: rgb(64, 0, 128);">NSMutableArray</span></a><span style="color: rgb(0, 34, 0);">*</span> images; <wbr> <span style="color: rgb(17, 116, 10);"><em>//as a property</em></span> <span style="color: rgb(166, 19, 144);">@property</span> <span style="color: rgb(0, 34, 0);">(</span>retain, nonatomic<span style="color: rgb(0, 34, 0);">)</span> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/"><span style="color: rgb(64, 0, 128);">NSMutableArray</span></a><span style="color: rgb(0, 34, 0);">*</span> images; <wbr> <span style="color: rgb(17, 116, 10);"><em>//inside CTColumnView.m</em></span> <span style="color: rgb(17, 116, 10);"><em>//after @implementation...</em></span> <span style="color: rgb(166, 19, 144);">@synthesize</span> images; <wbr> <span style="color: rgb(0, 34, 0);">-</span><span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">id</span><span style="color: rgb(0, 34, 0);">)</span>initWithFrame<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(0, 34, 0);">(</span>CGRect<span style="color: rgb(0, 34, 0);">)</span>frame <span style="color: rgb(0, 34, 0);">{</span> <span style="color: rgb(166, 19, 144);">if</span> <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(0, 34, 0);">[</span>super initWithFrame<span style="color: rgb(0, 34, 0);">:</span>frame<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">!=</span><span style="color: rgb(166, 19, 144);">nil</span><span style="color: rgb(0, 34, 0);">)</span> <span style="color: rgb(0, 34, 0);">{</span> self.images <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/"><span style="color: rgb(64, 0, 128);">NSMutableArray</span></a> array<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span> <span style="color: rgb(166, 19, 144);">return</span> self; <span style="color: rgb(0, 34, 0);">}</span> <wbr> <span style="color: rgb(0, 34, 0);">-</span><span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">void</span><span style="color: rgb(0, 34, 0);">)</span>dealloc <span style="color: rgb(0, 34, 0);">{</span> self.images<span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(166, 19, 144);">nil</span>; <span style="color: rgb(0, 34, 0);">[</span>super dealloc<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span> <wbr> <span style="color: rgb(17, 116, 10);"><em>//at the end of drawRect:</em></span> <span style="color: rgb(166, 19, 144);">for</span> <span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color: rgb(64, 0, 128);">NSArray</span></a><span style="color: rgb(0, 34, 0);">*</span> imageData <span style="color: rgb(166, 19, 144);">in</span> self.images<span style="color: rgb(0, 34, 0);">)</span> <span style="color: rgb(0, 34, 0);">{</span> UIImage<span style="color: rgb(0, 34, 0);">*</span> img <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span>imageData objectAtIndex<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">0</span><span style="color: rgb(0, 34, 0);">]</span>; CGRect imgBounds <span style="color: rgb(0, 34, 0);">=</span> CGRectFromString<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(0, 34, 0);">[</span>imageData objectAtIndex<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(36, 0, 217);">1</span><span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span>; CGContextDrawImage<span style="color: rgb(0, 34, 0);">(</span>context, imgBounds, img.CGImage<span style="color: rgb(0, 34, 0);">)</span>; <span style="color: rgb(0, 34, 0);">}</span> </wbr></wbr></wbr></wbr></wbr> |
So with this code update we add an ivar and a propertycalled images where wewill keep a list of the images appearing in each text column. Toavoid declaring yet another new class to hold the image datainsideimageswe're going to store NSArray objects whichwill hold:
And now the code that calculates the images' position andattaches them to the respective text columns:
<span style="color: rgb(17, 116, 10);"><em>//inside CTView.h</em></span> <span style="color: rgb(0, 34, 0);">-</span><span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">void</span><span style="color: rgb(0, 34, 0);">)</span>attachImagesWithFrame<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(0, 34, 0);">(</span>CTFrameRef<span style="color: rgb(0, 34, 0);">)</span>f inColumnView<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(0, 34, 0);">(</span>CTColumnView<span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span>col; <wbr> <span style="color: rgb(17, 116, 10);"><em>//inside CTView.m</em></span> <span style="color: rgb(0, 34, 0);">-</span><span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">void</span><span style="color: rgb(0, 34, 0);">)</span>attachImagesWithFrame<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(0, 34, 0);">(</span>CTFrameRef<span style="color: rgb(0, 34, 0);">)</span>f inColumnView<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(0, 34, 0);">(</span>CTColumnView<span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span>col <span style="color: rgb(0, 34, 0);">{</span> <span style="color: rgb(17, 116, 10);"><em>//drawing images</em></span> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color: rgb(64, 0, 128);">NSArray</span></a> <span style="color: rgb(0, 34, 0);">*</span>lines <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color: rgb(64, 0, 128);">NSArray</span></a> <span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span>CTFrameGetLines<span style="color: rgb(0, 34, 0);">(</span>f<span style="color: rgb(0, 34, 0);">)</span>; <span style="color: rgb(17, 116, 10);"><em>//1</em></span> <wbr> CGPoint origins<span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>lines count<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">]</span>; CTFrameGetLineOrigins<span style="color: rgb(0, 34, 0);">(</span>f, CFRangeMake<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(36, 0, 217);">0</span>, <span style="color: rgb(36, 0, 217);">0</span><span style="color: rgb(0, 34, 0);">)</span>, origins<span style="color: rgb(0, 34, 0);">)</span>; <span style="color: rgb(17, 116, 10);"><em>//2</em></span> <wbr> <span style="color: rgb(166, 19, 144);">int</span> imgIndex <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(36, 0, 217);">0</span>; <span style="color: rgb(17, 116, 10);"><em>//3</em></span> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color: rgb(64, 0, 128);">NSDictionary</span></a><span style="color: rgb(0, 34, 0);">*</span> nextImage <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span>self.images objectAtIndex<span style="color: rgb(0, 34, 0);">:</span>imgIndex<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(166, 19, 144);">int</span> imgLocation <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>nextImage objectForKey<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"location"</span><span style="color: rgb(0, 34, 0);">]</span> intValue<span style="color: rgb(0, 34, 0);">]</span>; <wbr> <span style="color: rgb(17, 116, 10);"><em>//find images for the current column</em></span> CFRange frameRange <span style="color: rgb(0, 34, 0);">=</span> CTFrameGetVisibleStringR<wbr>ange<span style="color: rgb(0, 34, 0);">(</span>f<span style="color: rgb(0, 34, 0);">)</span>; <span style="color: rgb(17, 116, 10);"><em>//4</em></span> <span style="color: rgb(166, 19, 144);">while</span> <span style="color: rgb(0, 34, 0);">(</span> imgLocation < frameRange.location <span style="color: rgb(0, 34, 0);">)</span> <span style="color: rgb(0, 34, 0);">{</span> imgIndex<span style="color: rgb(0, 34, 0);">++</span>; <span style="color: rgb(166, 19, 144);">if</span> <span style="color: rgb(0, 34, 0);">(</span>imgIndex><span style="color: rgb(0, 34, 0);">=</span><span style="color: rgb(0, 34, 0);">[</span>self.images count<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span> <span style="color: rgb(166, 19, 144);">return</span>; <span style="color: rgb(17, 116, 10);"><em>//quit if no images for this column</em></span> nextImage <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span>self.images objectAtIndex<span style="color: rgb(0, 34, 0);">:</span>imgIndex<span style="color: rgb(0, 34, 0);">]</span>; imgLocation <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>nextImage objectForKey<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"location"</span><span style="color: rgb(0, 34, 0);">]</span> intValue<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span> <wbr> NSUInteger lineIndex <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(36, 0, 217);">0</span>; <span style="color: rgb(166, 19, 144);">for</span> <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">id</span> lineObj <span style="color: rgb(166, 19, 144);">in</span> lines<span style="color: rgb(0, 34, 0);">)</span> <span style="color: rgb(0, 34, 0);">{</span> <span style="color: rgb(17, 116, 10);"><em>//5</em></span> CTLineRef line <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">(</span>CTLineRef<span style="color: rgb(0, 34, 0);">)</span>lineObj; <wbr> <span style="color: rgb(166, 19, 144);">for</span> <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">id</span> runObj <span style="color: rgb(166, 19, 144);">in</span> <span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color: rgb(64, 0, 128);">NSArray</span></a> <span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span>CTLineGetGlyphRuns<span style="color: rgb(0, 34, 0);">(</span>line<span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">)</span> <span style="color: rgb(0, 34, 0);">{</span> <span style="color: rgb(17, 116, 10);"><em>//6</em></span> CTRunRef run <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">(</span>CTRunRef<span style="color: rgb(0, 34, 0);">)</span>runObj; CFRange runRange <span style="color: rgb(0, 34, 0);">=</span> CTRunGetStringRange<span style="color: rgb(0, 34, 0);">(</span>run<span style="color: rgb(0, 34, 0);">)</span>; <wbr> <span style="color: rgb(166, 19, 144);">if</span> <span style="color: rgb(0, 34, 0);">(</span> runRange.location <<span style="color: rgb(0, 34, 0);">= imgLocation <span style="color: rgb(0, 34, 0);">&&</span> runRange.location<span style="color: rgb(0, 34, 0);">+</span>runRange.length > imgLocation <span style="color: rgb(0, 34, 0);">)</span> <span style="color: rgb(0, 34, 0);">{</span> <span style="color: rgb(17, 116, 10);"><em>//7</em></span> CGRect runBounds; CGFloat ascent;<span style="color: rgb(17, 116, 10);"><em>//height above the baseline</em></span> CGFloat descent;<span style="color: rgb(17, 116, 10);"><em>//height below the baseline</em></span> runBounds.size.width <span style="color: rgb(0, 34, 0);">=</span> CTRunGetTypographicBound<wbr>s<span style="color: rgb(0, 34, 0);">(</span>run, CFRangeMake<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(36, 0, 217);">0</span>, <span style="color: rgb(36, 0, 217);">0</span><span style="color: rgb(0, 34, 0);">)</span>, <span style="color: rgb(0, 34, 0);">&</span>ascent, <span style="color: rgb(0, 34, 0);">&</span>descent, <span style="color: rgb(166, 19, 144);">NULL</span><span style="color: rgb(0, 34, 0);">)</span>; <span style="color: rgb(17, 116, 10);"><em>//8</em></span> runBounds.size.height <span style="color: rgb(0, 34, 0);">=</span> ascent <span style="color: rgb(0, 34, 0);">+</span> descent; <wbr> CGFloat xOffset <span style="color: rgb(0, 34, 0);">=</span> CTLineGetOffsetForString<wbr>Index<span style="color: rgb(0, 34, 0);">(</span>line, CTRunGetStringRange<span style="color: rgb(0, 34, 0);">(</span>run<span style="color: rgb(0, 34, 0);">)</span>.location, <span style="color: rgb(166, 19, 144);">NULL</span><span style="color: rgb(0, 34, 0);">)</span>; <span style="color: rgb(17, 116, 10);"><em>//9</em></span> runBounds.origin.x <span style="color: rgb(0, 34, 0);">=</span> origins<span style="color: rgb(0, 34, 0);">[</span>lineIndex<span style="color: rgb(0, 34, 0);">]</span>.x <span style="color: rgb(0, 34, 0);">+</span> self.frame.origin.x <span style="color: rgb(0, 34, 0);">+</span> xOffset <span style="color: rgb(0, 34, 0);">+</span> frameXOffset; runBounds.origin.y <span style="color: rgb(0, 34, 0);">=</span> origins<span style="color: rgb(0, 34, 0);">[</span>lineIndex<span style="color: rgb(0, 34, 0);">]</span>.y <span style="color: rgb(0, 34, 0);">+</span> self.frame.origin.y <span style="color: rgb(0, 34, 0);">+</span> frameYOffset; runBounds.origin.y <span style="color: rgb(0, 34, 0);">-=</span> descent; <wbr> UIImage <span style="color: rgb(0, 34, 0);">*</span>img <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span>UIImage imageNamed<span style="color: rgb(0, 34, 0);">:</span> <span style="color: rgb(0, 34, 0);">[</span>nextImage objectForKey<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"fileName"</span><span style="color: rgb(0, 34, 0);">]</span> <span style="color: rgb(0, 34, 0);">]</span>; CGPathRef pathRef <span style="color: rgb(0, 34, 0);">=</span> CTFrameGetPath<span style="color: rgb(0, 34, 0);">(</span>f<span style="color: rgb(0, 34, 0);">)</span>; <span style="color: rgb(17, 116, 10);"><em>//10</em></span> CGRect colRect <span style="color: rgb(0, 34, 0);">=</span> CGPathGetBoundingBox<span style="color: rgb(0, 34, 0);">(</span>pathRef<span style="color: rgb(0, 34, 0);">)</span>; <wbr> CGRect imgBounds <span style="color: rgb(0, 34, 0);">=</span> CGRectOffset<span style="color: rgb(0, 34, 0);">(</span>runBounds, colRect.origin.x <span style="color: rgb(0, 34, 0);">-</span> frameXOffset <span style="color: rgb(0, 34, 0);">-</span> self.contentOffset.x, colRect.origin.y <span style="color: rgb(0, 34, 0);">-</span> frameYOffset <span style="color: rgb(0, 34, 0);">-</span> self.frame.origin.y<span style="color: rgb(0, 34, 0);">)</span>; <span style="color: rgb(0, 34, 0);">[</span>col.images addObject<span style="color: rgb(0, 34, 0);">:</span> <span style="color: rgb(17, 116, 10);"><em>//11</em></span> <span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/"><span style="color: rgb(64, 0, 128);">NSArray</span></a> arrayWithObjects<span style="color: rgb(0, 34, 0);">:</span>img, NSStringFromCGRect<span style="color: rgb(0, 34, 0);">(</span>imgBounds<span style="color: rgb(0, 34, 0);">)</span> , <span style="color: rgb(166, 19, 144);">nil</span><span style="color: rgb(0, 34, 0);">]</span> <span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(17, 116, 10);"><em>//load the next image //12</em></span> imgIndex<span style="color: rgb(0, 34, 0);">++</span>; <span style="color: rgb(166, 19, 144);">if</span> <span style="color: rgb(0, 34, 0);">(</span>imgIndex < <span style="color: rgb(0, 34, 0);">[</span>self.images count<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span> <span style="color: rgb(0, 34, 0);">{</span> nextImage <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span>self.images objectAtIndex<span style="color: rgb(0, 34, 0);">:</span> imgIndex<span style="color: rgb(0, 34, 0);">]</span>; imgLocation <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span>nextImage objectForKey<span style="color: rgb(0, 34, 0);">:</span> <span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"location"</span><span style="color: rgb(0, 34, 0);">]</span> intValue<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">}</span> <wbr> <span style="color: rgb(0, 34, 0);">}</span> <span style="color: rgb(0, 34, 0);">}</span> lineIndex<span style="color: rgb(0, 34, 0);">++</span>; <span style="color: rgb(0, 34, 0);">}</span> <span style="color: rgb(0, 34, 0);">}</span> </wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr> |
Note: Ultimate credit goes to David Beck for his example onlooping 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 forglory!
Let's go section by section:
OK! Great! Almost there - one tiny final step: find in CTView.mthe line "[content setCTFrame:(id)frame];" and add below it:
<span style="color: rgb(0, 34, 0);">[</span>self attachImagesWithFrame<span style="color: rgb(0, 34, 0);">:</span>frame inColumnView<span style="color: rgb(0, 34, 0);">:</span> content<span style="color: rgb(0, 34, 0);">]</span>; |
Now you have all the code working, but you don't have greatcontent to visualize...
No fear, I've prepared the next issue of Zombie Monthly - amontly popular magazine for zombies - for you; just do thefollowing to import the content:
Then switch to CoreTextMagazineViewController.m and switch theline that gets the path to the file to use the new zombies.txtinstead:
<a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: rgb(64, 0, 128);">NSString</span></a> <span style="color: rgb(0, 34, 0);">*</span>path <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSBundle_Class/"><span style="color: rgb(64, 0, 128);">NSBundle</span></a> mainBundle<span style="color: rgb(0, 34, 0);">]</span> pathForResource<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"zombies"</span> ofType<span style="color: rgb(0, 34, 0);">:</span><span style="color: rgb(191, 29, 26);">@</span><span style="color: rgb(191, 29, 26);">"txt"</span><span style="color: rgb(0, 34, 0);">]</span>; |
That's it - compile and run, and enjoy the latest issue ofZombie Monthly! :)
Just one final touch. Say we want to make the text in thecolumns justified so they fill the entire width of the column. Addthe following code to make this happen:
<span style="color: rgb(17, 116, 10);"><em>//inside CTView.m</em></span> <span style="color: rgb(17, 116, 10);"><em>//at the end of the setAttString:withImages: method</em></span> <wbr> CTTextAlignment alignment <span style="color: rgb(0, 34, 0);">=</span> kCTJustifiedTextAlignmen<wbr>t; <wbr> CTParagraphStyleSetting settings<span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">]</span> <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">{</span> <span style="color: rgb(0, 34, 0);">{</span>kCTParagraphStyleSpecifi<wbr>erAlignment, <a target=_blank href="http://www.opengroup.org/onlinepubs/009695399/functions/sizeof.html"><span style="color: rgb(166, 19, 144);">sizeof</span></a><span style="color: rgb(0, 34, 0);">(</span>alignment<span style="color: rgb(0, 34, 0);">)</span>, <span style="color: rgb(0, 34, 0);">&</span>alignment<span style="color: rgb(0, 34, 0);">}</span>, <span style="color: rgb(0, 34, 0);">}</span>; CTParagraphStyleRef paragraphStyle <span style="color: rgb(0, 34, 0);">=</span> CTParagraphStyleCreate<span style="color: rgb(0, 34, 0);">(</span>settings, <a target=_blank href="http://www.opengroup.org/onlinepubs/009695399/functions/sizeof.html"><span style="color: rgb(166, 19, 144);">sizeof</span></a><span style="color: rgb(0, 34, 0);">(</span>settings<span style="color: rgb(0, 34, 0);">)</span> <span style="color: rgb(0, 34, 0);">/</span> <a target=_blank href="http://www.opengroup.org/onlinepubs/009695399/functions/sizeof.html"><span style="color: rgb(166, 19, 144);">sizeof</span></a><span style="color: rgb(0, 34, 0);">(</span>settings<span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(36, 0, 217);">0</span><span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">)</span>; <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color: rgb(64, 0, 128);">NSDictionary</span></a> <span style="color: rgb(0, 34, 0);">*</span>attrDictionary <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/"><span style="color: rgb(64, 0, 128);">NSDictionary</span></a> dictionaryWithObjectsAnd<wbr>Keys<span style="color: rgb(0, 34, 0);">:</span> <span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(166, 19, 144);">id</span><span style="color: rgb(0, 34, 0);">)</span>paragraphStyle, <span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSString_Class/"><span style="color: rgb(64, 0, 128);">NSString</span></a><span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span>kCTParagraphStyleAttribu<wbr>teName, <span style="color: rgb(166, 19, 144);">nil</span><span style="color: rgb(0, 34, 0);">]</span>; <wbr> <a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSMutableAttributedString_Class/"><span style="color: rgb(64, 0, 128);">NSMutableAttributedStrin<wbr>g</wbr></span></a><span style="color: rgb(0, 34, 0);">*</span> stringCopy <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><span style="color: rgb(0, 34, 0);">[</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSMutableAttributedString_Class/"><span style="color: rgb(64, 0, 128);">NSMutableAttributedStrin<wbr>g</wbr></span></a> alloc<span style="color: rgb(0, 34, 0);">]</span> initWithAttributedString<wbr><span style="color: rgb(0, 34, 0);">:</span>self.attString<span style="color: rgb(0, 34, 0);">]</span> autorelease<span style="color: rgb(0, 34, 0);">]</span>; <span style="color: rgb(0, 34, 0);">[</span>stringCopy addAttributes<span style="color: rgb(0, 34, 0);">:</span>attrDictionary range<span style="color: rgb(0, 34, 0);">:</span>NSMakeRange<span style="color: rgb(0, 34, 0);">(</span><span style="color: rgb(36, 0, 217);">0</span>, <span style="color: rgb(0, 34, 0);">[</span>attString length<span style="color: rgb(0, 34, 0);">]</span><span style="color: rgb(0, 34, 0);">)</span><span style="color: rgb(0, 34, 0);">]</span>; self.attString <span style="color: rgb(0, 34, 0);">=</span> <span style="color: rgb(0, 34, 0);">(</span><a target=_blank href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSAttributedString_Class/"><span style="color: rgb(64, 0, 128);">NSAttributedString</span></a><span style="color: rgb(0, 34, 0);">*</span><span style="color: rgb(0, 34, 0);">)</span>stringCopy; </wbr></wbr></wbr></wbr></wbr></wbr></wbr></wbr> |
This should get you started with paragraph styling; lookupkCTParagraphStyleSpecifierAlignment in Apple's Core Textdocumentation to get to the list of all paragraph styles you cancontrol.
Now that your Magazine App with Core Text is finished you mightbe 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 itto visualize a single multicolored line of text is a hugeoverkill.
Imagine you have 10 multicolor labels in your UI- this meansyou're going to consume the memory for 10 Safaris (ok, almost, butyou get the point).
So, keep in mind: UIWebView is a great web browser for when youneed one, while Core Text is an efficient text renderingengine.
Here's the complete Core Textexample project we developed in the abovetutorial.
If you want to keep playing around with this project to learnmore about Core Text, read up in Apple'sCoreText Reference Collection and see if you canadd some of the following features to the app:
Since I know you are already thinking how to expand the parserengine beyond what I included in this short tutorial I have tworecommendations for you:
If you have any questions, comments, or suggestions, please joinin the forum discussion below!