HMDT Blog | ページ 32

『列車図鑑 九州新幹線 新800系』発売


株式会社アリギリスさんより、iPad/iPhoneアプリ『列車図鑑 九州新幹線 新800系』が発売となっております。開発は、HMDTっす。

このアプリは、九州新幹線新800系をなめるように眺めることのできるもの。鉄道写真を撮らせたら並ぶものなしの、マシマ・レイルウェイ・ピクチャーズさんの撮り下し写真を収録。特殊技術で作った超ロングスーパーパノラマ写真で、全車両をつなげて見れちゃう。解説文やコラムはきしゃ旅フォトライターの松尾定行氏によるもの。写真をタッチすることで、新800系に隠された秘密をのぞけちゃうという、iPadならではギミックも搭載。

HMDT Webページの方でも紹介ページを作ったので、こちらも参考にしてね。

ということで、鉄道好きの方にはたまらない一品となっております。定価600円のところ、2011年9月30日までは発売記念セールで350円に。興味のある方は、ぜひご確認を。

HMDTではこんな感じの電子書籍も作っております。ePubやPDFだけが電子書籍ではない。書籍とアプリを合わせたような、新しいメディアを作りたい、って考えています。興味のあるコンテンツホルダーの方、一緒に何かやりませんか。

アップルジャパン解散


Macお宝鑑定団blog(羅針盤)によれば、アップルジャパン株式会社を解散して、Apple Japan合同会社と合併するとのこと。

日本の会社としてのアップルジャパンはなくなって、アメリカ本社直轄の一部署みたいになるってことかな?

うちの会社は、Apple製品で動くアプリを開発する関係上、たまにアップルジャパンの人たちに会ったりしてた。主に、サードパーティの開発社担当の方だね。お会いする方たちは悪い人じゃなくて、むしろとてもいい人たちなんだけど。いざ仕事の話となると、んまー、歯切れが悪くてね。結局何の成果も得られないことが多いから、アップルジャパンなくなっちゃえばいいのに、ってよく冗談半分に言ってた。そしたら、ほんとになくなっちゃった。

開発者としては、サードパーティの開発サポートがどうなるのかが気になる。個人的には本当は、クパチーノの開発部隊と強いコネクションを持ったアメリカ人が、日本に常駐してくれる形になると嬉しいんだけど。結局なにも変わりませんでした、ってのがオチかな。

UIViewControllerによるUIViewの回転 その2


前回に続き、UIViewControllerの話。今回は、親ビューとの関係を。

UIViewControllerを使って回転させると、管理しているビューのboundsとtransformが変化する。となると次は、親ビューでの位置を表すframeが気になってくる。だけど、UIViewのドキュメントによると、transformが単位行列以外の場合、frameの値は不定(undefined)ということになっている。だから、centerの値から考察するのが正しい。

ということで、ビューの回転とboundsおよびcenterの値を調べてみた。ウインドウの上に直接UIViewControllerを貼付けて、4方向に回転させる。結果はこんな感じ。

うーん?分かったような分からんような。

ポイントその1。向きによってboundsの値は同じであっても、centerの値は異なる。縦の正位置と逆さま位置、および横の右向き左向きは、それぞれboundsの値は同じだけどcenterの値が異なる。

ポイントその2。縦と横でcenterのずれる値の座標が違う。縦だとy座標がずれて、横だとx座標がずれる。

なんでこんなことが起きているかというと、boundsは子ビューの座標系で測るのに対して、centerは親ビューの座標系での値になるからだ。つまり、boundsはtransformの影響を受けるが、centerは受けない。親ビューの座標系では、デバイスがどんだけ回転しようと、デバイス左上が原点だ。

さらに話をややこしくしているのが、ステータスバーの存在。デバイスを回転すると、ビューが回転するけど、ステータスバーも回転する。ということは、その分centerは移動しなくてはいけない。

ということで、それぞれの向きの位置情報について、もう少し突っ込んで記述すると、こんな感じになる。

これで、boundsとcenterの関係が分かったでしょ。デバイスの回転とともにステータスバーも回転するんで、ステータスバーの高さである20ピクセルのずれが発生している。左上原点のところにステータスバーが存在するかどうかがポイントだね。あと、この図で見るとまるでcenterが回転しているみたいだけど、こっちじゃないからな。回転しているのはあくまでboundsの方だぞ。

UIViewControllerによるUIViewの回転 その1


