非同期処理は、いったん保留してCore Textやるざます。
縦書きね。

それに合わせて、公開するするといいつつ保留になってた、するする詐欺のABプリントのプロジェクトの公開をするっすよ~。

Core Data本を出したし、読んでくれた人へのサンプルの1つも兼ねてね。
で、このABプリントは日本語の縦書きをやってるんですが、出したときはiOS 5全盛でiOS 5のCore Textはうまく縦書きができないとかいう都市伝説があったので、直接自前でCGContextShowGlyphs使って描いてたんですよ。
それが…

Deprecated:非推奨、いつ無くなっても責任は負わないよ~ん、サポートもしない。
ごらんの有り様だよ…
まあ、ぶっちゃけCGContextShowGlyphsAtPositionsを残してもらえてるんで、そのまま簡単にCGContextShowGlyphsをCGContextShowGlyphsAtPositionsに置き換えでもいいんですが、いい機会なのでCore Textを使ってみようかと思います。Use Core Text instead.とか書かれてるしな。
iOS 6でもバグ技が話題になっているが、アラビア語の文字列を扱う時でiOS 6限定の不具合なので無視する。
とにかく、いつもの感じでXcodeのFile→New→Project…メニューを選んでワンツースリーだ。
今回はSingle View Applicationテンプレート使用ね。

設定はこんな感じ。
ここらへんは適当に。ついにARCデフォルトになりましたな。
そのおかげでデバッグ本やドリル本の補足PDFを用意するはめになったのよ。近々サポートページにアップされる予定。

そういえば、独自のクラスにはプリフィックスを3文字以上にしろってコーディング規約に明記されてたのを最近発見。EDじゃなくEDUくらいにするべきですな…
で、作成されたEDViewControllerの画面に、Core Text実験用のビューを1つ貼付けます。
これを
と名付け、新規に単独ファイルとしてプロジェクトに追加。
File→New→File…メニューを選んでワンツースリーだ。

設定はこんな感じ。
UIViewの派生クラスね。

で、EDViewController.m側の-viewDidLoadメソッドでEDTextViewを1つ作成してEDViewControllerの管理するビュー:self.viewに貼付ける。
これで準備派完了。
ブランチ:ex0 ブランチについては最後で
#import "EDViewController.h"
#import "EDTextView.h"
・・・
- (void)viewDidLoad
{
[super viewDidLoad];
EDTextView* textview = [[EDTextView alloc]
initWithFrame:CGRectMake(40, 100, 240, 260)];
[self.view addSubview:textview];
}
Runさせても何も見えませんが、ちゃんと張り付いています。

で、通常UIViewの内部に文字列を表示させるならUILabelを貼付けて以上。
なわけですが、目的はCore Textの学習であり縦書きの調査なわけなので、EDTextViewでは-drawRect:メソッドをオーバーライドして、その中でUIGraphicsGetCurrentContext()で戻されるCGContextRefに対して描画をおこないます。
こういった場合、iOS 6まではUIKitのNSString拡張を利用して
- (void)drawRect:(CGRect)rect
{
// 表示する文字列
NSString* string = @"503 ご~おぉまぁ~り・さん…
// self.bounds全体を青色で塗りつぶす
[[UIColor blueColor] setFill];
UIRectFill(self.bounds);
[string drawInRect:self.bounds withFont:[UIFont systemFontOfSize:14]];
注意: [[UIColor blueColor] setFill];UIRectFill(self.bounds)を実行しておかないと、ビュー全体が黒くなって文字が判別できなくなる。-initFrame:メソッド側でself.backGround = [UIColor blueColor]でも可。
でよかったわけですが、ここらへんは全部、非推奨になってしまったんでNSStringなら-drawInRect:withAttributes:もしくはNSString のかわりにNSAttributedStringを使う事になります。
もっとも-drawInRect:withAttributes:はiOS 7から利用可能なんで、iOS 6もサポートするんじゃいな人は、NSAttributedString一択っす。
ブランチ:ex1
- (void)drawRect:(CGRect)rect
{
// 表示する文字列
NSString* string = @"503 ご~おぉまぁ~り・さん…
// self.bounds全体を青色で塗りつぶす
[[UIColor blueColor] setFill];
UIRectFill(self.bounds);
// NSAttributedStringを使ったテキスト描画
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string];
[attributedString drawInRect:self.bounds];
}
Runするとこんな感じ。

