2009年7月19日日曜日

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
これとかこれを見るとよいかと思います。

2009年7月12日日曜日

vim javascript indent plugin syntax

javascript用のvimプラグインがたくさんあって探してもこれといったまとめがなかったので探した範囲でまとめました。タイトルが釣りっぽいです。ごめんなさい。


■syntax
JavaScript syntax : Better JavaScrirpt syntax support
http://www.vim.org/scripts/script.php?script_id=1491

悪くはないです。ただし中身を見た感じFirefox + Dojoで使うことを想定されているようで、jQueryとかでprototype.jsでハイライトして欲しいオブジェクトがなかったりします。気に入らなければ適当に改造するのがよいと思います。それに、所詮syntaxですから無くてもあんまり困りません。


■indent
よさげなのが2つあります。まず一つ目、Ryan Fabellaさん作。スタンドアロンで動くもの。
OOP javascript indentation : This indentation script for OOP javascript (especially for EXTJS)
http://www.vim.org/scripts/script.php?script_id=1936
especially for EXTJSとか書かれてますがjQueryでも全く問題ないです。快適。

実際にインデントしてみた結果はこんな感じです。




//でコメントアウトすると完全にレイアウトが壊れてしまいます。/**/を使えば問題ないようです。


二つ目、Tye Zdrojewskiさん作。別にプラグインが必要になるもの。
http://www.vim.org/scripts/script.php?script_id=1840
会社のマシンにはこちらを入れています。実際にインデントしてみた結果はこちら。





こちらは//だと問題ありませんが、/**/だと崩れてしまいます。あと、$(function(){hogehoge...});を綺麗にインデントできていません。


どちらも一長一短なので、どちらを使うかは好みの問題ですね。


■plugin
IndentAnything : Write indentations or enhance existing indentations without writing code
http://www.vim.org/scripts/script.php?script_id=1839

Tye Zdrojewskiさんのjavascript indentを動かすために必要です。なんか勝手にIndentAnything_htmlなんていうindentが付属で付いてきますので、既にhtmlのインデントを持っている人は注意です(IndentAnything_htmlはおまけなので入れなくても大丈夫だと思います)。