iPhoneが登場したとき話題をさらったのは、デバイスを回転させるとそれに追随して中の画面も回転すること。いまとなっては当たり前のような機能だけど、登場した当時は魔法のように見えたものだった。

あまりに衝撃的で、かつ直感的で自然に見えたため、まるで回転に対応するアプリを作るのはとても簡単だと思われることもある。でもねー、かなり面倒くさいんだよね、これ。単純な話として、縦画面と横画面を作らなくてはいけないので、手間が倍になる。それはしょうがないからがんばるとして、どうやって回転を検知して、どうやって回転に対応する描画を行うか、というのが思ったよりも複雑だ。

ということで、回転にまつわるアレコレをまとめてみた。先に結論を言うと、回転に対応するただ一つの正解は存在しないみたいなので、アプリの仕様に応じていろいろと使い分けが必要になると思う。道具立てを紹介することは出来る。でも、どれを使うかはあなた次第。

では、回転をサポートする、もっとも簡単な方法から紹介していこう。それはもちろん、UIViewControllerを使うこと。UIViewControllerには、shouldAutorotateToInterfaceOrientation:というメソッドがある。これを上書きしてYESを返すようにすれば、それで回転することになる。

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation
{
    return YES;
}

実装の仕方によって、縦画面のサポートや横画面だけのサポート、またはあるタイミングでは回転を許して、あるタイミングでは許さない、ということも実現できる。

回転が発生すると、willRotateToInterfaceOrientation:duration:、willAnimateRotationToInterfaceOrientation:duration:、didRotateFromInterfaceOrientation:といったメソッドが順次呼び出される。回転時に特別なアニメーションを行いたいときは、これらの中で対応する。

というのが、簡単な使い方。

さて。もう少し突っ込んでみよう。そもそも、「回転」とは何だ?「回転が発生する」とは、いったい何が起きたことを意味するのだ?

答えは、UIViewの座標系が変化した、と言うことが出来る。UIViewには、大きさを表すbounds、アフィン変換を表すtransformといったプロパティがある。これらが変化することで、回転が実現されるのだ。UIViewControllerの場合、管理しているviewプロパティに対して、これらの属性をアニメーションとともに変化させることになる。

実験してみよう。UIViewContorllerにある回転イベントを通知するメソッドで、ビューのframe、bounds、transformといった値を表示させてみる。こんな感じのソースコード。

- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)orientation
        duration:(NSTimeInterval)duration
{
NSLog(@"### willRotateToInterfaceOrientation:duration ###");
NSLog(@"frame %@", NSStringFromCGRect(self.view.frame));
NSLog(@"bounds %@", NSStringFromCGRect(self.view.bounds));
NSLog(@"transform %@", NSStringFromCGAffineTransform(self.view.transform));
}

- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)orientation
        duration:(NSTimeInterval)duration
{
NSLog(@"### willAnimateRotationToInterfaceOrientation:duration: ###");
NSLog(@"frame %@", NSStringFromCGRect(self.view.frame));
NSLog(@"bounds %@", NSStringFromCGRect(self.view.bounds));
NSLog(@"transform %@", NSStringFromCGAffineTransform(self.view.transform));
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)orientation
{
NSLog(@"### didRotateFromInterfaceOrientation: ###");
NSLog(@"frame %@", NSStringFromCGRect(self.view.frame));
NSLog(@"bounds %@", NSStringFromCGRect(self.view.bounds));
NSLog(@"transform %@", NSStringFromCGAffineTransform(self.view.transform));
}

結果はこんな感じ。

(縦から右横に回転)
### willRotateToInterfaceOrientation:duration ###
frame {{0, 20}, {320, 460}}
ounds {{0, 0}, {320, 460}}
transform [1, 0, 0, 1, 0, 0]
### willAnimateRotationToInterfaceOrientation:duration: ###
frame {{20, 0}, {300, 480}}
bounds {{0, 0}, {480, 300}}
transform [0, -1, 1, 0, 0, 0]
### didRotateFromInterfaceOrientation: ###
frame {{20, 0}, {300, 480}}
bounds {{0, 0}, {480, 300}}
transform [0, -1, 1, 0, 0, 0]

(右横から逆さまの縦に回転)
### willRotateToInterfaceOrientation:duration ###
frame {{20, 0}, {300, 480}}
bounds {{0, 0}, {480, 300}}
transform [0, -1, 1, 0, 0, 0]
### willAnimateRotationToInterfaceOrientation:duration: ###
frame {{0, 0}, {320, 460}}
bounds {{0, 0}, {320, 460}}
transform [-1, 0, -0, -1, 0, 0]
### didRotateFromInterfaceOrientation: ###
frame {{0, 0}, {320, 460}}
bounds {{0, 0}, {320, 460}}
transform [-1, 0, -0, -1, 0, 0]