で、NSStringの-drawInRect:withFont:なんかでやっていたフォントの指定はどうやるかというと、NSDictionaryでキーと設定値のペアを作ってNSAttributedStringインスタンス作成時に指定する事になります。
例えばフォントの指定には NSFontAttributeNameというキーを使う。
そんでもって、このNSDictionaryを引数に取る-initWithString:attributes:でNSAttributedStringを作成すればOK。
ブランチ:ex2
// 24ポイントフォント使用
UIFont *font = [UIFont systemFontOfSize:24];
// NSAttributedString用の属性を用意
NSDictionary *attributes = @{
NSFontAttributeName:font
};
// 属性指定でNSAttributedStringを作成
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string
attributes:attributes];
[attributedString drawInRect:self.bounds];
モダンObjective-C文法使うと、NSDictionaryやNSArrayの作成が簡単に記述できて、ほんとはかどるわ。モダ…何?な人は日本語ドキュメントページに置いてある「Objective-Cによるプログラミング」ドキュメントを「リテラル構文」で検索すべし。
Runするとこんな感じ。

ちなみにフォント以外にもNSForegroundColorAttributeNameキーでテキスト色とか
ブランチ:ex3
// 文字に黄色指定
UIColor* textColor = [UIColor yellowColor];
// NSAttributedString用の属性を用意
NSDictionary *attributes = @{
NSFontAttributeName:font,
NSForegroundColorAttributeName:textColor
};
NSParagraphStyleAttributeNameキーで段落属性なんてのも指定できる。
文字の中央よせ配置なんかはこれで指定するわけだ。
ブランチ:ex4
// 段落設定
NSMutableParagraphStyle* paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.alignment = NSTextAlignmentCenter; // センタリング
paragraphStyle.minimumLineHeight = 40; // 行間40ポイント
// NSAttributedString用の属性を用意
NSDictionary *attributes = @{
NSFontAttributeName:font,
NSForegroundColorAttributeName:textColor,
NSParagraphStyleAttributeName:paragraphStyle
};
Runするとこんな感じ。

NSMutableAttributedStringにして、文字列の一部区間にだけ属性を適用させる事も可能。次のように5文字めから10文字分にだけ、これまでの属性を指定すると
NSMutableAttributedString* attributedString = [[NSMutableAttributedString alloc] initWithString:string];
NSRange range;
range.location = 5;
range.length = 10;
[attributedString addAttributes:attributes range:range];
次のようになります。

5や10がバイト数の指定ではなく、文字数の指定になってるところも注目ですな。便利。
んなわけでして…たいがいの事はNSAttributedStringで事足りるわけですよ。
ただし、さすがに縦書きは無理みたいで、その場合はNSAttributedString の下働きとして使われているCore Textの出番という事になる。
ということで、まずはCore Textを使うためにプロジェクトにCoreText.frameworkを追加します。ワンツースリー。

ふぉっぉおおおおおお。
これでフレームワーク選択画面が出るんで、あとはCoreText.frameworkを選んでAddボタンを押す。

ここまでできたらEDTextView.mでCoreText/CoreText.hをimportして-drawInRect:部をCore Text版に変更。
ブランチ:ex5
#import
#import "EDTextView.h"
・・・
- (void)drawRect:(CGRect)rect
{
NSAttributedString *attributedString = [[NSAttributedString alloc]
initWithString:string
attributes:attributes];
// CTFramesetter用意
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(
(__bridge CFAttributedStringRef)(attributedString));
// CTFramesetterを使い、文字列を指定した領域(今回はビューの矩形)に
// 配置させたCTFrameを作成する
UIBezierPath* path = [UIBezierPath bezierPathWithRect:self.bounds];
CTFrameRef frame = CTFramesetterCreateFrame(framesetter,
CFRangeMake(0, 0), path.CGPath, NULL);
// 描画用コンテキスト取り出し
CGContextRef context = UIGraphicsGetCurrentContext();
// 座標系の調整 数学で使う上方向にyが増加する座標系にする
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// テキスト用のアフィン変換用行列を初期化する
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// 描画
CTFrameDraw(frame, context);
// 作成したCTFramesetterとCTFrameの所有権を放棄する
CFRelease(frame);
CFRelease(framesetter);
}
Runしても、NSAttributedStringの-drawInRect:と変わりありません。
ちゅ~か、たぶんNSAttributedStringの-drawInRect:は内部でこういうことやっていると思われ。
で、これにアレとアレ(ブランチ:ex6)を足すと~
ハート形に縦書き文字配置~。
ちゃんと禁則処理もしてくれてます。確認するために指で画面なでるとハートが拡大縮小して文字を配置しなおすようにしてみたお(ブランチ:ex7)。

