2009年8月23日日曜日

vimのProjectプラグインまとめ

今回のPython Hackathonで大活躍してくれたProjectプラグインについて適当にまとめ。


■プラグインのダウンロードはこちらから
http://www.vim.org/scripts/script.php?script_id=69


■まずはこれを読む
http://d.hatena.ne.jp/hidde/20080930/1222776171
http://d.hatena.ne.jp/hirono_hideki/20090816/1250365295
http://blog.kaihatsubu.com/archives/001438.html


■覚えておかなくてはならないコマンド
\c 対話的に新しいディレクトリをプロジェクトに追加する。
\C \cと同じだが、指定したディレクトリ以下のすべてのディレクトリを再帰的にプロジェクトに追加する。
\r 現在カーソルがあるディレクトリの中身を再度読み込みなおす。プロジェクトの設定を変更したときやファイルを追加したときなどに実行する。
\R \rと同じだが、指定したディレクトリ以下のすべてのディレクトリを再帰的に読み込みなおす。
\w 現在開いているファイルを閉じる
\W すべての開いているファイルを閉じる
Space プロジェクトウィンドウを最大化する。もう一度押すといつものサイズに戻る。
以上、これだけ!


■使うときのコツ
\Cや\Rなどを使って新しいファイルをプロジェクトに取り込んだら、プロジェクト自体を:wして保存するのを忘れないようにする。

Python Hackathon #1に参加してきました

http://atnd.org/events/649

Python Hackathon #1に参加してきました。主催はいつものごとくvの人。しかしながら幸いにして雨は降りませんでした。


■以下、適当にダイジェスト
会場がいつもの四ッ谷健保会館ではなく、大久保の健保会館だったため場所を間違える人が続出。
今回のハンズオンは、
  • id:tokibitoのパーフェクトdjango教室
  • Google App Engineハンズオン Kayを使ってみよう
  • Pythonソースコード読書会
の3本立てだったのですが、Pythonソースコード読書会が参加者0人のためあえなく中止><

私はというと社内ブログの投稿順序を記録して利マインドメール投げたりするためのアプリをひたすら書いてました。が、前日夜遅くまで東方をプレイしていたせいで半分意識がもうろう・・・
気がついたらもう18時になって終わっていた感じです。PyPyとかwxPythonとかPyQtとかjQueryのカスタムイベントの話とかをしていたそうなのですが、意識が無くほとんど聞けずちょっと後悔。しかし後悔先に立たず・・・

18時半から毎度おなじみ@moriyoshiのすべらない話。

(音小さくてすみません、iPhone 3GSの限界)
相変わらず面白いなぁ。しかし本人曰く「納得いかない、やり直したい」とのこと。凄いw


■懇親会
普段は懇親会お断りしているのですが、今回は禁酒の上で懇親会に参加してみました。
http://www.yamachan.co.jp/index.html
場所は大久保のこの店。この手羽がまた美味しい!普段は飲み会など参加しても3分の1人前も食べない私ですが、今回は思わず一人前完食。話も盛り上がってなかなか充実したひとときでした。


■はんせい
  • 前日はちゃんと寝ておこう
  • ソースコード書いてばっかりないでもっと話そう
逆に良かった点としては、ちゃんと必要なライブラリとか実行環境とかを用意してきたためスムーズに作業に入れたところと、懇親会に参加したところかな。
当日のTwitterで見られたつぶやきのうちに、「話しかけるきっかけがないまま終わってしまった」というものがありましたが、納得。この手の勉強会は勉強会の最中よりも懇親会の方が話が弾んで名刺の交換もはかどるので、懇親会には出た方がお得ですね。勉強会の最中は相手もコード書いてたりでなかなか話しかけづらい雰囲気なんですよねー。その点、id:mopemopeさんとかは気さくに参加者に話しかけてくださってて凄いと思います。一見しゃべってるばかりで何もしてなさそうなのですが、話しかけてこの勉強会の緩い雰囲気を保ってくださっているんだなぁと感心。自分ももっともっと話していきたいです><