まずは、transformに注目。最初は[1 0][0 1]。つまり、単位行列(identity)だ。次に[0 -1][1 0]。そして、[-1 0][0 -1]と、変化してく。つまり、90度ずつ回転していっているわけだね。この辺の数学的な話は省略。

続いてboundsを見てみる。最初は{320, 460}。次に{480, 300}。そして{320, 460}。これも、回転した画面の大きさにあわせて変化していっていることが分かる。

つまり、ビューの回転とは、boundsとtransformが変化した、ってことだ。

frameと、親ビューとの関係については、次回。

MoneyTronのDeveloper’s Voiceを公開


HMDTで開発したアプリの、開発者の生の声を届けるコーナー、Developer’s Voice。今日は、MoneyTronのDeveloper’s Voiceを公開。

MoneyTronは、お小遣い帳アプリ。iPhoneアプリらしい、奇麗でクールで特徴的なユーザインタフェースが目をひく。結構人気があって、売れているんですよ、これ。

Developer’s Voiceでは、MoneyTronの企画/デザインを行った、webtronの大宮さんをご招待。MoneyTronを皮切りに、デザイン論を熱く語っております。

アプリの審査でリジェクトされてイラッときたこと


久しぶりに、Appleの審査でリジェクトくらってイラッときたこと。

あるアプリで、iOS 5対応を進めていた。iOS 5のAPIを一部使っている。でもiOS 4でも動かしたいんで、respondsToSelector:を使って動作環境によって切り分けるようにしていた。

で、完成して申請したんだけど、とうぜんまだiOS 5のやつは受け付けられていないんで、iOS 4 SDKでビルドして出した。そしたら、non public APIを使っているって言われて、えぇ?と思って確認したら、そのiOS 5のAPIのことだった。どうやら、そのAPIと同じ名前のものがprivate APIとしてあったらしい。

ムカチーン。なにがイラつくって、こっちは当然のプログラミングをしている。iOS 5登場が近いからその対応をしている。iOS 4でもiOS 5でも動くようにしている。private APIは、iOS 5まで含めれば、使っていない。そもそもそのAPI、iOS 4上の実際の動作では呼び出されない。

それを機械的にツールかなにかで判定して、private使っているから申請し直せって。その、アプリの動作や作りをまったく無視した審査が気に食わない。private API、使ってないっつーの。

そんなに審査したいんだったら、すべてのAPI呼び出しをチェックできるObjective-Cランタイムを用意して、その上でアプリを動作させて、本当にprivate APIを呼び出しているかどうか確認しろ。それなら許す。

属性付きラベルを作る その3


前回に続き、属性付き文字列を表示する話。

属性付き文字列を表示したら、次はそれをタッチしたくなるのが人情というものだな。えーっと、想定しているのは、文中にリンクを埋め込みたい、ということね。文章の一部を青字、または下線付きで表示する事で、リンクがあることを表す。ユーザがそれをタップしたら、情報を表示したり、別のページへジャンプする。ってことをやりたいよね。ということで、タッチイベントを拾ってみよう。

タッチイベントを受け取ること自体は簡単だ。いま作っているラベルはUILabelのサブクラスなので、touchesEnded:withEvent:を上書きすればいい。問題は、タッチしたのが文字列のどの部分なのかを特定する事だ。これを実現するには、Core TextのAPIを使って、文字列の位置情報を特定していく必要がある。

いまのところ、CTFrameというオブジェクトでテキストの描画を行っている。ここから、まず始めに、行の情報を取り出そう。CTLineというオブジェクトが行を表す。CTFrameからは、CTLineの配列と、それぞれの行の原点座標を取り出す事ができる。

CTLineからは、その行が持つタイポグラフィの情報を取得できる。ascentとかdescentとか、そういうやつ。余談だけど、ascentをアセント、descentをディセント、ってカタカナで書くと、何の事だか分かんなくなるな。アルファベットの方が理解しやすいと思う。これで、CTLineの領域が分かる。したがって、どの行をタッチしたか分かる事になる。そしてさらに、CTLineに対して座標を指定すると、テキストの何文字目にあたるかが分かる。これで、タッチした文字まで特定できるという訳だ。

以上が理屈。では、これをもとに実装をしてみよう。

- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event
{
    // Get touch point
    UITouch*    touch;
    CGPoint     point;
    touch = [touches anyObject];
    point = [touch locationInView:self];
    point.y = CGRectGetHeight(self.bounds) - point.y;

    // Get lines
    CFArrayRef  lines;
    lines = CTFrameGetLines(_ctFrame);

    // Get line origins
    CGPoint*    origins;
    origins = malloc(sizeof(CGPoint) * CFArrayGetCount(lines));
    CTFrameGetLineOrigins(_ctFrame, CFRangeMake(0, CFArrayGetCount(lines)), origins);

    int i;
    for (i = 0; i < CFArrayGetCount(lines); i++) {
        // Get line
        CTLineRef   line;
        line = CFArrayGetValueAtIndex(lines, i);

        // Get origin
        CGPoint origin;
        origin = *(origins + i);

        // Get typographics bounds
        float   ascent;
        float   descent;
        float   leading;
        double  width;
        width = CTLineGetTypographicBounds(line, &ascent, &descent, &leading);

        // Decide line frame
        CGRect  lineFrame;
        lineFrame.origin.x = origin.x;
        lineFrame.origin.y = origin.y - descent;
        lineFrame.size.width = width;
        lineFrame.size.height = ascent + descent;

        // Check with point
        if (CGRectContainsPoint(lineFrame, point)) {
            // Get index for position
            CFIndex index;
            index = CTLineGetStringIndexForPosition(line, point);
            if (index == kCFNotFound) {
                continue;
            }

            // Get attributes at position
            NSDictionary*   attrs;
            NSRange         range;
            attrs = [_attrStr attributesAtIndex:index effectiveRange:&range];

            // Notify to delegate
            if ([_delegate respondsToSelector:
                    @selector(attributedLabelTouchedAtIndex:attributes:effectiveRange:)])
            {
                [_delegate attributedLabelTouchedAtIndex:index 
                        attributes:attrs effectiveRange:range];
            }
        }
    }

    // Release objects
    if (origins) {
        free(origins), origins = NULL;
    }

    // Invoke super
    [super touchesEnded:touches withEvent:event];
}

CTFrameからCTLineの配列を取り出すのは、CTFrameGetLinesという関数だ。さらに、行の原点座標を取り出すのは、CTFrameGetLineOriginsという関数。こちらはCGPointの配列領域をあらかじめ確保しておく必要がある。

CTLineのタイポグラフィ情報は、CTLineGetTypographicBoundsで取得する。これを使うと、ascent、descent、leading、そして行の長さを知る事ができる。これらの情報と原点座標を組み合わせて、行のframeを計算する。タッチした座標から文字のインデックスを取り出すのは、CTLineGetStringIndexForPosition。これで、どの文字がタッチされたかが分かった。

タッチされたという情報は、デリゲートに通知する事にした。タッチした文字のインデックスに加えて、属性の情報も送ってやる。その文字がどんな属性を持っているかと、どの範囲までそれが続いているか、という情報だ。

これでできた!試しに動かしてみると、こんな感じ。文字列をタッチすると、その文字と属性とを取り出せているのが分かる。取り出した情報を元に、リンクだったら関連する動作をさせればいい。

ここまでのソースコード:AttrLabel-3.zip

属性付きラベルを作る その2


前回の続き。Cocoa touchで属性付き文字列を表示するラベルを作ろう。

今回は、属性を追加するためのメソッドを追加してみる。太字の属性。それと、文字の前景色の属性。

これらの設定を行うために、専用のメソッドを用意してみた。addBoldFontAttrWithRange:と、addForegroundColorAttrWithColor:range:だ。範囲と、前景色の場合は色とを指定する。

// Attributes
- (void)addBoldFontAttrWithRange:(NSRange)range;
- (void)addForegroundColorAttrWithColor:(UIColor*)color range:(NSRange)range;

これらのメソッドの実装は、指定した範囲と色をインスタンス変数に格納するだけ。実際に文字列に属性を付加するのは、前回説明した_refreshAttributedStringメソッドの中になる。