お、おお~、おおぉ~う
ほらほら、ちゃんと"~"や"」"が先頭に来ないようにとか処理してます。
出版のプロの目だと話にならんのかもしれんが、素人目には十分ですわ。
もっとも、私のABプリントは住所が対象なので、英語同様スペース部以外では折り返したくないわけです。ちょっと工夫が必要になるんですが、そこらへんはこれからじっくりやりましょう。
CTFramesetterとは何なのか、そいつが作り出すCTFrameとは、以下次回!
待ちきれねーよな人は、ハート縮小拡大まで実装したサンプルプロジェクトをダウンロードして自習だ。
------------
サンプルプロジェクト:coretext.zip
ちなみに、ブランチ:ex1~ex7の各ステップ間のソース変更の差分はこうやって確認できます。

これで、選んだログ項目(リビジョンという)と、その1つ前のリビジョンの差分を表示する画面が現れる。

確認が終わったら、Donボタンで画面閉じます。
基本の編集画面に戻るにはShow the standard editorを選択ね。

各ex1~ex7までの段階を試したい場合は、Source Control→coretext master→Switch to Branchメニュー選んでください。
出てきた画面で、あらかじめ登録しておいたex1~ex7までの好きなブランチ(分岐)地点を選んで、その状態にプロジェクトを変更できます。

Runすると、その地点での処理が試せます。
最新状態に戻すには、さっきと同じくSource Control→core text {最後に選んだブランチ名}→Switch to Branchメニューを選んで、今度は出てきた画面で、masterを選びます。

これで最新状態に戻る。
途中で紹介したNSMutableAttributedStringを使う版は無いのであしからず。
以上ったら以上!
縦書きね。

それに合わせて、公開するするといいつつ保留になってた、するする詐欺のABプリントのプロジェクトの公開をするっすよ~。

Core Data本を出したし、読んでくれた人へのサンプルの1つも兼ねてね。
で、このABプリントは日本語の縦書きをやってるんですが、出したときはiOS 5全盛でiOS 5のCore Textはうまく縦書きができないとかいう都市伝説があったので、直接自前でCGContextShowGlyphs使って描いてたんですよ。
それが…

Deprecated:非推奨、いつ無くなっても責任は負わないよ~ん、サポートもしない。
ごらんの有り様だよ…
まあ、ぶっちゃけCGContextShowGlyphsAtPositionsを残してもらえてるんで、そのまま簡単にCGContextShowGlyphsをCGContextShowGlyphsAtPositionsに置き換えでもいいんですが、いい機会なのでCore Textを使ってみようかと思います。Use Core Text instead.とか書かれてるしな。
iOS 6でもバグ技が話題になっているが、アラビア語の文字列を扱う時でiOS 6限定の不具合なので無視する。
とにかく、いつもの感じでXcodeのFile→New→Project…メニューを選んでワンツースリーだ。
今回はSingle View Applicationテンプレート使用ね。

設定はこんな感じ。
ここらへんは適当に。ついにARCデフォルトになりましたな。
そのおかげでデバッグ本やドリル本の補足PDFを用意するはめになったのよ。近々サポートページにアップされる予定。

そういえば、独自のクラスにはプリフィックスを3文字以上にしろってコーディング規約に明記されてたのを最近発見。EDじゃなくEDUくらいにするべきですな…
で、作成されたEDViewControllerの画面に、Core Text実験用のビューを1つ貼付けます。
これを
EDTextView
と名付け、新規に単独ファイルとしてプロジェクトに追加。
File→New→File…メニューを選んでワンツースリーだ。