2009年8月10日月曜日

cocos2dのドキュメントをdoxygenで生成してみました




cocos2dの公式ブログに、ドキュメントをdoxygenでビルドしてXcodeのドキュメントビューワで見られるようにする方法が記載されていたので試してみました。
http://www.cocos2d-iphone.org/archives/358

■前準備
  • cocos2d for iPhone 0.8をダウンロードしておく。
  • doxygenをインストールする。どこからでもかまいませんが、私はMacPortsを使ってインストールしました。

■それでは早速doxygenでビルドしてみましょう
ダウンロードしてきたcocos2d for iPhoneの、cocos2d-iphone.xcodeprojをXcode開きます。開いたらターゲットからcocos2d-documentationを選択し「情報を見る」をクリックします。「ビルド」タブの中を調べると、上記の画像のようにDOXYGEN_PATHという項目がありますので、そこを自分の環境に合ったdoxygenへのパスに変更します。私の場合は/opt/local/bin/doxygenです。あとはこのcocosed-documentationをビルドするだけです。全部自動的にやってくれます。


■おまけに学んだこと
作成されたドキュメントは以下のパスに配置されるみたいです。
~/Library/Developer/Shared/Documentation/DocSets/
つまり、ここにDocSet形式のファイルを置けば自由にドキュメントをXcodeに追加することができるってわけですね。自分のプロジェクト用のドキュメントをdoxygenで生成したり出来そうです。

何でもいいから作ってみようと思って作った結果がこれだよ!




製作期間二日。この程度の物に二日とか許されるのは小学生までよねー・・・orz
一応自機も動くしあたり判定もあるしゲームオーバーにもなるよ!すごいよcocos2d!



以下、全然関係ないけど設計のお話です。

(2009/08/30追記) 以下の内容に従って作ってみた結果がこちら:http://akisute.com/2009/08/blog-post_29.html
見ての通り大失敗してます。やっぱり継承して作った方がうまくいくっぽいです・・・


Javaの継承システムでは、サブクラスはスーパークラスのコンストラクタを引き継がない。また、暗黙的にスーパークラスのデフォルトコンストラクタを最初に呼びだすことになっている。だから、これはコンパイルが通らないし、
class Parent {
    private String name;
    public Parent(String name) {
        this.name = name;
    }
}

class Child extends Parent {
    int age;
    public Child(int age) {
        // デフォルトコンストラクタがないし、明示的にSuper(String)を呼んでいない
        this.age = age;
    }
}
これもコンパイルが通らない。
class Parent {
    private String name;
    public Parent(String name) {
        this.name = name;
    }
}

class Child extends Parent {
    int age;
    public Child(int age) {
        super("child");
        this.age = age;
    }
}

public static void main(String[] args) {
    // ChildにはSuperのコンストラクタが継承されないのでこれはできない
    Child child = new Child("akisute");
}
ところがObjective-Cの継承システムでは、サブクラスがスーパークラスのイニシャライザをすべて受け継いでしまう。なぜならイニシャライザも何の変哲もないタダのメソッドだから。だから、こんなコードが書けてしまう。
@interface Parent : NSObject {
    NSString *name;
}
@end

@interface Child : Parent {
    int age;
}
@end

@implementation Parent
    - (id)initWithName:(NSString *)aName
    {
       if (self = [super init])
       {
           name = aName;
       }
       return self;
    }
@end

@implementation Child
    - (id)initWithAge:(int)anAge
    {
        // Parentにはinitが無いが、NSObjectにはあるので
       if (self = [super init])
       {
           age = anAge;
       }
       return self;
    }
@end