では、その拡張された実装を。最初の部分は変化なし。フォント属性の追加のところで、太字属性に関する記述が新たに追加されている。

    ...

    //
    // Set font attribute
    //

    // Set normal font attribute
    CTFontRef   ctFont;
    ctFont = CTFontCreateWithName(
            (CFStringRef)self.font.fontName,
            self.font.pointSize,
            NULL);
    [_attrStr addAttribute:(NSString*)kCTFontAttributeName
            value:(id)ctFont range:NSMakeRange(0, length)];

    // Get bold font ranges
    UIFont*     boldFont;
    CTFontRef   ctBoldFont;
    boldFont = [UIFont boldSystemFontOfSize:12.0f];
    ctBoldFont = CTFontCreateWithName(
            (CFStringRef)boldFont.fontName, self.font.pointSize, NULL);
    for (NSValue* boldFontRangeValue in _boldFontRanges) {
        // Add bold font
        NSRange boldFontRange;
        boldFontRange = [boldFontRangeValue rangeValue];
        if (NSMaxRange(boldFontRange) <= length) {
            [_attrStr addAttribute:(NSString*)kCTFontAttributeName
                    value:(id)ctBoldFont range:boldFontRange];
        }
    }

    // Release font
    if (ctFont) {
        CFRelease(ctFont), ctFont = NULL;
    }
    if (ctBoldFont) {
        CFRelease(ctBoldFont), ctBoldFont = NULL;
    }

    ...

太字設定は、まず太字用のCTFontオブジェクトを作る。今回は、システムのボールドフォントからフォント名を取得した。インスタンス変数の_boldFontRangesという配列に太字を設定する範囲を格納している。そこから範囲を取り出して、addAttribute:value:range:メソッドを使って属性を追加している。

続いて、前景色のための実装。

    ...

    //
    // Set foreground color attribute
    //

    // Set normal body forgrond color
    UIColor*    bodyForegroundColor;
    if (self.highlighted) {
        bodyForegroundColor = self.highlightedTextColor;
    }
    else {
        bodyForegroundColor = self.textColor;
    }
    [_attrStr addAttribute:(NSString*)kCTForegroundColorAttributeName
            value:(id)bodyForegroundColor.CGColor range:NSMakeRange(0, length)];

    // Get foreground colors
    int i;
    for (i = 0; i < [_foregroundColors count]; i++) {
        // Get foreground color and range
        UIColor*    foregroundColor;
        NSRange     foregroundColorRange;
        foregroundColor = [_foregroundColors objectAtIndex:i];
        foregroundColorRange = [[_foregroundColorRanges objectAtIndex:i] rangeValue];

        // Add foreground color attribute
        if (NSMaxRange(foregroundColorRange) <= length) {
            [_attrStr addAttribute:(NSString*)kCTForegroundColorAttributeName
                    value:(id)foregroundColor.CGColor range:foregroundColorRange];
        }
    }

    ...

基本的な流れは、フォントの設定のときと同じだ。色の属性は、CGColorオブジェクトとして設定することに注意。

新たに追加されたメソッドは、次のように呼び出す。

    // Set text
    _attrLabel.text = @"これは太字や色付き文字が表示できるラベルです。";

    // Add attributes
    [_attrLabel addBoldFontAttrWithRange:NSMakeRange(3, 2)];
    [_attrLabel addForegroundColorAttrWithColor:[UIColor blueColor]
            range:NSMakeRange(6, 5)];

で、実行結果がこれ。

確かに属性付き文字列が表示された!これで便利なラベルの出来上がり。

ここまでのソースコード:AttrLabel-2.zip

属性付きラベルを作る その1


iOSのCocoa touchにはUILabelというクラスがある。ご承知の通り、画面上に文字をラベルとして表示するためのクラスだ。簡単に使えて便利なんだけど、単純な表示しかできない。単一フォントで単一サイズの表示しかできない。

プログラミングを進めていくと、当然の事ながら複数のフォントやサイズが混在した、リッチテキストを表示したい、という要求が出てくる。リッチテキストを取り扱えるクラスとしては、HTMLを表示するUIWebViewがある。まぁ、大きいテキスト表示するならそれでいいんだけど、一行とか二行だけ表示したい、ってときもあるよね。または、UITableViewCellの中で表示したいとか。セルにUIWebViewを埋め込むのはいかにもおおげさだ。

つまり、属性付き文字列を表示できるUILabelのようなクラスが欲しい訳だ。ないものは作るべし!ということで、作ってみた。

まずは、どうやってリッチテキストを描画するか?ってことを考えよう。これにはCore Textを使う。Core Textは、テキスト描画のための低レベルのフレームワークだ。リッチテキストの描画機能もこれに含まれる。使えるのはiOS 3.2以降。これを使って描画する、UILabelのサブクラスを作ろう。

AttributedLabelというクラス名にする。クラスの宣言は、こんな感じ。

@interface AttributedLabel : UILabel
{
    NSMutableAttributedString* _attrStr;
    CTFrameRef                 _ctFrame;
    BOOL                       _needsRefreshAttrStr;
}