設定はこんな感じ。
UIViewの派生クラスね。

で、EDViewController.m側の-viewDidLoadメソッドでEDTextViewを1つ作成してEDViewControllerの管理するビュー:self.viewに貼付ける。
これで準備派完了。
ブランチ:ex0 ブランチについては最後で
#import "EDViewController.h"
#import "EDTextView.h"
・・・
- (void)viewDidLoad
{
[super viewDidLoad];
EDTextView* textview = [[EDTextView alloc]
initWithFrame:CGRectMake(40, 100, 240, 260)];
[self.view addSubview:textview];
}
Runさせても何も見えませんが、ちゃんと張り付いています。

で、通常UIViewの内部に文字列を表示させるならUILabelを貼付けて以上。
なわけですが、目的はCore Textの学習であり縦書きの調査なわけなので、EDTextViewでは-drawRect:メソッドをオーバーライドして、その中でUIGraphicsGetCurrentContext()で戻されるCGContextRefに対して描画をおこないます。
こういった場合、iOS 6まではUIKitのNSString拡張を利用して
- (void)drawRect:(CGRect)rect
{
// 表示する文字列
NSString* string = @"503 ご~おぉまぁ~り・さん…
// self.bounds全体を青色で塗りつぶす
[[UIColor blueColor] setFill];
UIRectFill(self.bounds);
[string drawInRect:self.bounds withFont:[UIFont systemFontOfSize:14]];
でよかったわけですが、ここらへんは全部、非推奨になってしまったんでNSStringなら-drawInRect:withAttributes:もしくはNSString のかわりにNSAttributedStringを使う事になります。
もっとも-drawInRect:withAttributes:はiOS 7から利用可能なんで、iOS 6もサポートするんじゃいな人は、NSAttributedString一択っす。
ブランチ:ex1
- (void)drawRect:(CGRect)rect
{
// 表示する文字列
NSString* string = @"503 ご~おぉまぁ~り・さん…
// self.bounds全体を青色で塗りつぶす
[[UIColor blueColor] setFill];
UIRectFill(self.bounds);
// NSAttributedStringを使ったテキスト描画
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string];
[attributedString drawInRect:self.bounds];
}
Runするとこんな感じ。

で、NSStringの-drawInRect:withFont:なんかでやっていたフォントの指定はどうやるかというと、NSDictionaryでキーと設定値のペアを作ってNSAttributedStringインスタンス作成時に指定する事になります。
例えばフォントの指定には NSFontAttributeNameというキーを使う。
そんでもって、このNSDictionaryを引数に取る-initWithString:attributes:でNSAttributedStringを作成すればOK。
ブランチ:ex2
// 24ポイントフォント使用
UIFont *font = [UIFont systemFontOfSize:24];
// NSAttributedString用の属性を用意
NSDictionary *attributes = @{
NSFontAttributeName:font
};
// 属性指定でNSAttributedStringを作成
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:string
attributes:attributes];
[attributedString drawInRect:self.bounds];
Runするとこんな感じ。

ちなみにフォント以外にもNSForegroundColorAttributeNameキーでテキスト色とか
ブランチ:ex3
// 文字に黄色指定
UIColor* textColor = [UIColor yellowColor];
// NSAttributedString用の属性を用意
NSDictionary *attributes = @{
NSFontAttributeName:font,
NSForegroundColorAttributeName:textColor
};
NSParagraphStyleAttributeNameキーで段落属性なんてのも指定できる。
文字の中央よせ配置なんかはこれで指定するわけだ。
ブランチ:ex4
// 段落設定
NSMutableParagraphStyle* paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.alignment = NSTextAlignmentCenter; // センタリング
paragraphStyle.minimumLineHeight = 40; // 行間40ポイント
// NSAttributedString用の属性を用意
NSDictionary *attributes = @{
NSFontAttributeName:font,
NSForegroundColorAttributeName:textColor,
NSParagraphStyleAttributeName:paragraphStyle
};
Runするとこんな感じ。