int main (int argc, char const *argv[])
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    // Childには無いinitWithNameが呼び出せてしまう
    Child *child = [[[Child alloc] initWithName:@"akisute"] autorelease];
    NSLog(@"@%", child);
    [pool release];
    return 0;
}
で、何が一体問題になるのかというと、この仕様のせい(おかげ?)で安易な継承が出来なくなっている。たとえば今回のcocos2dを使ったゲームでは、キャラは全部Spriteのサブクラスにしたかったんだけれども、そうするとSpriteクラスに最初から用意されているイニシャライザ(initWithFile:とかinitWithTexture:とか)が外に漏れてしまう。しかもこいつらを呼びだされると本来自分の作ったキャラクラスで呼びだしたかったイニシャライザが回避されて、任意の画像でへんちくりんなキャラを作られてしまう危険性がでてしまう。そこで、今回はこういうふうに継承ではなくてコンポジットを使ったコードにした。
@interface MSObject : NSObject {
    /** Sprite for this object. Can be changed to AtlasSprite for further performance. */
    Sprite *sprite;
    /** Hit box size */
    CGSize hitBoxSize;
}
後から考えたらこの方が正解だった。だって、こうしておけば外へ公開するAPIを変化させずに、中身のSpriteをよりパフォーマンスの良いAtlasSpriteに後からいくらでも差し替えられる!

2009年8月8日土曜日

cocos2d細かいところメモ

cocos2dを使っていて適当に気づいたところとかメモしてみます。

■ログ出力
CCLOGというマクロccMacros.hで定義されています。
#ifdef DEBUG
#define CCLOG(...) NSLog(__VA_ARGS__)
#else
#define CCLOG(...) do {} while (0)
#endif
注意点としてDEBUGがdefineされていないと使えません。


■ベクトル演算とか角度変換とか
CGPointを拡張してベクトル演算をするためのメソッドが追加されています。なかなか便利です。たとえばこんな感じ。
    // 二つのベクトルのdot(内積)とlength(ベクトルの長さ)を計算してcosθを求める
float dot = ccpDot(lastAccerelometerVector, convertedVector);
float a = ccpLength(lastAccerelometerVector);
float b = ccpLength(convertedVector);
float cosTheta = dot / (a*b); // 注意:a*bが0だとゼロ除算で死にます。真似しないでね><
// 角度→ラジアン変換用のマクロ。逆ももちろんあります
float threshold = cos(CC_DEGREES_TO_RADIANS(120.0));
Chipmunkにも同様のメソッドがあります。お好きな方をご利用いただけますが、個人的には全部cocos2dでやる方が好きです。唯一の問題は、こんなメソッドを使っているとベクトル内積の計算式を忘れます。


■音を出力する方法
最初からCocoa Touchで用意されているAudio Queue Service, OpenAL, AVAudioPlayer, Audio Unitに加えて、さらにcocos2dについてくる音再生用ライブラリとして
  • CocosDenshionとSimpleAudioEngine
  • sound-engine
これだけたくさんの中から選択できます。今回は一番簡単なSimpleAudioEngineという奴を使ってみました。
        // prefetch sound resources
