2010年4月1日木曜日

UIBarButtonItem の見た目を画像にしたいときのテクニック

iPhoneアプリでよく使われる、ナビゲーションバーに配置するボタン UIBarButtonItem の見た目を完全に画像にする時のテクニックです。
参考にしたページはこちら。
http://discussions.apple.com/thread.jspa?threadID=1505647
http://www.iphonedevsdk.com/forum/iphone-sdk-development/13809-uibarbuttonitem-customview-action.html
http://discussions.apple.com/thread.jspa?threadID=1546506&tstart=60


■作戦1:initWithImage
てか、 UIBarButtonItem には initWithImage あるからそれでいいんじゃないか、と思ってさっそく以下のコードを書いてみました。
UIBarButtonItem* buttonItem = [[UIBarButtonItem alloc]
initWithImage:[UIImage imageNamed:@"appstore.png"]
style:UIBarButtonItemStyleBordered
target:self
action:@selector(appstoreAction)];
self.navigationItem.rightBarButtonItem = buttonItem;
するとあら不思議、確かに画像は表示されたのですが、画像の周りにいつものボタンの枠が表示されてしまっています。styleを変更してもうまくいきません。どうやらこれはあくまでいつものボタンの中に画像を表示するだけのメソッドで、ボタンの見た目そのものをそっくりそのまま画像に入れ替える(ボタン画像を作ってさしかえる)用途には使えないようです。


■作戦2:initWithCustomViewとUIImageViewを組み合わせる
それなら initWithCustomView を使って画像を表示させてみましょう。
UIImageView* customView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"appstore.png"]];
customView.userInteractionEnabled = YES;
UIBarButtonItem* buttonItem = [[UIBarButtonItem alloc] initWithCustomView:customView];
buttonItem.target = self;
buttonItem.action = @selector(appstoreAction);
self.navigationItem.rightBarButtonItem = buttonItem;
この方法だと、ボタンの縁が消えてappstore.pngがそのまま表示され、見た目は臨み通りの状態に表示させることができたのですが、今度は別の問題が。なんとボタンを押してもアクションが呼びだされません。海外のフォーラムでも同じような問題を抱えている投稿が見受けられました。


■作戦3:initWithCustomViewとUIButtonを組み合わせる
それならということで、カスタムビューを UIImageView から UIButton に変更してみることにしました。
UIButton *customView = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 95, 33)];
[customView setBackgroundImage:[UIImage imageNamed:@"appstore.png"]
forState:UIControlStateNormal];
[customView addTarget:self action:@selector(appstoreAction) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem* buttonItem = [[UIBarButtonItem alloc] initWithCustomView:customView];
self.navigationItem.rightBarButtonItem = buttonItem;
ビルドして試してみると、見た目は作戦2と同様に綺麗に表示され、さらにタップするときちんとアクションが実行されるようになりました!

2010年3月31日水曜日

Flash の埋め込みパラメータ wmode で発生したトラブルいろいろ

Flashまわりの仕事をしていて wmode パラメータで躓いたのでメモ。


■事の発端
開発機のMac上では正常に動作していたコンテンツが、Windows機のFirefoxでテストを開始したとたん以下のような症状に見舞われてしまいました。
  • TextFieldにフォーカスを合わせて日本語をタイプするとTextFieldとは全然違うとんちんかんな位置に入力中の文字列が表示される
  • しかも入力した日本語が文字化けする
ちょっと調べてみて、どうやら wmode パラメータが悪さをしているのではないかということが分かりました。
http://www.326studio.net/blog/2009/10/firefox.html
http://fukata.org/2009/11/29/1905/

ということで早速 wmode パラメータを消してみたところ上手く動作しました。


■そのときのSkype上での会話(一部改変)
登場する皆様ご紹介
 akisute: そもそもテキストボックス内に入力が表示されないのが普通な気がしてきた
easy: あいうえお=0B0D0F0H
akisute: http://www.326studio.net/blog/2009/10/firefox.html
akisute: こいつだな
wozozo: なんでその指定してたんだっけ
AE35: > htmlにswfファイルを呼び出すときにwmodeの指定でtransparent,opaqueの指定をするとこの現象が起きます。
akisute: http://fukata.org/2009/11/29/1905/
akisute: ということでをぞぞんよろしくお願いします
wozozo:
{
id : 'externalswf',
quality : 'high',
wmode : 'transparent',
allowScriptAccess : 'always',
width: 100,
height: 100,
flashvars: 'abesi hidebu tawaba'
});
wozozo: いまこうなってます。
wozozo: wmodeごと削除?
feiz: お、そこは以前のプロジェクトでぶつかった問題ですね
feiz: wmode=transparentを付けると
feiz: flash上にhtmlのダイアログを表示したりしたときにflashが上に出てしまう問題が回避できます
feiz: ただし日本語が化けます。
wozozo: e
wozozo: じゃあこれ消していいの
feiz: 両方いっぺんに回避する方法はあったっけ・・・
wozozo: でもいま
feiz: ああ、無いんだった
wozozo: flashの上にhtmlを表示するときには、
wozozo: embedされてるdivごと hide してる
feiz: あ、それが正しいはず
feiz: 以前のプロジェクトでもそうした。
wozozo: naru
wozozo: じゃあ消す。
feiz: ちなみに二つの問題は両方とも特定ブラウザ限がついてたはず。
feiz: どうでもいいけど。
akisute: それはすごい面白いネタなので
akisute: メモってBlogる