@end

NSMutableAttributedStringっていうのが、属性付き文字列を取り扱うためのクラス。便利そうな名前のクラスであるが、こいつはそのままでは描画ができず、Core Textと組み合わせて使う必要がある。

その次にあるCTFrameというのが、Core Textのオブジェクト。画面にテキストの描画を行うには、こいつを使う。

実装の方では、まず属性付き文字列を作らなくてはいけない。そのために、_refreshAttributedStringというメソッドを作る。

- (void)_refreshAttributedString
{
    NSDictionary*   attrDict;

    // Release old attributed string and frame
    [_attrStr release], _attrStr = nil;
    if (_ctFrame) {
        CFRelease(_ctFrame), _ctFrame = NULL;
    }

    // Create attributed string
    _attrStr = [[NSMutableAttributedString alloc]
            initWithString:self.text];

    // Get length
    int length;
    length = [_attrStr length];

    //
    // Set font attribute
    //

    // Set normal font attribute
    CTFontRef   ctFont;
    ctFont = CTFontCreateWithName(
            (CFStringRef)self.font.fontName,
            self.font.pointSize,
            NULL);
    attrDict = [NSDictionary dictionaryWithObjectsAndKeys:
            (id)ctFont, (NSString*)kCTFontAttributeName,
            nil];
    [_attrStr setAttributes:attrDict range:NSMakeRange(0, length)];

    // Release font
    if (ctFont) {
        CFRelease(ctFont), ctFont = NULL;
    }

    //
    // Create frame
    //

    // Create frame
    CGMutablePathRef    path;
    CTFramesetterRef    framesetter;
    path = CGPathCreateMutable();
    CGPathAddRect(path, NULL, self.bounds);
    framesetter = CTFramesetterCreateWithAttributedString(
            (CFMutableAttributedStringRef)_attrStr);
    _ctFrame = CTFramesetterCreateFrame(
            framesetter, CFRangeMake(0, length), path, NULL);
    CGPathRelease(path);

    // Clear flag
    _needsRefreshAttrStr = NO;
}

このメソッドでは、まず古いオブジェクトを破棄して、新しいNSMutableAttributedStringオブジェクトを作る。そして、これに属性を付加していく。とりあえず、フォント属性を指定してみた。ここで注意してほしいのは、フォントの指定にはCTFontを使う必要があること。そして属性辞書を作るんだけど、このとき指定するキーもCore Textで用意されているものを使うこと。これらに注意。

最後にCTFrameを作る。まず始めに、CGFramesetterを作る。CTFramesetterCreateWithAttributedStringという関数があるので、これを使う。これで属性付き文字列に紐付けられたCGFramesetterができる。これに、さらに描画領域を表すパスを紐付けるためにCTFramesetterCreateFrame関数を使い、CTFrameを作る。これで、描画準備完了だ。

あとは、drawRect:で描画してやればいい。

- (void)drawRect:(CGRect)rect
{
    // Refresh attributed string
    if (_needsRefreshAttrStr) {
        [self _refreshAttributedString];
    }

    // Get current context
    CGContextRef    context;
    context = UIGraphicsGetCurrentContext();

    // Get bounds
    CGRect  bounds;
    bounds = self.bounds;

    // Save context
    CGContextSaveGState(context);

    // Flip context
    CGContextTranslateCTM(context, 0, CGRectGetHeight(bounds));
    CGContextScaleCTM(context, 1.0f, -1.0f);

    // Draw frame
    CTFrameDraw(_ctFrame, context);

    // Restore context
    CGContextRestoreGState(context);
}

描画の前に、コンテキストをひっくり返してやる。Core Textは左下原点なんでね。そのまま書くと天地が逆転した文字列が描かれる。そして、CGFrameDrawを呼ぶ事で、画面に描画される。

まずは、普通に文字列を描いてみた。次回は、いろいろと属性を付加してみる。

ここまでのソースコードAttrLabel-1.zip

uListeningのDeveloper’s Voiceを公開


HMDTで開発した製品の秘話を語る「Developer’s Voice」。今回新たに、「uListening」のDeveloper’s Voiceを公開。

uListeningは、英会話教材の第一人者アルクさんと一緒にやらせてもらっているアプリ。リスニング機能をメインに、iPhoneでの英会話学習をサポートするよ。

Developer’s Voiceでは、担当開発者がこだわりの機能の数々を語っております。ぜひ、ご一読を。