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

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はおまけなので入れなくても大丈夫だと思います)。

EditControlとAccessoryViewの背景は透明



  • EditControlの背景はデフォルトで透明
  • AccessoryViewの背景もデフォルトで透明
  • UILabelの背景はデフォルトで白塗りつぶし(透明にはならない)

OS 2.2.1での実験結果なので、3.0では違うかも(たぶん同じ)

それだけです。お粗末様でした・・・

2009年7月7日火曜日

YourTurnをApp Storeにサブミットしてみた



2ヶ月ぐらい前からちょこちょこ作っていた物をレビューに出してみました。まぁ、どうなるかは分かりません。現在一週間経過、特に何も変化無し。今回の目的であるApp Store童貞を捨てることは見事達成できそうです><


公開されるのを気長に待ちながら、現在1.1アップデートを作成中。セルがかっこよくなりました。

2009年7月5日日曜日

iMacとiPhoneでBluetoothのヘッドセットを使ってみた

以前からずっと気になっていたBluetoothヘッドセットをこのたび購入したので、iPhone 3GやiMacとつないでみました。
使用感などをレポートしてみたいと思います。


■購入したヘッドセット
某所で紹介されていた、「Bluetoothステレオヘッドセット ELANVITAL EVSH-2882」という奴です。ネックバンド型でA2DPに対応していて通話ももちろん出来ちゃう、その上8000円未満となかなかお買い得。(ソニーとかモトローラのやつは1万6000円とかしますからね><)


■iMacで使う
まずは「環境設定」から「Bluetooth」を開いてヘッドセットとペアリングさせます。ペアリングさせると、「ヘッドセットを有効にする」と「ヘッドフォンを有効にする」の二つが選択できる様になります。

こんな感じ。

実はここに落とし穴があって、
  • ヘッドセットを有効にすると、HSPを用いてiMacとヘッドフォンが通信を行う。
    そのため音楽等を聞くとモノラルで電話並みのひどい音質になる。
  • 逆にヘッドフォンを有効にすると、A2DPを用いて通信が行われるため音質は最高になるが、
    ヘッドセットとして認識されない(要するにヘッドセット内蔵のマイクが使えない)
何この残念な状況orz

MacのBluetoothの扱いが悪いのか、このヘッドセット自体に問題があるのか、それともBluetooth2.1の規格がそもそもそんな使い方を想定していないのかは知りませんが、とにかく「Mac OS X 10.5.7上でELANVITAL EVSH-2882を使う場合、そのままではステレオ音質でヘッドセットとしてマイクを自由に使うことはできない」というのは間違いなさそうです。

それでもいろいろ試して見た結果、以下の手順を取ればステレオの音質でマイクが使えるようになりました。
  1. 「ヘッドフォンを有効」にする。
  2. 「環境設定」の「サウンド」を開き、出力と入力を以下のように設定する。

    出力は、「Bluetooth ヘッドフォン」を選択。


    入力は、「Bluetooth ヘッドセット」を選択。
この設定を行った状態でSkypeを開き、オーディオデバイスを適当に設定して友人とSkypeで通話してみましたが、無事ステレオ音質でマイクが使えました。しかしこの方法は何かしらの無理をしているようで、突然音質がモノラルになったり、突然マイクが使えなくなったりします。その上バックグラウンドで動かしていたiTunesの音が全く聞こえませんでした。ということで、おすすめできません。


■iPhoneで使う
設定方法とかはAppBankさんのこちらの記事が詳しいのでそちらをご参照をば。
http://www.appbank.net/2009/06/19/iphone-news/32183.php
http://www.appbank.net/2009/06/21/iphone-application/32492.php

使ってみた感想ですが、A2DPのおかげで音質はかなり良いです。私はオーディオマニアではないので参考にはならないかもしれませんが、有線のヘッドフォンと全く違いが分かりません。普段から1万円以上のイヤフォンやヘッドフォンを使われるような方でなければ問題ないと思います。

着信があったときの移行もスムーズです。また、着信を受けてからBluetoothヘッドセットの電源を入れて、オーディオデバイスをそちらにに切り替えても問題なく通話が出来ました(ただし友人曰く、ちょっと音が遠いとのことなので、ひょっとしたらiPhone本体のマイクで音を拾っていたのかも)

Macでは音声出力先・入力先ごとに音量を保持してくれていたのですが、iPhoneではどうやらそのような機能がないようです(すべての音量が共通)。そのため、Bluetoothヘッドセットで小さいと思った音がiPhone本体のスピーカーでは大音量と言うことがあります。ちょっと不便。

最大の問題が、「一時停止」「次の曲」「前の曲」ボタンがすべて利用できないことです。私は結構頻繁に曲を次に送ったり一時停止したりする使い方をするので、これらのボタンが使えないのが苦痛になっています。
ひょっとしたら使える機種もあるかもしれませんが、少なくとも今回私が購入したものではダメでした。気になる方はご自身で確認されることをお勧めします。

電池の消耗については、有線よりは確実に減ります。どれぐらいがっつり減るかは未確認ですが、1時間程度付けっぱなしにしていると10%ほど減っていました。(別の要因もあるので一概には決められませんが・・・)

なお、音声コントロールはBluetoothヘッドセットが使用できませんでした。ヘッドセットをペアリングさせて使える状態にしていても、本体のマイクとスピーカーが使用されてしまいました。

結論としては、聞くだけ・通話するだけ専門としてはほとんど申し分ありません。ケーブルが邪魔になるジムでの運動時などでは最大限に威力を発揮すると思います。逆に普段使いでは電池の消耗・iPodコントロール不可などのデメリットが目立つかと思います。


■iMacとiPhoneで併用
ペアリング自体は全く何の問題もありません。iPhoneとiMacの両方に同じヘッドセットをペアリングさせることが出来ました。
しかし当然ですが同時に使うことは出来ません。ヘッドセットの電源を入れた後、「先に認識を完了した」デバイスが独占的にヘッドセットを使います。認識が遅れた方はエラーになりヘッドセットを使うことが出来ません。
iPhoneとiMacの両方のBluetoothがオンになっていると、どちらが先に認識するか安定しないため、
  • iPhoneで使うときは、iMac側のBluetooth自体をオフにする
  • iMacで使うときは、逆にiPhone側のBluetoothをオフにする
という面倒な作業が必要になります。結局、私はiPhone専用としてヘッドセットを使うことにしました。

2009年6月26日金曜日

第5回 Python温泉に参加中



第5回Python温泉に参加中です。以下、随時更新していきます。



■2009/06/26
16:00ぐらい
到着。挨拶もそこそこに早速Mac引っ張り出して開発開発。


18:46
晩ご飯準備中、いすがないので大の大人が20人も階段に座り込んだりたったままプログラム書いたり>< カオス><

19:00
ご飯。Pyspaグッズ販売について説明。Pyspaマグほしい。


19:56
晩ご飯終了。@moriyoshiがパンツ一丁で走り回っていたりとか。

21:04
最近書いてなかったうちにたまったネタでBlog更新。
@nakamura001さんにiPhone 3GSの良さを語ったりとか。

22:02
ひたすらプログラム。眠い、集中力が落ちてきているが何とかがんばる。
iPhone 3GSの新機能 VoiceOverを使ってsayコマンド祭りを試みるが、vの人に「感動を覚えない」と一蹴される><

22:25
眠い・・・寝るー。

■2009/06/27
6:56
おきったー。およそ7時間半睡眠。よほど疲れていたらしい。あと食い過ぎで胃がもたれている感じなので今日は自重する。軽く散歩して目を覚ましてから昨日の続きのコードを書く。
周りは死屍累々。徹夜と思われる4人以外全員寝てる。


7:00
layoutSubviewsの中で[super layoutSubviews];を呼び出してなくてセルがうまく描画されていなかった問題を修正。

8:26
朝ご飯終了。Python温泉鉄の掟のため、朝ご飯タイム(8時〜8時半)は強制的にたたき起こされます。

9:27
くぼけーさんと政治談義とか?やはり渡米して政治学とか専攻されている方は凄い、外から客観的に日本が見られるというのはいいな

10:00
コーディングコーディング。YourTurnのリファクタリングを行ったり。

11:00
お菓子買い出し


12:17
お昼ご飯&コーディング。隣で@nishioさんとvの人がErlangやらHaskelやらについて熱く議論してますが、1mmも理解できないので無視。何か論文みたいなのを確認しているようだが・・・いや、この人たちレベル高杉><