■さらに調査

調べれば調べるほどこの wmode には問題だのバグだのが山盛りだと言うことが分かって驚愕。
http://blog.graffiti-web.org/archives/2006/04/wmode.html
http://chabudai.org/blog/?p=34
http://d.hatena.ne.jp/jedisystemer/20090227/1235754536
http://3ping.org/2004/05/15/1441
Flash Player 10では修正されているらしいのですが、上記のバグはFlash Player 10で発生してますし、うーん。これは触らぬ神にたたり無しですね。


■結論
wmode は指定しないようにしましょう、と。Flash Player 9以下ではIEでフレームレートが落ちる問題がありますが、そもそも9を使っている人も少なくなりましたし問題ないと思います。

2010年3月20日土曜日

IE6, IE7 上で flash.net.URLLoader を使って Comet (Long Poling) によるプッシュ通知を行う際の注意点

Flashは一度書いたらどのブラウザでも同じように動く、そう信じていた時が私にもありました。その私の考えを裏切るかのように、 またIEか と言いたくなる現象が発見されました。

調査の際に参考にしたページはこちら。
http://saruzaurus.blogspot.com/2008/07/comet_10.html


■問題
IE6, IE7 で、 flash.net.URLLoader / flash.net.Loader を使用すると、同時に2本しかコネクションが張れません。通常の使い道でしたらこの制限で何ら問題はないのですが、問題になるのは CometTornado のように所謂ロングポーリングと呼ばれるHTTP通信の仕方をするアプリケーションを書いたときです。ロングポーリングではいったんHTTPリクエストをサーバーに投げた後、サーバーで何かイベントが発生するまでそのレスポンスをひたすら待ちます。従って常時HTTPコネクションが1本消費されている状態になるため、事実上1本しかHTTPコネクションを使うことができません。

またIE6, IE7 では、 flash.net.URLLoader.close() の実装にも問題があるようです。
http://www.kirupa.com/forum/showthread.php?t=335691
http://stackoverflow.com/questions/455656/urlloader-gets-stuck-when-polling
きちんと使用後に URLLoaderclose しているのですが、それでも何故かメモリ使用量が減らなかったり、コネクションが閉じられなかったりして貴重なコネクション数が消費されっぱなしになったりするなど、どうにもこうにも不安定です。



これらが原因となって、常時ロングポーリングでイベントを待ち、イベントを受け取ったら処理を行ってまたすぐロングポーリングを張りに行くようなアプリケーションを書くと、その他の通信を行った際にロングポーリングが止まってしまったりその他の通信がコネクション数限界で実行されなかったりします。


■原因
Flashは内部的にはブラウザの機能を用いて実装されています。したがってブラウザ側のHTTP接続数が2本であれば自動的にFlash側のHTTP接続限界も2本になります。当たり前と言えば当たり前なのですが、盲点でした。

Flash側の接続数とブラウザ側の接続数が共有されるかどうかは未知数・未検証ですが、おそらく共有されるのではないかと思っています。従って(まずこんな構成にすることはないと思いますが)FlashでCometによる通知待ちを行い、Ajaxでも同様の通知待ちを行うと、それだけで接続限界になってしまいます。


■対策
今のところ最良の方法がまだ分かっていません・・・ごめんなさいorz 一応いくつか思いついている作戦は、
  1. Windowsのレジストリを操作してIE6/IE7の同時接続数を増やす
  2. 同時接続数制限は同一ドメインに対してのみ発生するらしいので、サブドメインを切って別々のドメインに分ける
  3. 他の通信が発生しているときはロングポーリングを切断する
などなど。上手い対策が発見されましたらまた書きます。

2010/03/31 23:59 追記:サブドメインを切って別々のドメインに分けたら上手く動作しましたので、この方法を一応お勧めしておきます。