NSMutableAttributedStringにして、文字列の一部区間にだけ属性を適用させる事も可能。次のように5文字めから10文字分にだけ、これまでの属性を指定すると
NSMutableAttributedString* attributedString = [[NSMutableAttributedString alloc] initWithString:string];
NSRange range;
range.location = 5;
range.length = 10;
[attributedString addAttributes:attributes range:range];
次のようになります。

5や10がバイト数の指定ではなく、文字数の指定になってるところも注目ですな。便利。
んなわけでして…たいがいの事はNSAttributedStringで事足りるわけですよ。
ただし、さすがに縦書きは無理みたいで、その場合はNSAttributedString の下働きとして使われているCore Textの出番という事になる。
ということで、まずはCore Textを使うためにプロジェクトにCoreText.frameworkを追加します。ワンツースリー。

ふぉっぉおおおおおお。
これでフレームワーク選択画面が出るんで、あとはCoreText.frameworkを選んでAddボタンを押す。

ここまでできたらEDTextView.mでCoreText/CoreText.hをimportして-drawInRect:部をCore Text版に変更。
ブランチ:ex5
#import
#import "EDTextView.h"
・・・
- (void)drawRect:(CGRect)rect
{
NSAttributedString *attributedString = [[NSAttributedString alloc]
initWithString:string
attributes:attributes];
// CTFramesetter用意
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(
(__bridge CFAttributedStringRef)(attributedString));
// CTFramesetterを使い、文字列を指定した領域(今回はビューの矩形)に
// 配置させたCTFrameを作成する
UIBezierPath* path = [UIBezierPath bezierPathWithRect:self.bounds];
CTFrameRef frame = CTFramesetterCreateFrame(framesetter,
CFRangeMake(0, 0), path.CGPath, NULL);
// 描画用コンテキスト取り出し
CGContextRef context = UIGraphicsGetCurrentContext();
// 座標系の調整 数学で使う上方向にyが増加する座標系にする
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// テキスト用のアフィン変換用行列を初期化する
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
// 描画
CTFrameDraw(frame, context);
// 作成したCTFramesetterとCTFrameの所有権を放棄する
CFRelease(frame);
CFRelease(framesetter);
}
Runしても、NSAttributedStringの-drawInRect:と変わりありません。
ちゅ~か、たぶんNSAttributedStringの-drawInRect:は内部でこういうことやっていると思われ。
で、これにアレとアレ(ブランチ:ex6)を足すと~
ハート形に縦書き文字配置~。
ちゃんと禁則処理もしてくれてます。確認するために指で画面なでるとハートが拡大縮小して文字を配置しなおすようにしてみたお(ブランチ:ex7)。

お、おお~、おおぉ~う
ほらほら、ちゃんと"~"や"」"が先頭に来ないようにとか処理してます。
出版のプロの目だと話にならんのかもしれんが、素人目には十分ですわ。
もっとも、私のABプリントは住所が対象なので、英語同様スペース部以外では折り返したくないわけです。ちょっと工夫が必要になるんですが、そこらへんはこれからじっくりやりましょう。
CTFramesetterとは何なのか、そいつが作り出すCTFrameとは、以下次回!
待ちきれねーよな人は、ハート縮小拡大まで実装したサンプルプロジェクトをダウンロードして自習だ。
------------
サンプルプロジェクト:coretext.zip
ちなみに、ブランチ:ex1~ex7の各ステップ間のソース変更の差分はこうやって確認できます。

これで、選んだログ項目(リビジョンという)と、その1つ前のリビジョンの差分を表示する画面が現れる。

確認が終わったら、Donボタンで画面閉じます。
基本の編集画面に戻るにはShow the standard editorを選択ね。

各ex1~ex7までの段階を試したい場合は、Source Control→coretext master→Switch to Branchメニュー選んでください。
出てきた画面で、あらかじめ登録しておいたex1~ex7までの好きなブランチ(分岐)地点を選んで、その状態にプロジェクトを変更できます。

Runすると、その地点での処理が試せます。
最新状態に戻すには、さっきと同じくSource Control→core text {最後に選んだブランチ名}→Switch to Branchメニューを選んで、今度は出てきた画面で、masterを選びます。

これで最新状態に戻る。
途中で紹介したNSMutableAttributedStringを使う版は無いのであしからず。
以上ったら以上!