12:21
現在のタイムライン:
コンパイラーとかインタープリターとかCPUとか自作する?

CPU自作の本の表紙が萌え〜で買いづらい

もっと萌え〜なのと一緒に持って行けばいい

むしろオライリー本を萌え〜で挟んで持って行く

「何それ蛇とか萌え〜なのかしらこわい」

12:27
Python談義。@aodagさんがWerkZeuk嫌いですとかそういう話。
@moriyoshiの実装力&PHP力 + @nishiohirokazuの理論 + @tmatsuoのドキュメントスキル + 料理スキルという化け物プログラマの話(しかもニートらしい)

12:34
でもcommit logがひどい「impl」「impl」「impl」「impl「implばっかじゃねーか!」

12:43
rabbitMQのお話、@everesさんとvの人が戦争中
rabbitMQはzero-configulationだよ(ユーザーの追加だけ)!rpmでもyamでもapt-getでも入るよ!

14:56
お昼寝して散歩してリフレッシュ。



15:34
化学実験(コーヒーにドライアイスを投下などする)。

16:27
順調に進まない。あとは設定をつけるだけとか高をくくっていたのだが
設定をつけるって超大変じゃないか・・・

17:45
晩飯撤収タイム。いまのうちに温泉に行ってくることにする。

18:36
カードゲームタイム!


20:12
ご飯終了。カードゲームの続き。
隣では企業偏差値ランキングとかで盛り上がっているが。

22:18
@moriyoshi先生によるLiveプログラミング会(1時間半!)これは熱い!
なぜかjava.applet.Appletでボールを跳ね返すプログラムを作って遊んでいた。

続いて@ianmluiceの作ったSnippleというWebアプリの解説。タスクキューを使ったバックグラウンド処理とか、「メールボックスのような設計」とか(ある人が処理を行ったら、フォロワー全員のメールボックスにキューで処理を投げて、ある人がそのストリームを読めるようにする)

22:46
会議室に降りてきてラストスパート!

23:28
幕間設定できた!!


Just a small step for the world, but a giant leap for me!!

■2009/06/28
1:32
音ファイル設定も下地が完成。後は家に帰って音声素材を探してきて追加するだけ。
どうやら明日には予定通りApp Storeに提出できそうです。

2:00
就寝。

7:00
起床。

8:00
朝ご飯。

9:14
宿泊代金も支払って暇タイム。

10:04
10時より第6回Python温泉受付開始しました。開始5分で28人枠埋まりました。

11:00
解散!おつかれさまでした!

Xcodeのテンプレートを作成する方法

Xcodeにデフォルトで付随しているテンプレートでは満足出来なくなってきたため、デフォルトのテンプレートを改造して自分用の新規ファイル作成テンプレートを作ることにしました。
参考文献はこちら。
http://d.hatena.ne.jp/griffin-stewie/20090315/p1


■デフォルトのテンプレートのあるパス
デフォルトのiPhone SDK用のテンプレートは以下のパスにあります。
/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/
中を見ると、
akisute Xcode$ pwd
/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode
akisute Xcode$ tree -L 1
.
|-- File Templates
|-- Plug-ins
|-- Project Templates
|-- Specifications
`-- Target Templates

5 directories, 0 files
このようにファイル用のテンプレートとかプロジェクト用のテンプレートなどが用意されています。

■自分で作成したテンプレートを置くパス
自分で作成したテンプレートは、以下のパスに配置するとXcodeが認識してくれます。
~/Library/Application Support/Developer/Shared/Xcode/


■テンプレートの書き方
まずは元となるファイルをデフォルトのテンプレートのあるパスからコピーしてきます。今回はファイルのテンプレートを操作したいので、File Templatesディレクトリを丸ごとコピーします。
akisute Xcode$ cp -r File\ Templates/  ~/Library/Application\ Support/Developer/Shared/Xcode/
コピーしたテンプレートを修正します。テンプレートの構成は以下のようになっています。
File Templates
`-- Cocoa Touch Class
    `-- Objective-C class
        |-- NSObject subclass.pbfiletemplate
        |   |-- TemplateInfo.plist
        |   |-- class.h
        |   `-- class.m
        |-- TemplateChooser.plist
        |-- UITableViewCell subclass.pbfiletemplate
        |   |-- TemplateInfo.plist
        |   |-- class.h
        |   `-- class.m
        |-- UITableViewController subclass.pbfiletemplate
        |   |-- TemplateInfo.plist
        |   |-- class.h
        |   `-- class.m
        `-- UIView subclass.pbfiletemplate
            |-- TemplateInfo.plist
            |-- class.h
            `-- class.m
Cocoa Touch ClassディレクトリとObjective-C classディレクトリが階層構造になっており、その下のHogehoge.pbfiletemplateが実際のテンプレートになります。中のclass.hとclass.mを書き換えることで、テンプレートの内容を変更できます。

論より証拠、実際にやってみます。さっきの構造をXcodeに読み込ませると、以下の図のようになります。


それぞれの要素がどの位置に対応しているかがわかります。

実際にテンプレートを編集するときは、class.hとclass.mの内容を適当なテキストエディタで編集します。このとき、«YEAR»や«DATE»のような特定の文字列は自動的に置換されます。iPhone SDK付属のテンプレートを参考にしてみてください。


■余談:«»はどうやって出力する?
基本コピペすれば問題ないですが、自分で入力する場合は、
・JIS配列のキーボードの時は、Option + [とOption + ]で入力できます。
・ASCII配列のキーボードの時は、Option + \とOption + |で入力できます。

Keychain Accessから秘密鍵を書き出すときに、「エラーが発生しました。項目を読み込めません。」と表示されたときの対処法

iPhone開発にはAppleが発行する証明書が必要になるため、なにかとKeychain Accessのお世話になることが多いです。特に、複数台のMacで開発を行っている場合は、証明書を認証するための秘密鍵を複数台のMacにインストールする必要があります。
このとき、最初のMacから別のMacに秘密鍵を移すために、Keychain Accessから秘密鍵を書き出しする必要があるのですが、書き出した秘密鍵を取り込もうとすると「エラーが発生しました。項目を読み込めません。」とエラーが出て取り込みが出来ないケースがあります。


こんなダイアログです。

このダイアログが出てしまったときの対処法をまとめてみました。


■結論から言うと
このダイアログが出てしまったときは、秘密鍵の書き出し方を間違っています。


この「すべての項目」画面から鍵を書き出してしまうと、書き出しには成功しますが読み込むときに100%失敗してしまいます。


こちらの「証明書」画面から証明書の階層を開き、鍵を書き出すと、正常に読み込むことが出来ます。

2009年6月13日土曜日

サイテック社の中の人とお話してきました

http://blog.cytech.bz/