SimpleAudioEngine *audioEngine = [SimpleAudioEngine sharedEngine];
[audioEngine preloadEffect:@"bell.aif"];
[audioEngine preloadEffect:@"gong.aif"];
[audioEngine playEffect:@"gong.aif"];
たったのこれだけです。playEffectでサウンドエフェクトが、playBGMでBGMが再生できます。同時再生数とか使用可能なファイルタイプとか細かいところは不明ですが、そこそこの量が同時に出せるみたいです。
注意点としてSimpleAudioEngineはCocosDenshionを使用しています。CocosDenshionのライセンスはcocos2dと独立しており、こちらは年間2500ドル以上の利益が出ている場合には500ドルのライセンス料を徴収するとかなんとかそういう条項があります。(http://www.cocos2d-iphone.org/wiki/doku.php/cocosdenshion:licenseを参照)


今はこのcocos2dの上にどのような構造でアプリを組んでやろうかとか考えてます。・・・あー、こんなこと考えてるからいつまで経っても本題のアプリが完成しないんだー ><

2009年8月7日金曜日

cocos2d for iPhone Project Template v0.8 真似して作ってみました



cocos2d導入キット(http://d.hatena.ne.jp/Seasons/20090511/1241990196)としてid:Seasonsさんが公開されているキットを元に、私もcocos2d用のプロジェクトテンプレートを作ってみることにしました。id:Seasonsさん、すばらしいキットをありがとうございます!

今回の作業にあたり参考にしたページはこちら。
http://d.hatena.ne.jp/griffin-stewie/20090315/p1

作成したテンプレートはgitに公開いたしましたので、よろしければ使ってみてください。
http://github.com/akisute/cocos2d-xcode-template/tree/master


■インストール方法
downloadタブから0.8をダウンロードしてきて、解凍したファイルを以下のパスに配置してください。
~/Library/Application Support/Developer/Shared/Xcode/Project Templates
配置後、.gitと.gitignoreファイルを削除してください。削除しないと後からテンプレを使って新しいプロジェクトを作ったときに、これらのファイルがコピーされてしまいgitを使おうとしたときに問題が発生する可能性があります。


■テンプレートの内容
id:Seasonsさんが公開されている0.8beta用のテンプレートを元に、一部自分の気になった箇所を自分好みに修正しました。また、使用するcocos2dのバージョンを0.8betaから0.8リリース版に変更しました。一応ビルドして画面が出るところまでは確認しています。ただし、全機能をテストしたわけではないので、一部不具合があるかもしれません!
また注意点として、簡素化のため元のcocos2dや外部ライブラリに付随していたREADMEやドキュメント、LICENSEファイルなどをすべて削除してしまっています。おそらくは大丈夫だと思いますが、ライセンス的に問題が発生するかもしれません。大変申し訳ありませんが、このテンプレートを利用した際に生じる一切の不具合について当方では責任を負いきれませんのでご了承ください。


■id:Seasonsさんのテンプレートから変更したところ一覧
  1. 使用するcocos2dのバージョンを0.8リリース版にした。
  2. プロジェクトのグループ構成およびディレクトリ構成を変更した。この変更により、本来システムが利用する/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Project Templates/Application以下ではなく、ユーザーのライブラリである~/Library/Application Support/Developer/Shared/Xcode/Project Templates以下にテンプレートを配置しても動作するようになった。
  3. MenuSceneとGameSceneが最初のテンプレとして用意されていたのを統廃合し、MainSceneひとつだけにした。
  4. ビルドの設定を必要に応じて変更し、またGCC_PREPROCESSOR_DEFINITIONSにDEBUGを追加した。これにより最初からデバッグビルド時にCCLOGマクロが正しく機能する。
  5. ソースコードのインデントがまちまちになっていたのを、すべてApple式のインデントに修正。
  6. ソースコード中から日本語コメントをすべて除去し英語コメントに置換。
  7. 一部使用されていたdepreciatedメソッドを除去。[[Director sharedDirector ] setDeviceOrientation:CCDeviceOrientationLandscapeRight];を使うようにした。


■思わぬ収穫
この作業によってXcodeの仕組みとテンプレートの作り方、ビルドの仕方などにかなり詳しくなることができました。たとえば・・・
  • Xcodeのテンプレートファイルは単なる普通のXcodeプロジェクトと何ら変わらない。そのままビルドして実行することもできる。
    唯一異なる点が、xcodeprojバンドルの中に、TemplateIcon.icnsとTemplateInfo.plistというファイルが存在し、これらを変更することでテンプレートとしての設定を変更することが出来る点。
  • libhogehoge.a(静的ライブラリ?)の作り方。ターゲットに新規ターゲットを追加して、必要なソースをぽいぽい投げ込むだけ。antでbuild.xmlを書くより簡単。
  • ___PROJECTNAMEASIDENTIFIER___.pchの配置パスを変更したときは、ターゲットのビルド設定のPrefix Headerの値を書き換える必要がある
  • 同様に、___PROJECTNAMEASIDENTIFIER___-Info.plistのパスを変更したときも、ターゲットのビルド設定のInfo.plistファイルの値を書き換える必要がある
  • main.mは何処に置いておいても大丈夫らしい

ということで、Xcodeプロジェクトのテンプレートを作成するのは凄く勉強になります。皆さんも一度自分好みのテンプレートを作成してみてはいかがでしょうか?

2009年8月4日火曜日

cocos2d for iPhoneをためしてみました



YourTurnが一段落したので、今度は前々から一度やってみたかったゲームにチャレンジしてみようと思い、cocos2d for iPhone(http://code.google.com/p/cocos2d-iphone/)を使い始めました。cocos2d for iPhoneとは、iPhone上で簡単に2Dのゲームを作成するためのフレームワークです。FPS管理、アニメーション、パーティクル、シーンの管理およびアニメーション付きの切り替え、メニュー、タッチおよび加速度イベントの管理など、ゲームに必要な要素をほぼすべて網羅してくれています。さらにオプションとしてBGMやサウンドエフェクトの管理と、2D物理エンジンも用意されており、まさに至れり尽くせりといった感じです。

まずは手始めにチュートリアルをやってから、Tic Tac Toe(いわゆるタダのマルバツゲーム)を作ってみました。
ソース全体 http://github.com/akisute/Cocos2DTest/tree/master
ヘッダ部分のみ http://gist.github.com/160244
実装部分のみ http://gist.github.com/160246

この程度の内容のソースでパーティクルも作れるし音も鳴らせるしで、本当に簡単です。ここまで簡単だとRPGツクールより簡単かもしれませんw
今までゲームプログラミングの経験が全然無い人のとっかかり用としては最適ではないでしょうか?

2009年7月25日土曜日

Bloggerにretweetボタンを付ける方法

IDEA*IDEAさんで紹介されていた、こちらの記事(http://www.ideaxidea.com/archives/2009/07/retweet_js.html)を参考にして、私のBlogにも早速Retweetボタンを設置してみました。Bloggerでこのボタンを設置するにはちょっとだけ工夫が必要でしたので、私が行った設置方法を簡単にご紹介いたします。


■1.スクリプトを読み込ませる
まずは以下のスクリプトタグを、Bloggerテンプレートの<head>要素内に設置します。
<script src='http://ejohn.org/files/retweet.js'/>

■2.ボタンを配置する
スクリプトタグを置いたら、後はボタンを好きな場所に置くだけです。以下のソースを任意の場所にコピペしてあげてください。
<a class='retweet' expr:href='data:post.url' expr:title='data:title + ": "+ data:post.title' />
たったのこれだけでretweetボタンを配置することができます。いやー便利。


■トラブルシューティング
(1) もし上記のソースをコピペしても動かない!という場合には・・・
コピペする際にソースコード中の
"

& quot;
要するにHTMLの文字実体参照に差し替えてください。
※残念ながら、Bloggerの本文中にこの記号が含められないのです・・・

(2) もしボタンを配置する場所が分からない場合には・・・
コメント欄でご指摘いただいたのですが、Bloggerのテンプレートは長くて複雑で、初めての人には何処に何を置けばいいのか分かりづらいです。ということで、各投稿のフッター欄(post-footer-line)にRetweetボタンを配置するサンプルソースを書いてみました。
    <div class='post-footer'>
    <div class='post-footer-line post-footer-line-1'>
      <span class='post-ほげほげ'>
      <!-- ここになにやら難しい記述がたくさん -->
      </span>
      <!-- post-footer-line post-footer-line-1の一番最後に追加 -->
      <a class='retweet' expr:href='data:post.url' expr:title='data:title + &quot;: &quot;+ data:post.title' />
      
      </div> <!-- post-footer-line post-footer-line-1 ここまで -->

      <div class='post-footer-line post-footer-line-2'>
      <!-- ここになにやら難しい記述がたくさん -->
      </div>

      <div class='post-footer-line post-footer-line-3'>
      <!-- ここになにやら難しい記述がたくさん -->
      </div>
    </div>
  </div>
</b:includable>

■ちょっとだけ補足説明
http://ejohn.org/blog/retweet/に記載されているコードをそのまま何も考えずに貼り付けると、Retweet先が現在ロケーションバーに表示されている先になってしまいます。
このコードを貼り付けて自分のBlogのトップページなどからRetweetボタンを押してみると、すぐに変だと気づくはずです
<a class='retweet self' href='' />
そこでBloggerのテンプレートXMLの記法を利用して、現在の記事のタイトルとURLを正しく指定してやります。具体的にはexpr:hrefとかdata:post.urlとかです。どうやら、expr:をhtmlの任意の属性に付けてやると、data:post.urlとかdata:titleとかが利用可能になるっぽいです。expr:を付けた属性の中で任意の文字列リテラルを利用したいときは& quot;で文字列を囲み、+で文字列を結合してやることができるみたいです。

2009年7月19日日曜日

YourTurn 1.1 サブミットしました

YourTurn 1.1をApp Storeにサブミットしました。現在レビュー待ちです。
http://wiki.github.com/akisute/YourTurn


■そもそもこのアプリ何?
学会タイマーアプリといえばわかりやすいと思います。要するに参加者ごとに時間を決めて割り振り、時間が来たらタイマー音がなる、それだけのアプリです。





2分程度のビデオデモを作ってみましたので、よろしければご覧ください(無音ですけど・・・)。


順調にレビューが進めば2週間後の8月上旬にはストアに並ぶと思います。無料、というかソースも全部公開してます。気が向いたらプレゼンしたりするときにでも使ってやってください。

iPhone OS 3.0のTableViewでscrollEnabledをNOにするとtableView:didSelectRowAtIndexPath:が機能しない

http://forums.macrumors.com/showthread.php?t=470266

OS2.2のころはscrollEnabledをNOにしても正常にdidSelectRowAtIndexPathが動いていたので、これは正直結構困ります。仕方がないのでscrollEnabledを使うのを回避して、スクロールするように戻しました。OS 3.1では直っているとよいのですが。

iPhone付属のPhotos(写真)アプリのような、回転可能な全画面表示ビューを作る方法



Photos(写真)アプリに使われている、全画面ビューを真似して作ってみました。具体的には以下のような仕様になります。
  • ステータスバーの後ろも含め、ビューの内容が全画面(320x480)で表示される
  • 画面をタップするとステータスバーとナビゲーションバーが消える
  • もう一度タップすると再度表示される
  • iPhoneを傾けると画面が回転する
参考にしたページは以下のとおり。
http://life.ponpoko.tv/?p=533

また、サンプルソースも公開してます。こちらのソースの、- (void)fullScreenMode:(BOOL)mode animated:(BOOL)animatedがフルスクリーン表示を実現しているメソッドになります。


■注意
iPhone OS 3.0以上専用です。2.2以下では別の方法を取る必要があります。


■まずはStatus BarとNavigation Barを消す
http://life.ponpoko.tv/?p=533 こちらのページに書いてある内容を100%そのままパク参考にさせていただければ何も問題ありません。iPhone OS 2.2までは記載されている通りのworkaroundが必要になりますが、3.0以降ではバグが修正されているため、単にStatus BarとNavigation Barを消せばOKです。

ただし一点、[self.navigationController setNavigationBarHidden:YES animated:YES];を使うと、ナビゲーションバーが上方向に対してぴょこんと引っ込むようなアニメーションになります。ステータスバーはその場でフェードアウトして消えるので、なんだか不自然な感じに見えます。Photos.app(写真アプリ)など、iPhone標準のアプリと同様にナビゲーションバーをフェードアウトさせて消したい場合には、UIViewのアニメーション機能とalpha値を利用して以下のようにします。
    if (animated)
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
}
self.navigationController.navigationBar.alpha = mode ? 0 : 1;
if (animated)
{
[UIView commitAnimations];
}
Three20プロジェクトのソースから拝借してきました><


■Viewを全画面配置する(Status BarとNavigation Barのあった場所にも描画させたい)
さんざん悩んだあげく、実はUIViewControllerにそのためのプロパティがあったと知り、ものすごく簡単に解決しました。
- (void)viewWillAppear:(BOOL)animated
{
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleBlackTranslucent;
self.navigationController.navigationBar.barStyle = UIBarStyleBlackTranslucent;
// このUIViewController.wantsFullScreenLayoutをYESにするとフルスクリーン表示になる
self.wantsFullScreenLayout = YES;
}
ついでにステータスバーとナビゲーションバーを透明にして裏が透過するようにしてみました。


■ビューの回転に対応したい
何も考えずにshouldAutorotateToInterfaceOrientationすればいいだろう常考
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
とおもったら思わぬ落とし穴が。確かに全画面表示にした状態でビューを回転させたり、逆にナビゲーションバーが出ている状態でビューを回転させたりする分にはこれだけで全く問題ないのですが、
  1. ナビゲーションバーを消す(全画面表示にする)
  2. iPhoneを傾けてビューを回転させる
  3. 再度ナビゲーションバーを表示する
という手順を踏むと、再度表示したときにナビゲーションバーが崩れる問題が発生。結局ナビゲーションバーを消したり再表示したりする際に、以下のようなworkaroundをするハメに。
    // Force set the frame of the navigation bar
CGRect frame = self.navigationController.navigationBar.frame;
frame.origin.y = 20.0;
self.navigationController.navigationBar.frame = frame;


■全画面表示をしている画面から前の画面に戻るとバグる問題
これで完成かと思えば、最後にもう一つ落とし穴が。
  1. iPhoneを横向きに傾けて、Landscape表示にする
  2. ナビゲーションバーを表示する
  3. そのまま横向きになっている状態で、ナビゲーションバーの戻るボタンを押して、前の画面に戻る
  4. 前の画面に戻る際に、wantsFullScreenLayoutをNOにして通常画面モードにし、ステータスバーとナビゲーションバーのスタイルもDefaultに戻す
という風な操作を行うと、なぜかナビゲーションバーのスタイルだけがDefaultに戻らず、表示も崩れるという問題が発生しました。たぶんOS 3.0のバグだと思います。
いろいろ試してみた結果、全画面表示をしている画面ではなく、前の画面のviewWillAppearのタイミングで以下のコードを呼びだせば問題が解決しました。
- (void)viewWillAppear:(BOOL)animated
{
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault;
self.navigationController.navigationBar.barStyle = UIBarStyleDefault;
}

CGGradientを用いてUITableViewCellを描画し、テーブルをカッコよく見せる方法

デフォルトのUITableViewCellの背景が白くてのっぺりでいまいち味気ないと思い、背景にグラデーションを付けてかっこよく見せる方法を調べてみました。単純に別途用意した背景画像をbackgroundViewに表示してもよいのですが、Cocoa Touchの2Dグラフィックスライブラリにはグラデーションを描画するためのCGGradientというクラスが最初から用意されています。さっそく私もパクってインスパイアされてやってみました。

参考にしたページはこちら。
http://developer.apple.com/documentation/graphicsimaging/conceptual/drawingwithquartz2d/dq_shadings/dq_shadings.html#//apple_ref/doc/uid/TP30001066-CH207-TPXREF101


■どこで描画するか
  • UITableViewCellのdrawRectで直接描画。
    少しでも高速に描画したい場合にはこの方法
  • 新規にUIViewを継承した背景用Viewを作成しセルのbackgroundViewに設定。そのViewのdrawRectで描画
    複数のUITableViewCellで同じ背景を適用したいときはこの方法が便利

■まずは実際に描画してみる
drawRectの中でCGContextを作成して、続いてCGGradientを生成。CGGradientを作るためにはCGColorSpaceとか色を指定する配列とかが必要になるのでそれらも生成。最後に生成したCGGradientオブジェクトを描画するという流れになります。ということでソースを見てみましょう。
- (void)drawRect:(CGRect)rect
{
// CGContextを用意する
CGContextRef context = UIGraphicsGetCurrentContext();

// CGGradientを生成する
// 生成するためにCGColorSpaceと色データの配列が必要になるので
// 適当に用意する
CGGradientRef gradient;
CGColorSpaceRef colorSpace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 1.0, 1.0, 1.0, // Start color
0.79, 0.79, 0.79, 1.0 }; // End color
colorSpace = CGColorSpaceCreateDeviceRGB();
gradient = CGGradientCreateWithColorComponents(colorSpace, components,
locations, num_locations);

// 生成したCGGradientを描画する
// 始点と終点を指定してやると、その間に直線的なグラデーションが描画される。
// (横幅は無限大)
CGPoint startPoint = CGPointMake(self.frame.size.width/2, 0.0);
CGPoint endPoint = CGPointMake(self.frame.size.width/2, self.frame.size.height);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);

// GradientとColorSpaceを開放する
CGColorSpaceRelease(colorSpace);
CGGradientRelease(gradient);
}
このコードを実行すると・・・


はい!もうグラデーションができました。嘘みたいに簡単です。

2010/05/24追記:注意点として、CGGradientとCGColorRefのオブジェクトは手動でリリースしないとメモリリークが発生します!


■UIColorを元にグラデーションを作る
先ほどの例では配列にRGBA要素を渡してグラデーションを作りましたが、UIColorが使えるともっとお手軽で、しかもRGBAだけではなくてHSBAで色が指定できて何かと便利です。ということで、次はUIColorからグラデーションを作ってみます。

UIColorから直接CGGradientを作ることは出来ないので、途中でCGColorとCFArrayRefを作り、それを元にCGGradientを生成してみます。コードはこちら。
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
UIColor *currentColor = [UIColor colorWithHue:currentBackgroundColorHSBA[0]
saturation:currentBackgroundColorHSBA[1]
brightness:currentBackgroundColorHSBA[2]
alpha:currentBackgroundColorHSBA[3]];
// gradient background
CGGradientRef grad;
CGColorSpaceRef colorSpace;

// UIColorからCGColorを取り出すのはとっても簡単
CGColorRef currentColorRef = [currentColor CGColor];
CGColorRef voidColorRef = [[UIColor colorWithHue:0.0
saturation:0.0
brightness:0.17
alpha:1.0] CGColor];
CGColorRef colorArray[2] = {voidColorRef, currentColorRef};
CGFloat locations[2] = { 0.0, 1.0 };

// CFArrayRefを作る。
// もっと簡単に作りたければ、NSArrayを作ってからCFArrayRefにキャストするだけでもよい。(未確認)
CFArrayRef colors = CFArrayCreate(kCFAllocatorDefault, (const void **)colorArray, 2, &kCFTypeArrayCallBacks);

colorSpace = CGColorSpaceCreateDeviceRGB();
// Gradientを生成する
grad = CGGradientCreateWithColors(colorSpace, colors, locations);

// 描画する
CGFloat progress = currentTime / allottedTime;
CGFloat height = self.frame.size.height + HEIGHT_GRADIENT;
CGPoint startPoint = CGPointMake(self.frame.size.width/2, progress * height - HEIGHT_GRADIENT);
CGPoint endPoint = CGPointMake(self.frame.size.width/2, progress * height);
CGContextDrawLinearGradient(context,
grad,
startPoint,
endPoint,
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation);
// GradientとColorSpaceを開放する
CGColorSpaceRelease(colorSpace);
CGGradientRelease(grad);
}
CGContextDrawLinearGradientの第4引数にkCGGradientDrawsBeforeStartLocationフラグとkCGGradientDrawsAfterEndLocationフラグを設定しています。これらのフラグを指定すると、startPoint以前、およびendPoint以降をグラデーションの開始色と終了色でべた塗りしてくれます。実行結果は以下の通り。


こんな感じで、画面中央から下が全部べた塗りになっているのが分かると思います。

他にも、CGGradientを作る際にlocationsを増やせば2色だけではなくて多色のグラデーションを作成することが出来たりします。このあたり、Appleが公開しているドキュメントだけでも相当詳しく紹介されているので、そちらを見ればほぼ間違いないかと。


■サンプルソース
今回のサンプルはgithubですべてソースを公開して居ますので、より詳しく学びたい方はそちらも併せてご参照ください。
http://github.com/akisute/YourTurn/tree/master
これとかこれを見るとよいかと思います。