■ことのあらすじ
BPStudy(http://beproud.jp/bpstudy/)に参加する

懇親会に行く

自分が座る席がない

社長席グループになる><

F-Quest(http://www.f-quest.com/)のCEOの人と話す

iPhoneとか好きだから!とか言うと、じゃあいい会社があるのでぜひ会ってみてくださいと勧められる

行ってみた(今ココ)


ということで、サイテック株式会社の中の人とたっぷり終電までお話してきました。
人生哲学、今後の技術動向、iPhone開発について、日本の文化についてなど、たくさんのことを学ばせていただいて、本当にありがとうございます!
お二人とも大変聡明で、そして何より精神的に強いです。独立しています。ちょっとうらやましい。
  • 自分とは考え方が180度異なる人を認められるようになるためには、そういう人たちと利害関係を持つ経験をすること
  • 利害関係が発生しない状態は悪い状態、競争相手・ブレーキをかけてくれる存在を持つこと
  • iPhone開発は金にならない、今のApp Storeは供給過多すぎてビジネスとして成立していない
  • iPhoneアプリはどう視覚的に訴えかけるか、要するにデザインが大事
  • 今やりたいと思っていることを信じ続けることができるか
  • 日本の文化は強みになる、日本の文化を翻訳して展開する
といったあたりが今の自分に凄く響きました。iPhone開発者懇親会に行ったときにも同じような考えの方が多数いらっしゃったのを思いだして、現在のトッププレーヤーは大体同じような感覚を持ってらっしゃるのだと再認識できました。あとは、これらの課題をどう解決するか、ですねー。

以下、お話しした内容全文です。
私が話した内容と、私が後から考えたことと、サイテックの方がお話しされたことが混ざってしまっていますが、ご容赦ください。
基本的に中二病っぽい発言が私です。

[2009/06/13 20:07追記]ご指摘のあった不適切な箇所を修正いたしました。ご迷惑おかけしましたことをお詫び申し上げます。
その他、いくつか思い出した内容を追記しました。
[2009/06/16 22:42追記]誤字修正。本田誠一郎って>< お恥ずかしいことこの上ないです・・・



■お話ししたお相手
@yatabeさん(CEO)
@fw_tx76129さん(プロジェクトマネージャ、2年目。別の会社で5年勤務。テキサスの大学に留学していた)


■今後の展開
 →マルチプラットフォーム
 →WebはPC上だけではなくて、モバイルとか家電にも広がっていく
 →となったときに、どこかで書いたソースがそのまま使えないというのは問題
   ※たとえばObjective-Cとか、iPhone以外では使えない資産になってしまう
 →Perl, Ruby, PHP, JavaScript, ActionScript...これ以上いくつ言語を覚えればいいのか(笑)
 →そこでFlash(Air, Flex)などを模索している


■現在の事業内容
 →現在はPHPの案件がメイン
 →自社でPHPのフレームワークを作ったりしている
 →@fw_tx76129さん・・・基礎部分だけ書いて、残りは新人に課題として「こんなの作ってみて」と振っている。
   ※そしたら毎日「面白くて仕方がないです」とか言いながら仕事しやがって(笑)
   ※滅茶苦茶うらやましい!


■Androidはどうなる
 →Androidの最大のメリットはOSのライセンス料ではないかと思っている。
   ※詳しい額は分からないが、現在のSymbianだと1台あたり5000円程度のライセンス料がかかっているのではないか
 →そのうちケータイブラウザやお財布ケータイなどの機能を搭載した機種が出てくるだろう
 →むしろ現在のケータイの、OSの部分だけをSymbianからAndroidに入れ替えただけの機種が出てくるかもしれない


■組み込み2.0
 →ハードウェアの組み込み系は未だにC言語で、オブジェクト指向なんて流行りだしたのはつい1年ぐらい前
   ※生産性が低い、そういうところで開発者の気持ちが鬱積している
 →そこにAndroidが登場した。
 →今後は生産性の低いハードウェアの制御とかミドルウェアのあたりはすべて任せてしまうようになるかも
 →そうすると今まで溜まっていた開発者の気持ちが爆発して、ものすごい物が生まれるかもしれない
 →組み込み2.0になるかも


■守る文化について(広告代理店の人の話)
 →CGM (Consumer Generated Media) が流行している。例:logotournament.com
 →日本でもC-team (http://blog.livedoor.jp/kensuu/archives/50783668.html) というのがある
 →このC-teamについて、ある広告代理店の人はこのような話をした
 →我々広告代理店は、広告主からデザイナーを「守ってやっているんだ」
   ※広告主はデザインとか良いバナーとか全く理解しない、単にお金だけ出して「後頼むわ」という
   ※そのくせ後からデザインに自分の勝手な感覚で思いっきりケチを付ける「金出してるんだから俺の物だ」
   ※その中間を上手く取り持つのが広告代理店だ
   ※日本人のデザイナーはこういう競争に耐えられない、弱いデザイナーを救ってあげなくてはならない
 →なるほどちょっと納得。実際間違ってない。欧米型の競争原理が働くと弱い人は没落するしかない。
   ※そのおかげで鍛えられて強い人が育つのかもしれないけど
 →日本人の「守ってあげる文化」
   ※鎌倉幕府の御恩と奉公もまさにそれ


■今やりたいと思っていることを信じつづけること
 →iPhone開発で飯を食ってみたいと思っているのですが
 →長いスパン、10年後ぐらいを見て、どうなるかを考えてやってみるといい
   ※それでも続けたいか?
   ※当然だが、10年後にiPhoneはなくなっているだろう、それでもか?


■iPhoneアプリは事業になるか
 →結論から言うと会社の事業として100%の力を割いてやっていくことは出来ない、リスクがある
 →App Storeは市場として全く成功していない
   ※アプリの価値が余りにも安すぎる
   ※魅力的な開発環境がクリエイターを誘った結果、完全な供給過多に陥っている
   ※アプリの価値をどうやって引き上げるかを考える
   ※携帯サイトで上場した企業はあっても、iPhoneアプリで上場した企業はない
 →しかし無視することは出来ない。案件が来たらまずiPhoneでやってみようと考える
 →App Store以外で成功する方法を考えなければならない
   ※たとえば、アプリを買収する。安く作ってもらってストアに出して、気に入ったアプリを大企業が金で買う
   ※シリコンバレーで、ベンチャー企業がExitを目指すビジネスモデルに相似している
   ※B2BでのiPhoneの利用は出来ないか考える
 →重要なのはアプリの構成、デザイン。どのような層にどう視覚的に訴えかけるか。
 →まだiPhoneのビジネスは生まれたての卵で、どう転ぶか何が生まれるか全く分からない
 →会社全体のエネルギーの2割3割を割いて動向を探る
   ※ただ、このやり方だとあるタイミングが来たときに波に乗った人が全部成功をさらっていきそう(笑)


■B2B、B2C、C2Cの次、D2D(Developer to Developer)
 →開発者が開発者を集めるために、開発者のための物を供給する
   ※Android
   ※iPhone
   ※MacromediaのころのFlash
   ※各種フレームワークなど
 →勝手にC2C (Creator to Creator), いやD2Dと命名
 →そうすると、集まってきた開発者が勝手に面白い物をどんどん作ってくれる
 →プラットフォームが一気に普及する
   ※Flashが搭載率99%の化け物プラグインの地位を握ったのもそのおかげ
   ※HTMLの仕様がろくでもない頃に、インタラクティブなウェブを作ることを可能にしてくれた唯一の存在


■自分とは違う価値観の人を、どうしたら「認めてあげられる」のか、怖くはないのか(正直自分は怖い)
 →@yatabeさん「私も怖いですよ」
 →怖いですけど、そういう利害関係だから
 →たとえば株主と社長の関係がそうで、株主というのは利益にしか興味がない
   ※うちは君にN億円投資してるんだから、きちんと年間M%ずつ利潤出してよね。売上だけはきちんと上げてね
 →社長としては社員の面倒を見てあげたり、先行投資をしたいけど、株主のために売上をあげなければならない
 →そういう利害関係を肌で感じて学んだ
   ※どちらが良い悪いではない。利害関係に良い悪いも無い
   ※株主と社長、どちらがいなくても成立しない


■では、もし「社長」=「株主」の会社が存在したら、どうなるか?
 →利害関係が発生しなくなる。社長が株主になってしまう
   ※社長が株主にである=利益にしか興味がないということ
 →ブレーキをかける存在が無くなってしまう
 →良い利害関係とか悪い利害関係は無いと思っているが、「利害関係が発生しない」のは悪いこと
 →競争して切磋琢磨しないと成長が無くなってしまう
 →成長が無くなったら、その会社は終わる
 →本田宗一郎さんも確か「株式会社は株を公開してもって貰うべきだ」というニュアンスのことをおっしゃっていた
   ※出典不明(忘れた・・・ちゃんと調べないと)


■とある企業の悲劇
 →90年代後半に現在のMQと同じメッセージングシステムを作って売り出した日本企業がある
   ※時代背景からいうと、ようやくインターネットがかろうじて流行ってきたぐらい。
   ※まだGoogleは設立1年目とかそのぐらい。Yahooとgooが検索エンジンの主流だったと思う。
 →他にも現在で言うTracやRedmineのようなチケットドリブンシステムを自社開発
 →アレを作ったのは信じられないぐらいの天才。本当に頭がいい。たぶん世界最先端だったと思う
 →にもかかわらず今は没落、なぜか
 →そのシステムを捨てることが出来なかったから。固執してしまったから。
   ※2000年代序盤に廃棄されるべきだった、既に競争力が無くなっていた
 →そのシステムを作った人が社内で神格化した。彼らの言うことが絶対になった。
 →同様にそのシステム自体も神格化した。動いてるソースをメンテナンスすることは絶対の禁忌になってしまい、改修が進まなかった
 →ブレーキをかける人がいなくなった。新しいことにチャレンジしなくなった。そして古くなり役に立たなくなった。
 →第2次世界大戦の帝国海軍が大艦巨砲主義から脱却できなかったのを繰り返しているように思える


■Windowsはタッチタイプすら出来ないコンピューター素人の人でも使えるように作られている。少なくともそれを目指している。
 →@akisute「にわかには信じがたい」
 →例:Windows95ぐらいの地点で既にinternationalizationを完備していた。とある言語ファイルを一つ入れると23の言語すべてが使えるようになる
 →Macはクリエイターがクリエイターのために作ったもの。だから当時の爆発的普及には至らなかった
 →ビルゲイツの偉大なところは靴を投げられなかったこと(笑)・・・ではなくて、ド素人の人にコンピューターを使わせたこと
 →最近Macが流行りだしているのは、世の中でクリエイターがクリエイターのために作るものが流行ってきているから
   ※単に世の中の流れがMacにとって追い風になっているだけに過ぎない


■世の中には流れがあって、単にそのときの流れに乗っている物が成功しているだけ。良い悪いではない。
 →最たる例が任天堂
   ※余りにもわかりやすいので説明不要だと思う


■文化の翻訳
 →iPhoneアプリもそうだけど、日本が世界に通用するのは何か?というと、「文化」
   ※世界はみんな日本の文化を知りたがっている。ニーズはものすごく強いらしい。
 →文化を発信するだけではなくて、文化を「翻訳」して伝えること。これが次のWebの目標。
 →たとえばニコニコ動画などは海外でも一定の成果を上げているが、「翻訳」しているのではなくて「そのまま押しつけている」だけ
   ※弾幕とかコメント職人の技は日本語以外ではとても再現できない
   ※@akisuteにはFacebook上にイスラエル人の知り合いがいるけど、全員日本語を使っている。翻訳していない、向こうがあわせてくれている
   ※押しつけるのはただの文化侵略(Civilization 4みたいですね)
 →翻訳というのは、相手の理解できる形に変換すること
   ※例をあげると、寿司はカリフォルニアロールになった。あれがアメリカ人の理解できる形
 →Facebookが世界では優勢になっているが、世界各地には独自の文化圏内でのSNSがたくさんある。Mixiもそのひとつ。
 →それらのコンテンツを「翻訳」して中継する、SNSのSNSのようなサービスがあるといい
   ※@akisuteがちょっと考えた例では、Mixiの日本語の書き込みが、アメリカ人に理解しやすいように翻訳されて表示される。
    このとき、Mixiのアバターは実際の顔写真(ダミー)に変換される。向こうの人は実際の顔をさらす文化があるため。
    逆にアメリカ人側の書き込みは日本人が親しみやすいアバターに変換されるなど。
   ※最終的に書き込み主の人種が全く分からないレベルまで文化的翻訳が行われれば完璧
 →そこで問題になるのが、我々は日本を余りにも知らない。歴史も文化も知らない。
   ※アメリカに留学していて気づいたのが、アメリカ人は自国の文化を本当によく知っている。
    中学校の歴史の教科書でも信じられないぐらい内容が細かい。300年しか歴史がないから。
 →たとえばNinjaとかもてはやされているが、実は向こうの人の方がよっぽど忍者をよく知っている。
 →アメリカの寿司屋はみんなアメリカ人が店主だったりする。
 →origamiで検索すると、フランス人のもはや折り紙とは思えないレベルの作品が多数ヒットする。
   ※発見できなかった・・・
   ※http://en.wikipedia.org/wiki/Origamiなどは、sub articleも含めると日本語版の記事よりよっぽど充実していたりする
 →このように、草の根レベルで既に文化の翻訳が行われて根付いているが、我々が翻訳できて伝えられていない
 →相手国の文化をインターネット経由で知ることができる。実際に現地に飛んで文化を学んでも、インターネット上では異なることがある
   ※確かに日本もリアルとネットじゃ文化が違う気がする。匿名文化ってのはネットだけかもしれないし
 →宗教問題とか人種問題とかが、ネット上なら解決できる。ネットは同じ興味のある人をクラスタごとに引き寄せる。そこに人種とか宗教は関係ない。
   ※興味がない、自分の嫌いな人は自分のいるクラスタとは違うクラスタにいて見えない。だからネット世界なら簡単に同居できる。
   ※この手の問題はリアルでは絶対に解決できないと思う。アメリカでは未だに黒人がどうのとか白人がどうのとかがあった。
   ※同様に、会津(福島)のお年寄りは、いまでも薩摩(鹿児島)が嫌いだとかがあるらしい
 →今は人気がない歴史学科とか文化学科の人材が、10年後20年後にこのあたりのビジネスモデルが開拓されると、突如として経済界から引っ張りだこになるかも
 →iPhoneアプリの話に戻すと、ラジオ体操アプリとかどうよ(笑)
   ※Radio Social Performance(爆笑)
   ※絶対クックパッドより人気出るから!w クックパッドは日本の外に翻訳できないサービス。
   ※一人でラジオ体操とか続けられないから、みんなで動画撮ってストリーミングすればいいんじゃないw

2009年6月8日月曜日

時間を入力するために、カスタムUIPickerViewを作ってみた



時間を入力するためのUIが欲しかったので、こんな感じのカスタムUIPickerViewを作ってみました。ソースコードはこちら。
http://github.com/akisute/YourTurn/blob/8119bf028acf4908edb602d277544bc2cf6a5848/Classes/YTTimePickerView.h
http://github.com/akisute/YourTurn/blob/8119bf028acf4908edb602d277544bc2cf6a5848/Classes/YTTimePickerView.m


使い方はソースを見ていただければきっとご理解いただけると信じておりますので、UIPickerViewを使っていて気づいた点などをいくつか晒してみたいと思います。


■参考にした記事
id:paellaさんのダイアリーを参考にさせていただきました。ありがとうございます。
http://iphone-dev.g.hatena.ne.jp/paella/20090521#20090521fn1

基本的にはこちらの記事にまとめられている内容に従って、
// 2) ビューを返したい場合
- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view {}
このデリゲートメソッドを使いカスタムViewを利用した実装を行ったのですが、一部私の実装とpaellaさんの記事で異なっている点があるので、ご紹介したいと思います。


■UILabelを直接デリゲートメソッドから返す
元記事では、
UIViewを返すデリゲート、必ずUIViewを返さないとうまく動いてくれません。このサイトやこのサイトで紹介されているようなUILabelを直接渡してあげる方法は、少なくとも私の環境(2.2.1)では何も表示されませんでした。
と、UIViewを返さなくてはダメとご指摘がありますが、どうやらUILabelでも大丈夫みたいです。ビルドターゲット = iPhone OS 2.2.1の環境で確認できました。

UILabelをそのままデリゲートから返却したときに画面に表示されなかった原因は、どうやらUILabelのframe.size.heightが0になってしまっていた(要するにつぶれてしまっていた)のが原因みたいです。そこで、以下のようなコーディングを行いました。
- (UIView *)pickerView:(UIPickerView *)picker viewForRow:(NSInteger)row forComponent:(NSInteger)component reusingView:(UIView *)view
{
UILabel *label = (UILabel *)view;
if (!label)
{
label = [[[UILabel alloc] init] autorelease];
label.frame = CGRectMake(label.frame.origin.x, label.frame.origin.y, 20.0, 23.0);
label.backgroundColor = [UIColor clearColor];
// 以下初期化コードなど・・・
}

return label;
}
このように、label.frameの高さを無理矢理指定してやれば、きちんと画面に出せるみたいです。また、この方法を使えば、元記事で
ポイントはビューの座標。どうも各要素での{0,0}の位置はPicker内各Rowのど真ん中にあるらしく、サンプルのように負数を指定してあげないとどこかに寄った状態になってしまうみたいです。
とご指摘があったビューの座標も、特に気にせず普通に指定することが出来るようになりました。


■Pickerを、UIScrollViewのサブクラス(UITableViewなど)にaddSubviewする場合の注意点
この記事の見出しに貼り付けた画像では、作成したカスタムPickerをGroupedスタイルなUITableViewのfooterViewに貼り付けています。
    // Initialize time picker with a previously selected value
timePicker = [[YTTimePickerView alloc] initWithFrame:CGRectZero];
NSInteger initialValue = [[NSUserDefaults standardUserDefaults] integerForKey:_USERDEFAULTS_TIMEPICKER_INITIALVALUE];
timePicker.time = (initialValue == 0) ? 300 : initialValue;
timePicker.timePickerViewDelegate = self;
[timePicker selectRowWithCurrentTime];
self.tableView.tableFooterView = timePicker;
このように、UIScrollViewのサブクラスのサブビューとしてUIPickerViewを貼り付けると、そのままの状態ではうまくPickerのドラムが回転してくれないという症状が発生してしまいます。
これはどうやらUIPickerViewのスクロールとUIScrollViewのスクロールがけんかしてしまっているみたいです。そこで、スクロールする必要のないTableView側の設定をOFFにします。具体的には、Interface Builderを利用する場合、以下の赤枠でくくった箇所を設定すると良いようです。



こうすることで、綺麗にドラムが回転してくれるようになります。

2009年6月7日日曜日

java-ja #15 TDDとかペアプロとか

株式会社ドワンゴの本社にて開催された、java-jaの第15回勉強会に参加してまいりました。(ドワンゴさんありがとうございます!噂通りの社風が垣間見られておもしろかったです)
今回のテーマはTDDとペアプロ。TDDの権威である@t-wadaさん(http://twitter.com/t_wada)の講義+実際にTDD&ペアプロを体験する時間が設けられ、非常に充実した時間が過ごせました。

以下、Twitterにアップした感想を転載。


とにかく疲れる!そして慣れていないせいもあるがコードが進まない!普段の倍疲労して半分しかコードが進まないなら、明らかに生産性が下がっているように見える。でも違う、逆だ。こんなに生産的にコードが書けるとは思わなかった!

TDDを進めていくうちに、自分が意図しない考えや見落とし、typoなどの指摘が次々に飛び出した。書き上がったテストコードも普段自分が作っているものより綿密だ(もっとも普段はかなり手抜きなのだが)。TDDに反する進め方を指摘してストップすることもできた。

TDD自体はというと、テストケースが増えたり仕様が複雑化してきたりすると、途端に難易度が跳ね上がるな。一箇所直すと複数のテストがあっという間に真っ赤になる!新しくテストを作る判断とかも難しい、なによりテストを実行するのが面倒になったりする。jUnitMaxが素晴らしい理由を体感。

ペアプロの弱点は調べ物だな。明らかに調べ物をするときは個々人がググったほうが早い。だから熟練度が高くないとオーバーヘッドが大きくなってかえって非効率的。@yuruyoroさんには足引っ張って申し訳ない。スキルの差を埋める必要があるのも課題かなー、、、

個人的には一人でやる方が楽かな。人と話すのがへたくそ何だよなあ、、、いつもより疲れるのもそれが原因かも。ウォーターフォールがいけてないとはよく言うが、TDDやペアプロ自体にもたくさん課題があるのだなー、、、でも同じ課題満載なら、新しいやり方を開拓する方がいいよね、多分


TDDやペアプロは良いプラクティスですが、慣れないと実践は難しいですし、ウォーターフォールに代わる銀の弾丸でもなさそうです。そのことを実際に体で覚えることができたのが一番の収穫かな!

2009年6月4日木曜日

Firefox 3.5を導入してみた


いろんなところで話題になっている(http://d.hatena.ne.jp/chroju/20090523/1243076800)Firefox激重問題ですが、我が家でもニコニコ動画を見ることができないぐらい重くなっていたので、思い切って3.5b4を導入してみました。ちなみに、Mac OS X 10.5.7で試しております。


■利用したバックアップ用アドオン一覧など
Mozilla Re-Mix: インストールしているFirefoxアドオンをWeb上に保存してリストアできるアドオン「BELOW」
http://mozilla-remix.seesaa.net/article/109386090.html
Mozilla Re-Mix: Greasemonkeyスクリプトの利用環境をもっと便利にするFirefoxアドオン「Wescript」
http://mozilla-remix.seesaa.net/article/119556026.html
Mozilla Re-Mix: 拡張機能の「個別設定」をインポート・エクスポートできるFirefoxアドオン「OPIE」
http://mozilla-remix.seesaa.net/article/90165156.html
Mozilla Re-Mix: Firefox3 Beta版に対応していないアドオンを強引にインストールする方法。
http://mozilla-remix.seesaa.net/article/84338863.html


■インストール手順
1.AppCleaner(http://www.freemacsoft.net/AppCleaner/)を使ってFirefoxを完全に削除
  Windowsより消すのがラクチンです。

2.公式サイトから最新のbeta版をダウンロードしてきてインストール
  コピーするだけ。これも非常にラクチンです。

3.アドオンの復旧、まずはXmarks (http://www.xmarks.com/) をインストール
  ブックマークを最初に復旧します。

4.about:configを書き換えて強制的に古いアドオンをインストールできるようにする

5.ブックマークから、上記のバックアップ用アドオンを探してきてインストール

6.BELOWを実行

7.OPIEを実行

8.FireGesturesの設定が正しく復旧されていないので、手で復旧

9.Tab Mix Plusの設定が正しく復旧されていないので、手で復旧

10.Tab Mix Plusがバグっていることが判明したので、開発版 (http://tmp.garyr.net/dev-builds/) をインストール
   具体的には最後に閉じたタブが開けませんでした。
   Tab Mix Plusは大体バージョンあげるたびに問題が出るので、このサイトを覚えておくと便利。

11.Firefox自体の設定を調整

12.Wescriptを実行

13.自作のGreasemonkeyスクリプトをインストール


以上です。作業時間は40分程度でした。BELOWとOPIEは極めて便利なので本当にお勧めです。


■で、何が変わったか
正直、見た目はFirefox 3.0と全く変わりがありません。見た目で変化がわかるのはロケーションバーぐらいでしょうか。



動作はというと、まず例のニコニコ動画がガクガクして全く見られない問題は解消しました。動画を見ているときのCPU使用率も、前は2コアとも40〜50%程度消費していたところ、16〜20%程度まで低減しています。どうやら中身はしっかり改良されているみたいです。

いちばん大きな変化はリリースノート(https://developer.mozilla.org/Ja/Firefox_3.5_for_developers)を見ても分かるとおり、HTML5サポートだと思います。videoタグやaudioタグを利用できるようになったそうです。

早速試してみました。
http://www.youtube.com/html5


・・・動かないじゃないか!!><
(ちなみにSafari 4 betaでは動きます)

おそらく使われているビデオの形式がmpeg4なのが原因だとは思いますが・・・
変な青い枠が出ているのも、おそらくGoogleの中の人がChrome (Webkit)でしかテストしていないからでしょうし。
しかし、うーん、まだまだ開発版って感じがします。


■気になるアドオンの動作状況
微妙にTab Mix PlusがBuggyです。閉じたタブの履歴を正しく保存してくれていない感じがします。その他AdblockやFirebugなど、メジャーどころはほとんどすべて入れていますが、まだ1日も使っていないので何とも言えません。

2009年5月31日日曜日

NSTimerは基本的にretainせずassignでよい

NSTimerを初めて使ってみたのでハマったところをメモしておきます。


■NSTimerはNSRunLoopにretainされる。NSTimerは引数targetで与えられたオブジェクトをretainする。
いちばんハマったのがこの挙動です。
AppleのNSTimerについての公式ドキュメント(http://www.devworld.apple.com/documentation/Cocoa/Conceptual/Timers/Articles/usingTimers.html#//apple_ref/doc/uid/20000807-CJBJCBDE)にもクラスリファレンスにもきちんとと明記されていたのですが・・・思いっきり見落としてました。

これらがいったいどんな問題を引き起こすか。
たとえば普通のクラスと同じ感覚でdealloc中にNSTimerのinvalidateを呼び出すコードを書くと、永遠にdeallocが呼び出されなくなってしまいます。
// ViewControllerがNSTimerを使っているとして・・・
- (void)viewDidLoad
{
timer = [NSTimer scheduledTimerWithTimeInterval:interval
target:self // このselfはretainされる
selector:@selector(timerFired)
userInfo:nil
repeats:YES];
}
- (void)dealloc
{
[displayLabel release];
[timerLabel release];
// ここでinvalidateしてはいけない、永遠にdealloc自体が呼び出されなくなる
[timer invalidate];
[timer release];
}

なぜなら、
  • NSTimerがdeallocを呼び出すオブジェクトをretainしている、したがってNSTimerがreleaseされるまでdeallocが呼ばれない
  • NSTimerはNSRunLoopにretainされている、従ってNSTimerのinvalidateが実行されるまではNSTimerはreleaseされない
  • invalidateを呼び出しているのはこのdeallocの中以外にない。・・・詰みました。
こうならないようにするためには、dealloc以外の箇所から、適切にinvalidateメソッドを呼び出してやる必要があります。ということで、ViewControllerの中でNSTimerを使うときは、以下の2点に気をつければ良さそうです。
  1. NSTimerのオブジェクトは基本retainしない(自分でNSRunLoopにaddTimerとかしたい場合は別として)
  2. 通常deallocのタイミングでオブジェクトをreleaseするのと同じようにNSTimerをinvalidateしたい場合は、- (void)viewWillDisappear:(BOOL)animated を使う

- (void)viewWillDisappear:(BOOL)animated
{
if (timer)
{
// ここでタイマーをinvalidateする
// invalidateするとNSRunLoopがretainされていたこのタイマーをreleaseしてくれる
[timer invalidate];
timer = nil;
}
}

■userInfoはただのNSDictionary
そのまんまです。クラスリファレンスを見ても
The user info the new timer.

This parameter may be nil.
としか書いてなくて困ったのですが、本当にただのNSDictionaryです。適当に使ってくれってことでしょうか。


■次のタイマーイベントまでの間隔をリセットしたいときはsetFireDate:
たとえば何らかの理由でタイマーのFireイベントに登録していたセレクタを自分で呼び出しちゃって、次のタイマーイベントまでの間隔をリセットしたいときなどは以下のような具合にするとよいです。
if (timer)
{
// timerのsetFireDateに、次にタイマーイベントが発生する日時をセットする
// NSDateに、dateWithTimeIntervalSinceNowという便利なメソッドがあるのでこれを使う
[timer setFireDate:[NSDate dateWithTimeIntervalSinceNow:interval]];
}

■っていうか
今このBlogを書くためにNSTimerでぐぐったらここに書いてあるようなことがいろいろ見つかってしまいました・・・><
先にちゃんと調べてから作らないと無駄ですね−。

2009年5月27日水曜日

Mac版ATOK + HHK Professional2で日本語入力をするときの操作方法まとめ

1年ほど愛用していたCENTURY社のWhite Knight (http://www.century.co.jp/products/ck-112cmw-psu.html) の調子が悪くなってしまい、ついにはキーを認識しなくなってしまったため、現在家のiMacにはHHK Professional2 黒 (http://www.pfu.fujitsu.com/hhkeyboard/hhkbpro2/) をつなげています。

ASCII配列自体にはある程度慣れてきたのですが、問題が日本語入力です。家のiMacではATOK for Mac 2008をMS-IME風スタイルで利用していますが、ATOKだろうがMS-IMEだろうがことえりだろうが、一般的には皆さん変換時に文節を矢印キーで動かしたり伸縮させたりしていることと思います。ところが、HHK Professional2にはまともな矢印キーがありません(あるにはありますが、これは矢印キーとして見なしてはいけないと思います・・・)。これでは日本語入力が出来ない!ということで、HHKらしくControlキーとアルファベットの組み合わせて日本語入力を華麗にこなすための操作方法をまとめてみました。

2009/06/01追記:以下のショートカットキーはMS-IME設定のときのものです。WindowsのMS-IME上でも全く同様に使えました!


■次の文節へ・前の文節へ
左手で行います。
Control + Dで次の文節へ
Control + Sで前の文節へ
Control + Aで先頭の文節へ
Control + Fで末尾の文節へ


■文節区切りの伸縮
右手で行います。
Control + Kで文節を短くする
Control + Lで文節を長くする


■文字の削除
変換中にControl + Hで単語削除
変換中にControl + Gで一文字削除


■ひらがな・カタカナ・英字などに一発変換
主に右手で行います。
Control + Uでひらがな
Control + Iでカタカナ
Control + Oで半角カタカナ
Control + Pで全角英字
Control + Tで半角英字


■これだけ覚えておけば大丈夫!
でもどうしても気に入らない場合は、「カスタマイザ」というとっても便利な機能があるので、すべて自分の好きなように変えてしまいましょう。


■しかし驚いたのは
ほとんどこれらの機能を使うことなくATOKが一発変換してくれたこと。実際、このBlogを書いている最中にも、Controlキーには2回程度しか触れていません。ATOKはマジに神。

2009年5月19日火曜日

UINavigationの片方のサイドに複数個のボタンを持たせたい



こんな感じでUINavigationBarの片方のサイドに複数個のボタンをおく方法を探してみました。UIToolBarと異なり、UINavigationBarではUINavigationItem.rightBarButtonItem, UINavigationItem.leftBarButtonItem, それからタイトル部分と、最大でも3個しかアイテムを配置することが出来ません。そのため、複数のボタンを一つの配置箇所にまとめて配置したい場合には、カスタムビューを作成する必要があります。
Appleの配布しているデモアプリケーションにもありますが、こういう場合にはUISegmentedControlのmomentaryプロパティをYESに指定して、ボタンみたいに利用するのがいちばん良いようです。
NSArray *items = [NSArray arrayWithObjects:@"Add", @"YourTurn", nil];
UISegmentedControl *segmentedControl = [[[UISegmentedControl alloc] initWithItems:items] autorelease];
segmentedControl.selectedSegmentIndex = UISegmentedControlNoSegment;
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
segmentedControl.momentary = YES;
[segmentedControl addTarget:self action:@selector(segmentedControlClicked:) forControlEvents:UIControlEventValueChanged];
self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:segmentedControl] autorelease];

この方法でひとまずはUINavigationItemの右側に複数のボタンを作ることが出来ました。しかしまだまだ完璧ではなくて、現地点で分かっている限りでも二つの問題があります。
  1. ボタンの背景色が通常のボタンとは異なる色になる
  2. 個々のボタンひとつひとつを個別にDisableすることができない

Appigo Todoなんかは普通にUINavigationBarの右側に3つもまとめてボタンを綺麗に配置してあったりするのですが、一体全体どうやってるんでしょうね?・・・うーん。

2009年5月7日木曜日

Objective-CではvalueForKeyPath:で集計関数みたいなものが使えて凄く便利

Pythonだと、
list = [{'no':1, 'name':'akisute'}, {'no':2, 'name':'abesi'}, {'no':3, 'name':'hidebu'}]
maxNo = max(list, key=lambda x:x['no'])
こんな感じでリストに含まれるオブジェクトの最大値を簡単に取り出せたりするのですが、Objective-Cでもできないかと思い調べてみました。ですが、NSArray自体にはそのようなメソッドが用意されていません。ひょっとして出来ないのかと思っていたら、ちょっと面白い方法で集計関数のようなものが実装されていることがわかりました。

Objective-CにはKey-Value Codingという概念があって、それを使って実装されているようです。Key-Value Codingについては正直全然理解していないのでここでの解説は避けます。すみません。

NSArrayのvalueForKeyPathを使って以下のように問い合わせを行うと、先ほどのPythonの例と同様にNSArray中の最大値を持つオブジェクトを取得することが出来るようです。
// noとnameプロパティを持つPersonクラスがあると仮定して・・・
id akisute = [[Person alloc] initWithNo:1 name:@"akisute"];
id abesi= [[Person alloc] initWithNo:2 name:@"abesi"];
id hidebu= [[Person alloc] initWithNo:3 name:@"hidebu"];
NSArray *array = [NSArray arrayWithObjects:akisute, abesi, hidebu, nil];
NSNumber *maxNo = [array valueForKeyPath:@"@max.no"];
valueForKeyPathの引数に、@max.noというキーを渡すところがキモです。これで、NSArrayのインスタンスに含まれるオブジェクトのnoプロパティの最大値を求めることが出来ます。
同様にして、名前の最大値を求めたいときには、
NSString *maxName = [array valueForKeyPath:@"@max.name"];

とすれば取れます。また、一番長い名前の長さを求めたいときは、以下のように指定することができます。
NSNumber *maxCountOfName = [array valueForKeyPath:@"@max.name.count"];

@max以外にも、@avg, @sum, @count, @min, @unionOfObjects, @distinctUnionOfObjectsなどが用意されているみたいです。

詳しくは荻原さんのObjective-C 2.0本をご参照あれ。いや、この本は本当に買ってよかったです。Objective-Cのバイブルですね。

詳解 Objective-C 2.0
荻原 剛志
4797346809

Windows起動時にhal.dllが見つかりませんと言われた場合の対処法

久しぶりにBootCampでWindows XPを起動しようとしたら、hal.dllが見つかりませんといった具合のエラーメッセージが表示されてうんともすんとも言わなくなってしまいました。dllが破損したのかと思い調べてみたところ、このエラーメッセージはC:¥boot.iniファイルの設定が破損された場合に表示されるメッセージだと言うことが分かりました(さすがマイクロソフト、エラーメッセージの意味不明さには定評があります)。なるほど、確かにboot.iniファイルが消えてました。

修復するにはWindows XPをCDから起動して、回復コンソールを立ち上げます。画面の表示に従えば大丈夫だと思います。
回復コンソールが起動したら、以下のコマンドを入力。
bootcfg /list

これで起動設定(boot.iniファイルの中身)の一覧が表示されます。私の場合は、boot.ini自体が存在しないので、設定がありませんと怒られてしまいました。
設定が破損しているのを確認したら、以下のコマンドで修復を行います。
bootcfg /rebuild

途中、3回ぐらい選択肢が表示されますが、以下のように回答します。
インストールをブート一覧に追加しますか?
YES

ブートIDを入力してください
適当でかまいません。MacでBootCampしている私の場合はWindowsと入力しました。普通のWindowsマシンの場合にはWindows XP Professionalとか入れておくとわかりやすいのではないでしょうか。

オプションを指定してください
/fastdetect

これで完了です。あっさり終了するのでちょっと拍子抜け。あとはコンピューターを再起動すれば問題なくWindows XPが起動すると思います。

2009年5月4日月曜日

Objective-CのnilとNULLの違いって何?

自分用メモ。
nilは「Objective-Cの空のオブジェクト」、NULLは「C言語の空ポインタ」と解釈する。もっとも良い例がNSFileManagerのcreateDirectoryAtPath:withIntermediateDirectories:attributes:error:です。
http://developer.apple.com/DOCUMENTATION/Cocoa/Reference/Foundation/Classes/NSFileManager_Class/Reference/Reference.html#//apple_ref/occ/instm/NSFileManager/createDirectoryAtPath:withIntermediateDirectories:attributes:error:

リファレンスを読んでみると、attributes:の指定が不要なときはnilを、error:の指定が不要なときはNULLを引数として与えろと明示的に書かれています。これは、attributes:が(NSDictionary *)型=NSDictionaryのオブジェクトを引数として受け取るのに対して、error:は(NSError **)型=NSErrorのオブジェクトのポインタを引数として受け取るためだと考えられます。といっても私はObjective-CもC言語もド素人ですので、ひょっとしたら大嘘かもしれません。違ってたらごめんなさい><


※追記:やっぱり定義そのものが違った><
すたっくおばふろ曰く(http://stackoverflow.com/questions/557582/null-vs-nil-in-objective-c
えらいひと曰く(http://www.libjingu.jp/trans/clocFAQ-j.html#objects-nil


知らないとついつい全部nilもNULLも同じだろと思いnilって指定してしまいそうですね。おそらくnilを指定しても動くとは思いますが・・・Objective-CとC言語の混ざり合いというか関わり合いがこんなところで垣間見れて面白いです。

Objective-CではJavaのようにNullPointerExceptionが発生したりしない

そう、Objective-CにはNilPointerExceptionのようなものがないんです。
Javaでは
int main(String[] args) {
  String str = null;
  int length = str.length(); //NullPointerException

  // bar()がnullだとNullPointerException
  Object result = Foo.bar().baz().abesi().hidebu();
}
Objective-Cでは
int main(int argc, char** argv){
  NSString *str = nil;
  int length = [str length]; //何も起こらない。lengthにはデフォルト値0が入る

  // barがnilを返したら、後続のbaz, abesi, hidebuもnilを返す。
  id result = [[[[Foo bar] baz] abesi] hidebu];
}

同僚の方(http://twitter.com/ksk_matsuo/status/1682599075)よりこっそり教えていただきました。ありがとうございます!

なんと言ってもJava人にとっていちばん恐ろしいのがこのNullPointerExceptionなのですよ。だからオブジェクトのメソッドを呼び出すときはいつでもオブジェクトがnullだったらどうしようガクブルと思いながらプログラム書いてるわけです。それがいきなり「Objective-Cになったらぬるぽなんてありません安心です^^」なんて言われても安心できないなぁ><

2009年5月2日土曜日

Pipeを使ってみた



つい最近知ったのですが、Yahooの運営するPipe(http://pipes.yahoo.com/)というサービスが最高に面白いです。


■そもそもPipeって何?
Pipeは、「数々のWebサービスが提供するRSSフィードなどのデータを簡単に加工して、別のRSSフィードなどのデータに変換して出力することができるサービス」です。「データ取得元」や、「繰り返し」、「文字列加工」や「URL生成」などのパネルをパイプでつないで、簡単なWebアプリケーションみたいなものを作ることができます。
もちろん無料です。必要なのは米Yahoo(http://www.yahoo.com/、日本のYahooではダメです)のアカウントだけ。
サーバー代金もいりません。好きなだけ新しいPipeを作ることが出来ます。


■さっぱりわからない。Pipeを使えば何が出来るのか具体的に知りたい。
たとえばこんなことが出来ます。
  • ニコニコ動画のマイリストIDを入力すると、マイリストのRSSフィードを生成して返すアプリを作ることが出来る。
  • ニコニコ動画のユーザーIDを入力すると、そのユーザーが公開している全動画のnicomimi(http://www.nicomimi.com/)へのリンクを自動的に生成してRSSフィードとして返すアプリを作ることが出来る。
  • ニコニコ動画の本日のランキング動画一覧から、タイトル・タグ・本文のいずれかに「東方」が含まれていて、再生回数が2万を超えている動画だけを抜き出してRSSフィードとして返すアプリを作ることが出来る。
ね?便利でしょ?


■わかったけど、それって難しくない?
では試しに、ニコニコ動画のマイリストIDを入力すると、マイリストのRSSフィードを生成して返すアプリを作ってみましょう。
Pipe(http://pipes.yahoo.com/)にアクセスして、Yahoo.comのIDでログインして、Create pipeってボタンを押して、何も考えずに左のサイドバーから以下のようにパネルを並べます。


簡単に説明すると、
1.マイリストIDをユーザーにテキストとして入力させて、
2.入力値を元に、ニコニコ動画のマイリストRSS取得用のURL(http://www.nicovideo.jp/mylist/5221166?rss=atom)を生成して、
3.URLからRSSフィードを取得して、
4.そのまま出力しているだけです。

実行結果はこちら。


ばっちりです。所要時間10分。サーバーの設定もデプロイも面倒なコーディングも一切いりません。デバッガも標準で付属(Pipe作成中にパネルをクリックすると実行結果がその場で見られる)。マウス操作だけで簡単に作れて、見た目がそれっぽいダイアグラムになるので、自分の作りたいWebサービスのちょっとした動作確認にも使える気がします。駆け出しのWebプログラマが、マッシュアップのセンスを磨いたりするのにも適していると思います。


■問題点
問題点もあります。主に日本人が使う上での問題点なのですが、
  • 全部英語
  • そもそも使いたいサービスがAPIやRSSフィードを公開していないと使えない
そう、使いたいサービスがAPIを公開していないケースが余りにも多い。ニコ動もキーワード検索結果がRSSで出ませんし、pixivに至ってはそもそもAPIの概念自体が無い。これでは「指定したマイリストIDのマイリスのうp主コメ中にpixivへのリンクが存在したらリンクをたどってpixivユーザーのIDを抜き出し画像全部RSSフィードとして取得する(ついでにお気に入りにも突っ込む)」みたいなアプリが作れません。逆にいえばAPIやRSSフィードのあるサービスであれば可能性は無限大です。サービスの提供している検索結果一覧やRSSフィードの内容が気に入らないなら、自分でPipeを書いて自分の好きなように調整すればよいのです。

Google App Engineで、index.yamlに記述したインデックスが正しく生成されないときの対処法



Google App EngineでDatastoreのindexを利用したアプリケーションを作成していたのですが、運悪くindexの生成がいつまで経っても終わらない現象に見舞われてしまいました。データ量はたかだか1000件程度しかないのですが、3日経ってもindexの状態が"Building"のまま。Googleグループの書き込みを見ると(http://groups.google.co.jp/group/google-appengine/search?group=google-appengine&q=index)、indexの生成に失敗するというケースがたびたび発生しているようです。
  • index対象となるモデルの数は全く関係がない。数件しか保存されていないモデルに対してindexを生成しても発生する。
  • Googleの中の人曰く、indexはアプリ開発者全体で共有のindex作成クローラーによって行われているため、全体的に一度に利用する人が増えると不安定になる。

■まずはappcfg.pyを利用して、indexを消して再生成してみる
まずはindexをいったん消して再作成することにより、自分の手で解決できないかどうか試してみます。こちらの記事(http://osima.jp/blog/gae-index-in-trouble.html)を参考にさせていただきました。

まずは、index.yaml中のトラブルを起こしているindexをコメントアウトして削除し、
# Get a list of articles for "all" news page.
#- kind: Article
# properties:
# - name: is_duplicated
# - name: date_update
# direction: desc

ターミナルから以下のコマンドを実行。
appcfg.py --force vacuum_indexes /path/to/my/application

うまくいけば、管理者コンソールのIndexesメニューを開くと、対象のindexが"Deleting"という状態になっているはずですので、しばらく待って完全にindexが消えてからindex.yaml中で再度コメントアウトした記述を復活して、
appcfg.py update_indexes /path/to/my/application

を実行すれば、無事にindexが再作成されます。


■それでもだめなら、GoogleグループからGoogleの中の人にお願いして消してもらう
これでうまくいけば万事解決なのですが、ときどきこの方法ではうまくいかない場合があります。たとえば私の場合は、下記のようなエラーが出て上手くindexの削除が出来ませんでした。
akisute $ appcfg.py --force vacuum_indexes .
Fetching index definitions diff.
Deleting selected index definitions.
2009-05-02 12:48:33,476 WARNING appcfg.py:670 An index was not deleted. Most likely this is because it no longer exists.

kind: Article
properties:
- name: is_duplicated
- direction: desc
name: date_update

このような場合は、2009/05/02現在、米国本家のGoogle App Engineグループ(http://groups.google.co.jp/group/google-appengine)に直接お願いしてGoogleの中の人によってindexを削除してもらうしかないようです。以下の点に気をつけて新規ディスカッションを投稿すれば、スムーズにindexを削除してもらえると思います。
  • indexの生成がBuildingのまま進まない、24時間以上経っているのにうんともすんとも言わないことを明記する
  • appcfg.py vacuum_indexを試したけれどもうまくいかなかったことを明記する
  • 自分のアプリのApp IDを載せる
  • どのindexを削除してもらいたいかを載せる
参考までに、Google App Engineグループをindexで検索したときの結果(http://groups.google.co.jp/group/google-appengine/search?group=google-appengine&q=index)を載せてみました。同様の症状例がたくさん報告されていますので、それらの文面を適当にパクって参考にして投稿するのをおすすめします。
ただし、
Before posting, please read our Charter. Please note that due to recent spam activity, a member's first post will be moderated by one of the group's managers.
と注意書きがあるとおり、現在Spam対策として、最初の1回目の投稿はモデレータによってチェックされてしまい、すぐに投稿が反映されないみたいです。投稿されたとしても、いつindexの削除をしてくれるかはGoogleの中の人の気まぐれになってしまうので、解決には少々時間がかかります。


■英語が苦手なら、日本のコミュニティに助けてもらおう!
どうしても英語が苦手なら、日本のGoogle App Engineコミュニティ(http://groups.google.co.jp/group/google-app-engine-japan)に相談してみるとよいかもしれません。たとえば、こちらの投稿(http://groups.google.co.jp/group/google-app-engine-japan/browse_thread/thread/289b87a344715ea1/c9b5f8e394ac6a47)が参考になります。


■でも本当は
早くこんなトラブルが起こらないようなシステムになってほしいです><

2009年4月26日日曜日

MacPortsを使って、Pythonの開発環境を整えてみた

ここ最近勉強会続きだったため、複数のバージョンのPythonの開発環境の整備をする必要に迫られました。
まずはPython2.6.2をインストールしようと考えたのですが、python.orgからdmgでダウンロードしてインストールすると余計な物をたくさんインストールされてしまいますし、何より環境の切り替えが大変です。
(Mac付属の2.5.1でないとDjangoがエラーを吐いたりするため、いつでも2.5.1に切り戻せるようにしたい)

そこで今回はMacPortsを使って開発環境を整えてみました。

■Pythonのインストール
これはMacPortsから以下のコマンドを実行するだけでいけました。
sudo port install python26

ただ、依存するモジュールが非常に多いためビルドに大変時間がかかりました。MacBook Airで、およそ1時間ぐらい。
これで/opt/local/Library/Frameworks以下にPython.frameworkがインストールされるのですが、インストールしただけでは自由に元々存在するPython 2.5.1との切り替えができません。
そこで、同じくMacPortsで提供されている、python_selectというスクリプトをインストールします。
sudo port install python_select

インストールしたら、以下のようにして自由にPythonのバージョンを切り替えることが出来ます。
$ python_select -l #利用可能なバージョン一覧を表示
Available versions:
current none python25-apple python26
$ python_select python26 #python2.6に切り替え

こいつは大変便利です。

■Djangoのインストール
DjangoもMacPortからインストールできます。
sudo port install py26-django

Djangoだけではなく、jinjaやSQLAlchemy, Werkzeugなど、名前の知られているPythonのフレームワークはすべて存在しているようです。とっても楽ちん。
ちなみにインストールできるportとしてpy25-djangoのようにPythonのバージョンを指定しているものと、py-djangoのように指定していないものがありますが、py-djangoをインストールしようとすると突然Python2.4をインストールしようとしやがりましたので、基本的にはPythonのバージョンを指定しているportを選んだ方が良さそうです。

さて、MacPortでインストールしたPythonのためにDjangoをインストールするのは簡単でしたが、
問題になってくるのは元々標準で入っているPython2.5.1のためにDjangoをインストールするときです。
easy_installが使えるようなので、今回はeasy_installを使ってインストールしました。
sudo easy_install django

ただしportと比較すると後からアンインストールするのが面倒だという欠点があります。portが使えるならportがいいですね。

■PILのインストール
PILのインストールも基本はMacPortで。
sudo port install py26-pil

問題はPython 2.5.1にインストールするときです。私の環境では、easy_installが失敗してしまい簡単にインストールできませんでした。
悩んだあげく、PILのウェブページからPython Imaging Library 1.1.6 Source Kitをダウンロードし、直接setup.pyを実行して解決しました。
tar zxvf Imaging-1.1.6.tar.gz
cd Imaging-1.1.6
sudo python setup.py install

2009年4月17日金曜日

第1回java-ja温泉に行ってきます

http://java-ja.yoshiori.org/index.php?%E7%AC%AC%E5%8D%81%E4%B8%89%E5%9B%9E

以下、やろうと思っていることリスト
  • 昔作ったGAEアプリを作り直し、Werkzeugなんか使ってみたい
  • iPhoneアプリを作成する、こないだ書いたPythonのツールを移植するだけ
  • Javaはインストールすらしていません

2009年4月13日月曜日

Google App Engineのcronは、users.is_current_user_admin()では認証できない

先日作成したまとめの内容に間違いがありましたので、訂正してお詫びさせていただきます。

users.is_current_user_admin()を利用して、RequestHandler中にて起動ユーザーがcronかどうかを判定することが出来ると記事に掲載しておりましたが、
実際には出来ない模様です。

以下の方法にて検証を行いました。
1.まずはこのようなRequestHandlerを作る
class CronFetch(webapp.RequestHandler):
    def get(self):
        if not users.is_current_user_admin():
            logging.warn('CronFetch invoked by non-admin user:%s' % users.get_current_user())
            return
        logging.info('CronFetch begin')
        # この間に処理を記述
        logging.info('CronFetch end')

2.cron.yamlを記述する
cron:
- description: Test cron
  url: /cron/fetch
  schedule: every 1 hours
  timezone: Asia/Tokyo

3.本番環境にデプロイして、admin consoleから確認してみる


ごらんの通り、users.get_current_user()の結果がNoneになります。
おそらく2009/04/13現在では、Google公式のドキュメントにあるとおりapp.yamlで指定する以外にcron起動かどうかの認証を行う方法はなさそうです。