2012-03-23

AndroidでOutOfMemoryError: bitmap size exceeds VM budget

「java.lang.OutOfMemoryError: bitmap size exceeds VM budget」というエラーに悩まされています。
エラーログは、次のような具合です。
03-22 23:43:50.148: E/AndroidRuntime(15933): Caused by: java.lang.OutOfMemoryError: bitmap size exceeds VM budget
03-22 23:43:50.148: E/AndroidRuntime(15933):  at android.graphics.Bitmap.nativeCreate(Native Method)
03-22 23:43:50.148: E/AndroidRuntime(15933):  at android.graphics.Bitmap.createBitmap(Bitmap.java:477)
03-22 23:43:50.148: E/AndroidRuntime(15933):  at android.graphics.Bitmap.createBitmap(Bitmap.java:444)
03-22 23:43:50.148: E/AndroidRuntime(15933):  at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:349)
03-22 23:43:50.148: E/AndroidRuntime(15933):  at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:498)
03-22 23:43:50.148: E/AndroidRuntime(15933):  at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:473)
03-22 23:43:50.148: E/AndroidRuntime(15933):  at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:336)
03-22 23:43:50.148: E/AndroidRuntime(15933):  at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:359)
03-22 23:43:50.148: E/AndroidRuntime(15933):  at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:385)
03-22 23:43:50.148: E/AndroidRuntime(15933):  at android.graphics.drawable.BitmapDrawable.inflate(BitmapDrawable.java:373)
03-22 23:43:50.148: E/AndroidRuntime(15933):  at android.graphics.drawable.Drawable.createFromXmlInner(Drawable.java:787)
03-22 23:43:50.148: E/AndroidRuntime(15933):  at android.graphics.drawable.StateListDrawable.inflate(StateListDrawable.java:172)
起動と終了を2〜3回繰り返すとエラーになるので、メモリリークの可能性がありますが、コードを読み直してもさっぱり分かりません。単純に、Drawableのサイズが大きすぎるのかもしれません。
ログによれば、StateListDrawableを表示するときにメモリーが足りなくなっています。他のログから推察すると、エラーが生じているのは、500x740のpngファイルでステートを4つ定義してあるDrawableのようです。PNGファイルのサイズは、最大で56KBでした。
アプリを実行するとメモリを50MBくらい消費していました。

とりあえず次のような対策をとりました。

  • 画像のサイズを小さくする
  • StateListDrawableのステータスの数を減らす
  • 背景で9patchやtileを利用できるところは利用する
これで、OutOfMemoryErrorが発生することをいまのところ防げていますが、やはり、やっていることの割にメモリの消費量が多すぎるような気がします。

2012-03-20

jQuery MobileだとIE7ではてなブックマークボタンも表示できない

jQuery Mobileを使うと、IE7からのアクセスで、はてなブックマークボタンを表示できないようです。ページが真っ白になってしまいます。

jQuery MobileのページにAdSenseを組み込むとIE7でページが真っ白になることは気づいていましたが、はてなブックマークボタンも駄目でした。TwitterボタンとFacebookいいねボタンは大丈夫なようですが、使えないWebパーツがたくさんありそうなので注意が必要です。

User-Agentに「MSIE 7」という文字列が含まれているかどうかで処理を切り分けていますが、これで判定できるのはブラウザモードです。
表示できないのはドキュメントモードがIE7の場合なので、IE9などでドキュメントモードのみIE7にしているとやはり真っ白になります。しかし、ユーザーエージェントを見てドキュメントモードを判定することはできそうにないので、仕方がありません。

2012-03-18

古いiPhoneにもインストールできるようにRequired device capabilitiesを削除

先日『一挙に英語翻訳』のiOS版をアップロードしましたが、いくつかミスに気づいたのでビルドしなおして再アップロードしました。

Info.plistで、「Required device capabilities」にarmv7 が指定されていたために、古いiPhoneにインストールできなくなっていました。iOS4.2以上ならば動作するようにしたいので、「Required device capabilities」を削除しました。

またgitレポジトリが壊れていた(たぶんSugarSyncのせい)

gitレポジトリが壊れていたみたいで、branchを一度切り替えたら元に戻せなくなってしまいました。オプションで -f を指定してなんとか元のbranchに戻しましたがデータが失われたかもしれないと思って焦りました。
SugarSyncでこのフォルダを同期していたせいだろうと思っています。gitやsvnなどと、SugarSyncやDropboxを組み合わせて使えば、無料でバックアップもリモートにとりつつバージョン管理ができると考えていたのですが危険なのでもうやりません。
他のレポジトリも一見正常に動作しているようにみえて実は壊れているという可能性もあります。すべてつくり直した方がいいかもしれません。

2012-03-17

OS X Lionにしたら.Xmodmapが効かなくなってた

snow leopardではちゃんと動作していましたが、lionでは .xmodmapが効かなくなっていることに気づきました。
次のように書き換えたらちゃんと動きました。
! Switch meta and control
keycode 67 = Meta_L
keycode 63 = Control_L
clear mod2
clear control
add mod2 = Meta_L
add control = Control_L

はてなブックマークボタンをつくる

Facebookのいいねボタンと、ツイッターのTweetボタンに加えて、はてなブックマークボタンをWebページに設置したいと思って調べてみたところ、次のページで簡単につくれることが分かりました

コードはこんな具合です。
<a class="hatena-bookmark-button" data-hatena-bookmark-layout="standard" data-hatena-bookmark-title="一挙に英語翻訳" href="http://b.hatena.ne.jp/entry/http://subpedia.org/translate/english-japanese/" title="このエントリーをはてなブックマークに追加">
  <img alt="このエントリーをはてなブックマークに追加" height="20" src="http://b.st-hatena.com/images/entry-button/button-only.gif" style="border: none;" width="20" />
</a>
<script async="async" charset="utf-8" src="http://b.st-hatena.com/js/bookmark_button.js" type="text/javascript">
</script>

『一挙に英語翻訳』をAppStoreに再申請しました

先日リジェクトされてしまった『一挙に英語翻訳』のiOS版を修正して、再申請を行いました。
iPadでも使いやすいようにUIを改めてあります。

Appleにリジェクトされた理由が正確には分からないため、今回の申請も通るかどうか分かりません。もしまた拒否された場合は、履歴保存機能を付け足して再々申請すると思います。

Xcode5.1でビルドしたら何やら設定ダイアログが出た

Xcodeを5.1 にアップグレードしたためだと思いますが、リリースモードでビルドをしたら何やら警告のダイアログが出ました。設定を変更した方がいいということらしいです。

すべてにチェックを入れて、Perform Changesを実行しました。
たぶんこれでいいのだと思いますが、後でトラブルが生じたときのためにメモしておきます。

UISegmentedControlで同じインデックスのクリックを検知

UISegmentedControlのクリックを検知するには、通常、ValueChangedイベントでやりますが、このイベントは同じインデックスがクリックされた場合は呼ばれません。しかし、TouchUpInsideイベントや、TouchDownイベントをとろうとしても、これらが呼ばれません。

UISegmentedControlを継承したサブクラスをつくれば、UISegmentedControlのクリックを検知できます。次の例では、同じインデックスが選択された場合は、TouchDownイベントが発生するようにしてあります。


ClickableSegmentedControl.h
#import 

@interface ClickableSegmentedControl : UISegmentedControl
@end


ClickableSegmentedControl.m
#import "ClickableSegmentedControl.h"

@implementation ClickableSegmentedControl

- (void)setSelectedSegmentIndex:(NSInteger)selectedSegmentIndex
{
    BOOL isSameIndex = (selectedSegmentIndex == self.selectedSegmentIndex);
    [super setSelectedSegmentIndex:selectedSegmentIndex];
    if (isSameIndex) {
        [self sendActionsForControlEvents:UIControlEventTouchDown];
    }
}
@end

しかし、このやり方では、iOS4ではクリックを検知できても、iOS5ではムリでした。iOS4とiOS5のどちらにも対応するには、touchesEnded:withEvent:をオーバーライドすればいいようです。次の例では、TouchUpInsideイベントを発生させています。注意しなければいけないのは、これだとインデックスが変更されたどうかにかかわらずイベントが発生してしまう点です。touchesEnded:withEvent: は、ValueChangedイベントの後で呼ばれるようなので、この点を考慮して適切にハンドリングする必要があります。

ClickableSegmentedControl.m
#import "ClickableSegmentedControl.h"

@implementation ClickableSegmentedControl

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesEnded:touches withEvent:event];
    [self sendActionsForControlEvents:UIControlEventTouchUpInside];
}
@end

2012-03-16

UISplitViewでデバイスが縦のときもMasterViewを表示する

iOS5.0以上であれば、UISplitViewControllerDelegateに、次のコードを書き足すだけで、デバイスが縦のときにもUISplitViewを2ペインにできます。
- (BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController:(UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation
{
    return NO;
}

右側のDetailViewが狭くなるのがやはり気になるので、左側のMasterViewの表示/非表示を切り替えられるボタンを付けたら使いやすくなるような気がするのですが、このやり方は調査中です。

splitViewController:shouldHideViewController:inOrientation: が使えるのはiOS5.0+なので、iOS4.xでは、普通にMasterViewが消えてしまいます。
そこで、iOSのバージョンをチェックして、iOS4.xならば、スクリーンの向きを縦にできないようにします。
NSString* systemVersion = [[UIDevice currentDevice] systemVersion];
で「4.3.2」や「5.0.1」などの文字列を取得できるので、これを見てshouldAutorotateToInterfaceOrientation:の戻り値を決めます。


すごく便利そうなライブラリがオープンソースで公開されています。

『一挙に英語翻訳』iOS版の対応

先日リジェクトされた『一挙に英語翻訳』のiOS版ですが、まだ何の対策もとっていません。放置したままです。

Appleの真意は計りかねるのですが、iPadへの対応が甘かったという点は改善すべきだと自分で思います。iPhoneアプリをそのまま巨大にしたようなつくりになっていました。
UISplitViewを使って、iPad用に使いやすいUIを用意することにします。別アプリでiPad用を用意するのではなく、ユニバーサルアプリにします。
デバイスの回転に対応するのは今後の課題ということにして、とりあえずは横向き固定です(iPhone用は回転に対応します)。
履歴保存機能も欲しいですが、これは後回しにして、UISplitViewに対応したらAppStoreに再申請する予定です。

OS X LionにPostgreSQLを入れようとしてエラー

OS X LionにPostgreSQL8.4.11をインストールしようとしたのですが、途中で次のようなエラーメッセージがでて失敗してしまいます。

Problem running post-install step. Installation may not complete correctlyThe database cluster initialisation failed.

試したのは、たしかこのページからダウンロードしたdmgです。

そこでMacPotsでインストールを試みることにします。
$ sudo port install postgresql84 +python

2012-03-15

AdSenseを使うとjQuery MobileのページをIE7で開けない

jQuery MobileのページをIE7で開けないとしたら、それはAdSenseなどの広告コードが含まれているかもしれません。
もし広告コードが原因ならば、User-Agentに「MSIE 7.」という文字列が含まれているかどうかでIE7からのアクセスかどうかを判定して、広告コードを取り除けばよいでしょう。

2012-03-14

OS X LionをにアップグレードしたらMacPortsが動かない

なんかトラブルが起こりそうでOSのアップグレードって嫌なんですが、OSをアップグレードしないとXcodeもアップグレードできず、iOS5.1への対応もできないので、仕方がなしにOS X Lionを購入します。

ファイル消えては困るので、SugarSyncでバックアップをとるつもりでしたが止めました。SugarSyncも信頼していいのかよく分からないサービスで、最近は同期させていなかったので、同期するのに途方も無い時間がかかりそうだからです。

***
インストールは上手く行きました。XcodeもAppStoreからダウンロードできました。

eclipseが起動できなくなっていましたが、これはJavaアップデートすれば良かったみたいです。

gitも使えなくなっていることに気づきました。どうも、Git for OS Xがsnow leopardにしか対応していないようです。MacPortsでインストールし直す必要があるみたいです。

ところが、MacPortsがうまく動きません。次のようなエラーが連発します。

Error: Unable to open port: can't read "build.cmd": Failed to locate 'make' in path: '/opt/local/bin:/opt/local/sbin:/bin:/sbin:/usr/bin:/usr/sbin' or at its MacPorts configuration time location, did you move it?

MacPortsを更新することはできました。
sudo port selfupdate
sudo port upgrade outdated
しかしこの後も、エラーが連発します。もう一度 port upgrade outdated を実行しようとしたら、これもエラーになってしまいました。

Xcodeで、UnixのCommand Line Toolsがインストールされていないのが原因で、build.cmdが見つからないというエラーが出ているようです。以前はインストーラでCommand Line Toolsをインストールするか選べたはずですが、いまはAppStoreからダウンロードするだけなので、インストール時には選べません。しかし、Xcodeを起動した後、Preferences(Cmd + ,)のDownloadsタブからインストールできます。Command Line Toolsをインストールできたら、次のコマンドを実行します。
sudo port install git-core +doc +python26

いちおうgitをインストールできたみたいです。
しかし、MacPortsの調子が悪いです。
次のような警告が出ていることに気づきました。
Warning: Xcode appears to be installed but xcodebuild is unusable; some ports will likely fail to build.
Warning: You may need to run `sudo xcode-select -switch /Applications/Xcode.app`

Djangoの開発サーバーで静的ファイルを配信する

こんな感じにして、キャッシュを無効かするのがいいかと思います。

if settings.DEBUG:
    from django.views.static import serve as static_serve
    from django.views.decorators.cache import never_cache
    urlpatterns += patterns("",
        url(ur"^static/(?P.*)$", never_cache(static_serve), dict(document_root=settings.STATIC_ROOT)),
    )

と、思ったのですが、これでもキャッシュされてしまうようです。なぜだかよくわかりません。

CoffeeScriptで書いた後で思ったこと

CoffeeScriptの構文が美しいことに感動して、小さなプログラムを書いてみました。遊びでこっそり書くプログラムではなく、公開するつもりのプログラムです。
それで気づいたのは、JavaScriptで書いてもほとんど同じだということです(jQueryを使ったからかもしれません)。

ひとつだけ大きく違ったのは、何の迷いもなく、クラスをきれいに書けるということです。
しかし、JavaScriptでも、継承のやり方さえ決めてしまえば、それなりに可読性の高いコードを書けるような気もします。
デバッグのことを考えると、やはりCoffeeScriptよりも、JavaScriptのほうが有利です。

JavaScriptで継承をどんな風に書くか考えてみます。CoffeeScriptのプログラムをJavaScriptで書きなおしてみるかもしれません。

次のように create関数を定義して、これでオブジェクトの生成や継承をやることすれば、JavaScriptでもコードがすっきりするように思います。継承をするときは、createで生成して、extendで差分を定義します。newを隠蔽しているあたりは、クラス型の継承ではなく、プロトタイプ型の継承と言ってよいかと思います。
var create = function(proto) {
    var Meta = function() {};
    if (undefined !== proto && null !== proto) {
        Meta.prototype = proto;
    }
    var obj = new Meta();
    obj.__meta__ = Meta;
    obj.extend = function(obj) {
        for (var m in obj) {
            this[m] = obj[m];
        }
        return this;
    };
    return obj;
};

使い方は以下のような具合です。
var base = create({
    name: function() {return "this is base";}
});
var derived = create(base).extend({
    name: function() {return "this is derived";}
});
クラスを使わずにオブジェクトから直接オブジェクトを生成するイメージですが、var d1 = creat(derived); var d2 = create(derived); というようにできるので、とくに困ることはなさそうです。

『一挙に英語翻訳』のiPhone版がリジェクトされました

『一挙に英語翻訳』のiPhone版をAppStoreに申請していましたが、公開できないという拒否のメールが届きました。
「Webページを表示しているが、もっとiOSっぽい機能を追加しないとダメ」というのが理由でした。何かしら対策をとって、再申請する予定です。

『一挙に英語翻訳』のWeb版で苦戦

『一挙に英語翻訳』のWeb版をつくろうとしているのですが、ブラウザごとの挙動が違っていて、とくにIE対策が困難そうです。

IE6は、そもそもサポート外と考えていますが、 IE7でもページが真っ白になって表示できません。
IE8やIE9でも、「→」がきちんと表示されない(横棒が欠けてしまう)という問題があります。もっとも「⇒」ならばちゃんと表示されるようなので、これは大した問題ではありません。

問題は、IEでは、window.open() で複数のタブを開こうとするとポップアップ・ブロックに引っかかってしまうことです。タブ一つだけなら開けるのですが、複数はダメみたいです。
ポップアップがブロックされた場合はメッセージが一応表示されるのですが、これが表示されるタブから別のタブに遷移してしまっているため、ユーザーが気づかない可能性が高いです。
さらに、ポップアップ・ブロックのメッセージは、ページが更新されると消えてしまいます。しばらく時間が経過しただけでも消えてしまうようです。

ページが更新されるとポップアップがブロックされたことを通知するメッセージが消えてしまうことには、Ajaxを使えば対応できそうです。
しかし、ブロックされずに複数のタブを開くにはどうしたらいいかはよく分かりません。window.open() に失敗した場合は、戻り値がnullだかundefinedだかになるようなので、そこでブロックの有無を判定して、ユーザーにメッセージを表示するというのが良いのかもしれません。
window.open() を使わずに、$("form").submit() とする方法も試してみましたが、こちらもやはり開けるタブはひとつだけで、残りはブロックされてしまいます。

2012-03-12

JavaScriptでtextareaに入力中の文字数をカウントする

textareaなどに入力された文字数を表示する場合は、次のようにchangeイベントとkeyupイベントpasteイベントをハンドリングします。changeイベントだけだと、入力欄からフォーカスが外れて編集モードが終了するまで文字数が更新されなくなってしまうので、keyupイベントもハンドリングすることが必要です。また、テキストエリアに文章が貼り付けられた場合に備えて、pasteイベントもハンドリングします。
$("input[type='txtarea']").bind("keyup change paste", function() {
    var txt = $(this).val();
    var len = txt.length;
    console.debug("the text length is " + len)
});


2012-03-10

Bing Translator APIのAccess Tokenをpythonで取得

こんな感じです。

import urllib

def _retrieve_access_token():
    data = dict(client_id="my_client_id",
                client_secret="my_client_secret",
                scope="http://api.microsofttranslator.com",
                grant_type="client_credentials"
                )
    data = urllib.urlencode(data)
    resp = urllib.urlopen("https://datamarket.accesscontrol.windows.net/v2/OAuth2-13", data)
    return resp.read()

もちろん "my_client_id" と "my_client_secret" の部分は、Azure Marketplaceで取得した値を設定しなくてはいけません。
それなりに時間がかかる処理なので、キャッシュを有効に使いたいところです。

戻り値は、
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">Good afternoon</string>
のようにタグで囲まれているので、ここから翻訳結果の文字列だけを取り出してやる必要があります。

酔った勢いでサーバー更新しようとして失敗

お酒飲んで酔っぱらった状態でサーバーのデーターを更新しようとしたら、どこかでエラーになってしまうらしく、エラー通知メールが何十件も届きました。関係者の皆様方に謝罪いたします。すみません。
データとプログラムはすでに元に戻しましたので、いまは正常に動いているはずですが、Pocket WiFi経由でアップロードしたために、復旧が少し(数十秒)遅れてしまいました。
ごめんなさい。明日ちゃんとテストします。

2012-03-08

django-grappelliでレイアウトが崩れてしまう場合

djangoの管理コンソールのテーマをカッコ良くできるという触れ込みのdjango-grappelliを試してみたのですが、レイアウトが崩れてしまいました(FireFoxとChromeで確認)。一度は諦めかけたのですが、インストールガイドを再度読んでみたところ、INSTALLED_APPで'grappelli'を'django.contrib.admin'よりも先に読み込まなければいけないと書かれているのを読み落としていたことに気づきました。この部分を修正したら、ちゃんと表示されました。Django 1.3.1と、Grappelli 2.3.7 の組み合わせで確認しました。

しかし、選択中の項目がハイライトされないので、タブキーを使ってキーボードで操作するには不向きな管理画面になってしまうという問題点があります。

django-registrationの使い方

djangoでユーザー登録機能を簡単に追加できるプラグインdjango-registrationの使い方を調査中です。
pipからもインストールできますが、pipでインストールできる安定版バージョン0.7と、bitbucketでダウンロードできる0.8alpha-1とでは、だいぶ設定が異なるようです。

デフォルトでは次のように動作します。
  1. ユーザー名、メールアドレス、パスワードでユーザー登録情報が送信される
  2. Userオブジェクトが生成され(この状態ではis_activeがFalse)、ユーザーに確認メールが送信される。
  3. 確認メールの中のリンクがクリックされるとそのユーザーのis_activeがTrueとなってログインを行えるようになる

機能させるには、settings.pyの変更、DBの更新、urls.pyの変更、templateの作成の4つが必要です。

settings.py

INSTALLED_APPSに、"django.contrib.auth"、"django.contrib.sites"、"regstration"を記載します。
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.sites',
    'registration',
    # ...other installed applications...
)
ACCOUNT_ACTIVATION_DAYS = 7 # One-week activation window; you may, of course, use a different value.
ACCOUNT_ACTIVATION_DAYS は、確認メールの有効期間(日単位)です。

urls.py

'registration.backends.default.urls' をincludeします。bitbucketでダウンロードできる最新版のバージョン0.8では次のようにします。
urlpatterns += patterns('',
    (r'^accounts/', include('registration.backends.default.urls')),
)
pipでインストールされるバージョン0.7では、'registration.backends.default.urls' ではなく 'registration.urls' としなければなりません。バージョン0.8で 'registration.urls' をincludeした場合は警告は出ますが一応動作はすると思います。

tempates

templateを作成するのはめんどうなので、手っ取り早く動作させたい場合は、以下でダウンロードできるテンプレートのどれかを使うと良さそうです。(ただし、バージョンごとテンプレートの使用が異なるようなので、正常に動作しない可能性が高いです。)
https://github.com/dokterbob/django-registration-templates (古い?)
https://github.com/banterability/django-registration-templates (使用可能な変数がコメントアウトして書かれているだけ)
https://github.com/yourcelf/django-registration-defaults (未確認)
https://bitbucket.org/cliff/django-registration-templates (未確認)

登録するユーザー情報が、ユーザー名とメルアドとパスワードだけでは足りないという場合は、UserProfileを使います。

OSが古くてXcodeをアップグレードできない

Mac OS X 10.6 (Snow Leopard)を未だに使っていますが、これだとXcode 4.3.1をインストールできないみたいです。
Appleからリリースされる製品は、すでにSnow Leopardがサポート外になっていることが多いみたいですね。こういうのがApple流なんでしょうか。

2012-03-07

Djangoで開発サーバーのみDEBUG=Trueとなるように設定

settings.pyで行うDjangoのデバッグ設定ですが、開発サーバーでのみDEBUG=Trueで本番サーバーやテストサーバーではFalseになるようにしておくのがいいと思います。手動で値を書き換えていると、DEBUG=Trueのまま本番サーバーにアップロードしてしまうリスクがありますが、次のような設定しておけば安心です。「/myname/」の部分には、開発しているソースコードのパスには含まれるが本番サーバーやテストサーバー上のパスには含まれない文字列を指定します。
import os
PROJECT_DIR = os.path.abspath(os.path.dirname(__file__))
DEBUG = "/myname/" in PROJECT_DIR
TEMPLATE_DEBUG = DEBUG

開発サーバーで静的ファイルの配信を行う場合、DEBUG=Trueでないといけなかったりするので、DEBUG変数の設定は重要です。

2012-03-06

たぶんSugarSyncのせいでファイル名が勝手に大文字に変わる

たぶんSugerSyncで同期しているせいだと思うんですが、ファイル名やディレクトリ名が勝手に大文字に変わります(Mac)。Finder上では小文字のままでも、他のプログラムで読み込むと大文字だったりしてOS Xの仕組みに詳しくない僕にはわけがわかりません。

原因はよく分かりません。どいういう操作をすると大文字になってしまうのかもよく分かりません。しかし、どうもSugarSyncが悪さをしているように思えて仕方がないです。gitとSugarSyncを組み合わせて使っているのが良くないのかもしれません。

すごく不便だということはハッキリにしています。pythonのコマンドがエラーになったり、Eclipseでビルドに失敗したりで、作業効率を下げます。

SugarSyncでバックアップをとりつつ作業できれば安心だと思っていましたが、この弊害の原因かもしれないので、もうやめます。SugarSyncは立ち上げません。データを壊してしまうのでは、いくらそれをバックアップしても意味ないですから。

Dropboxもあまり信用していません。以前にパソコンのリカバリをしたときに、Dropboxのデータで環境を元に戻そうとしたら、破損しているファイルがあったからです。

どこかでサーバーを借りて、そこでバックアップをとるのが良さそうです。

さて、ファイル名やディレクトリ名が大文字に変わってしまった場合はどうすればいいのでしょうか?今回は、Finder上では小文字のままなのに、pythonのプログラムからは大文字になってしまうディレクトリを含むプロジェクトに対処します。gitレポジトリを持っています。
まずプロジェクトのフォルダの名前をFinderで変更して、その後、gitでcloneして元々の名前と同じプロジェクトをつくります。これで直りました。gitなんてそもそも使っていないという場合はどうすればいいのかは知りません。

2012-03-05

iPhone Simulatorでスクリーンショットを撮る

iPadを持っていないのにAppStoreにiPad用のスクリーンショットをアップロードしなくてはいけないといった場合には、iPhone Simulatorでスクリーンショットを撮ればいいのです。

iPhone Simulatorを起動させて、Cmd+Ctrl+C を押すとスクリーンショットがクリップボードに保存されます。
あとは、プレビュー.appを起動して Cmd+N を押すと、クリップボードの内容で画像が新規作成されるので、これを保存すればOKです。なお、プレビュー.appで画像の向きを変える場合は、Cmd+L や Cmd+R です。

Cmd+Shift+4 などでは適切なサイズのスクリーンショットをなかなか撮れません。Cmd+Ctrl+C を使うのが正解でしょう。

『一挙に英語翻訳』をAppStoreに申請

『一挙に英語翻訳』をAppStoreに申請しました。特に問題がなければ、10日ほどで公開されるはずです。

実はまだ、アプリのスクリーンショットを撮っていません。一枚だけ仮にアップロードしたスクリーンショットで申請を行っています。どうせ待ち日数が長いので、その間にスクリーンショットを用意すればいいだろうという考えです。

申請した後でスクリーンショットを用意することによって、無駄な待ち時間をほんの少し短くすることができるはずですが、同じことをアプリ名や、紹介文の多言語対応、キーワードなどで行おうとしてはいけません。これらは一度レビューが始まると変更できなくなってしまうからです。

iPhoneアプリは出だしが肝心です。下手な失敗をしないように気をつけたいものです。

warning: iPhone apps with a deployment target lower than 4.3 should include an armv6 architecture

Xcode4.2で作成したプロジェクトをReleaseモードでビルドすると、次のような警告がでることがあります。
Check dependencies
[BWARN]warning: iPhone apps with a deployment target lower than 4.3 should include an armv6 architecture (current IPHONEOS_DEPLOYMENT_TARGET = "4.2", ARCHS = "armv7").
これは、Deployment TragetをiOS 4.3よりも古いものに指定している場合に出る警告です。アーキテクチャに「armv6」を含めないといけないと言っています。
このままだとバイナリをAppStoreにアップロードするときに失敗してしまいます。Build SettingsのArchitecturesに「armv6」を追加してやれば、警告が消えてバイナリをアップロードできるようになります。

iOS4.1以下で Symbol not found: _ADBannerContentSizeIdentifierPortrait

iAdを組み込んでいるのですが、iOS4.1以下のシミュレータで起動を試みると、次のようなエラーになって起動できません。

iOSアプリ用のタブバーアイコンとツールバーアイコンを作成

iOSアプリのタブバーとツールバーに表示させるアイコンづくりをしました。ガイドラインはこちら

タブバー用アイコンのサイズは、30x30pixと、60x60pix (retina用) です。 一方、ツールバーとナビゲーションバー用はどちらも20x20pixと40x40pix(retina用)です。タブバー用は、これよりサイズがちょっと大きいですね。アイコンセットを購入するする場合は注意したいところです。

アイコンを着色しても反映されません。アルファ(透明度)だけで表現を行います。ガイドラインには白色だけを用いるように書かれていますが、実際には白色でなく黒色でも問題ありません。ネット上で配布されているフリーのアイコンセットでも黒色を利用しているものがあります。

影などのエフェクトは、システムが勝手につけてくれるので、アイコンの作成者がつける必要がありません。

自作したツールバーのアイコンを、システムの用意しているアイコンと混ぜて使う場合は、アイコンのサイズもできるだけ揃えたいところです。ブラウザのreloadとstopLoadingにシステムのアイコンを使用して、goBackとgoForward用のアイコンを自作したのですが、横幅を33ポイントにしたら大きさが統一されて見えました。

2012-03-04

一挙に英語翻訳 for iPhoneのアイコン作成中

iPhone/iPad向けの一挙に英語翻訳のアイコンを作成しています。
Android向けとほぼ同じですが、iPhoneアプリでは背景を透明にできないため、この部分をどうにかしないといけません。
こんなものができました。通常、iPhoneアプリでは上部の丸っこい光のエフェクトが自動で追加されるのですが、今回は国旗の色を変えずに背景の水色だけ変化させたかったため、自分でエフェクトをつくりました。

デフォルトのエフェクトをOFFにするには、Info.plistに「Icon already includes gloss effects」という項目を追加して値をYESに設定します。
あとは、タブ用のアイコンをつくれば完成というところまで漕ぎ着きました。

WYSIWYGのフリーHTMLエディタBlueGriffonが便利になった

いつの間に改善されたのかよく知りませんが、フリーのHTMLエディタBlueGriffonが実用に耐える便利なエディタになっていました。これはかなり嬉しいです。
BlueGriffonは、KompoZer(すでに開発が中止されている)の後継であり、レンダリングエンジンはFireFoxと同じGeckoです。軽快に動作する無料のWYSIWYGのHTMLエディタは他には無いような気がします。Windows、Mac、Linuxのすべてで動作するので、まだ使ったことがない人は試してみると良いかと思います。



以前は、日本語入力をするときに勝手に改行されるために余計な半角スペースが挿入されてしまって、残念な感じだったのですが、いまは設定で改行を無効にすることができます(文章の編集をプレビュー画面で行えば、画面幅で改行されるので文章作成で不便を感じることはありません。改行が無効にされるのはソースコードだけです)。

余計なスペースが勝手に挿入されるのを防ぐには、設定ダイアログの文書タブを開いて、[Wrap long lines]のチェックを外します。
こうしておけば、日本語での入力でもまったく問題ないです。
普段使いのエディタとしても十分使えそうです。


2012-03-03

iPhoneとiPadではiAdのサイズが違う

iPhoneとiPadでは、iAdのサイズが違います。
iPhoneでは、縦向きのとき320 x 50 points、横向きで480 x 32 pointsです。
一方、iPadでは、縦向きで768 x 66 points、横向きで1024 x 66 pointsになります。

これに合わせて、処理を変更する必要がある場合があります。AdStirを利用している場合は、サイズが320 x 50に固定なのでiPad用に広告を表示するのは控えた方が良さそうです。

iPhone(iPod touch)であるか、iPadであるかは、次のようにして判定できます。

iOSアプリの国際化(ローカリゼーション)

iOSのアプリを多言語対応させるには、NSLocalizedStringを使います。プロジェクトのコードに @"Hello World" という具合に文字列が使われているところを、NSLocalizedString(@"Hello World", @"comment") と書き換えます。
第2引数は単なるコメントなのでnilでも問題ありません。
第1引数の文字列をキーとして、多言語化が行われます。キー@"Hello World"に対応する翻訳がLocalizable.strings というファイルに記述されていない場合は、@"Hello World"というキーがそのまま使われることになります。

Localizable.stringsですが、これを生成するには、genstringsというコマンドを使うのが便利です。
ソースコードを記述してあるプロジェクトのソースコードを含んでいるディレクトリに移動して
find . -name '*.m' | xargs genstrings
を実行すると、.mファイルを検索してNSLocalizedStringと、CFCopyLocalizedStringを探し出して、Localizable.stringsを生成してくれます。
genstrings '*.m'
だけだとサブディレクトリを見てくれません。
あとは、Localizable.stringsをXcodeに加えて、ファイルをローカライズして、それぞれの文字列を翻訳して、ビルドします。
genstringsを複数回実行すると、以前に生成されたファイルが上書きされることになります。ファイルをローカライズしておけば別フォルダに移動するので、編集前にローカライズしておいた方が安全でしょう。

プロジェクトを開始したら、後で多言語対応しようと考えている部分はすべてNSLocalizedStringを使って記述することをおすすめします。翻訳自体は、プロジェクトが完成に近づいてきた頃に行うのが効率がいいと思います。

xibを使っている場合、その中に現れる文字列をどのように国際化するかということが問題となります。xibファイル自体を国際化することもできるのですが、これをやると管理が大変です。viewDidLoadで国際化する文字を再設定した方がいいでしょう。

あと、ホーム画面に表示されるアプリ名の国際化ですが、これはLocalizable.stringsではなく、InfoPlist.stringsでやります。
CFBundleDisplayName = "アプリ名";
といった具合です。

iOS版の一挙に英語翻訳の残りタスク

そろそろiOS版の一挙に英語翻訳も仕上げに入りたいところです。残りのタスクは、

  • 国際化対応
  • タブ画像の設定
  • ブラウザボタンの仕上げ
  • 翻訳サイトを切り替えた際に自動的にリロードさせる
  • アプリのアイコン作成
  • iPad対応
といったところです。完成もそう遠くないと思います。

ブラウザのボタンは背景な透明なUIToolbarにします。UIToolbarを使うと、ボタンを半透明にしたかったり、デバイスが横向きのときに背後のUIWebViewを反応させられなかったり、あるいはシステムボタンを使うと大きさが統一されなかったりと、いくつか問題点はあるのですが、それほど悪い出来ではありません。特に、UIWebViewの替わりにUIBarButtonItemが反応してしまうというのは、その逆に比べれば遥かにマシです。
iPadでは、黒い半透明のUIToolbarを使いたいと思っているので、iPadへの対応を考えた場合も、UIToolbarを使う利点があります。

タブの画像は、翻訳サイト用と辞書サイト用の二種類そろえれば十分だと考えています。同じアイコンがタブに並ぶのは、もしかすると変かもしれませんが。

検索履歴を保存できるようにしたいという考えもありますが、これについては、次のフェーズで対応することにします。


shouldOverrideUrlLoadingとshouldStartLoadWithRequestの違い

AndroidのWebViewのメソッドであるshouldOverrideUrlLoading(WebView, String)と、iOSのUIWebViewのwebView:shouldStartLoadWithRequest:navigationType: は、似たようなことを行いますが、重大な違いがあります。

AndroidのshouldOverrideUrlLoadingは、iframeのURLを読み込むときは呼ばれません。それに対して、iOSのhouldStartLoadWithRequestは、iframeの読み込みでも呼び出されます。

たとえば、自作アプリでexample.comのWebページだけを表示して、よそのページへのリンクはデフォルトのブラウザで表示するという仕様を考えてみます。
こんなときAndroidでは、shouldOverrideUrlLoadingでURLをチェックして、ホストがexample.comでなければIntentを投げるというふうにすれば実現できます。
しかし、iOSのwebView:shouldStartLoadWithRequest:navigationType: で同じことをやると、iframeまでSafariで開こうとしてしまいます。example.comのサイトが更新された場合に、問題を引き起こす可能性があります。
iOSの場合どうすればいいのかは、よくわかりません。

AdSenseなどの広告でiframeを用いている場合もあるので、十分に注意が必要です。

開発者のアプリを一覧表示するAppStoreへのリンクを開けない

iPhoneアプリに、自分がAppStoreにあげているアプリの一覧ページへのリンクを埋め込もうと考えたのですができません。
パソコンからだったら、たとえば http://itunes.apple.com/artist/qpen.net/id425654442 というのリンクをクリックすれば、iTunesを開いて適当に処理してくれるはずです。しかし、iPhoneからだと同じリンクを開くことができません。

そもそもiOS用のAppStoreに、同じ開発者によるアプリを一覧するページがあるかどうか知りません。無いのなら表示できないのも仕方が無いことです。

UIButton背後のUIWebViewのリンクが反応してしまう場合

画面を広く使えるように、UIWebViewの上に小さなボタンをいくつか並べようと考えています。

UIToolbarを使わず、UIButtonを直接UIWebViewの上に配置する計画です。小さくするのは画像だけで、ユーザーのタップに反応する部分は通常のサイズのボタンを考えていたのですが、これが予想に反してなかなか上手く行きませんでした。ボタンをクリックしたつもりだったのに、背後にあるWebページのリンクがクリックされてしまうということが頻繁に起こってしまったのです。普段はボタンクリックになる場所をクリックしても、背後にリンクがあるとそっちが反応しまうといった具合です(シミュレータ上で試したので、指のタップと違ってクリックの位置をかなり正確に制御できていたはず)。これでは、操作上問題があります。

こういう場合は、UIButtonの背後に、透明はUIViewを置いたらいいようです。こうしておけば、背後のリンクが意図せずに反応してしまうということを防げます。

2012-03-02

WebViewで文字が重なってしまうらしいが原因不明

WebViewを使っているAndroidアプリで、文字が重なってしまうという報告が韓国からありました。対応したいのですが、困ったことに機種依存のようで、こちらでは現象を再現することができません。問題の起こっているのは、Samsung Galaxy S2LTEだということです。韓国でだけ(あるいは韓国語表記でだけ)生じる現象なのかどうかは未確認です。

ほとんどWebViewでHTMLを表示しているだけのアプリなので、とりあえずデフォルトのブラウザで同じページを開いてみて問題が起こらないかを確認してもらいます。けど、おそらくダメでしょう。こちらでできることとしては、
  1. エミュレータのいろいろなバージョンで確認してみる
  2. 電気屋に行ってGalaxyS2を試してみる
  3. CSSでline-heightを明示的に指定する
  4. どこかに怪しげな処理がないかソースを確認する
くらいでしょうか。結局迷宮入りになってしまうような予感がしますが。

危うくline-heightを指定する際の注意点を忘れるところでした。line-heightにはemなどの単位を指定しないという注意点です。

* line-heightを明示的指定してサーバーを更新しました。まだ問題が生じるか確認してもらいます。line-heightのデフォルト値は、normalという値なので、明示的に高さを指定すれば、いい結果が得られるかもしれません。
WebViewには、Androidバージョンに依存するバグが潜んでいることがあるようです。以前に気づいたのは、Android3.xでJavaScriptのStringのreplaceで、第2引数に関数を渡すと正常に処理してくれないというのがありました(エミュレータで確認)。

iOSでRetina用の画像が表示されない場合

UIImageNamedで画像の名前を指定すると、Retinaディスプレイでは自動的に@2xの方が読み込まれるはずです。しかし開発をしていると、そうなってくれないことがたまにあります。
そんなときは、画像の名前を変えてみると上手くいくかもしれません。以前に読み込まれたリソースがどこかにキャッシュされていて、なかなか消えてくれないことがあるようです。
これはiOS4.0のバグなんだと言っている人もいますが、おそらくキャッシュが原因だと思います。

UITextViewに角丸のボーダーをつける

iPhoneアプリの開発で、UITextViewに枠線をつける場合は次のようにします。ボーターの太さや色、角の丸みも設定することが可能です。

まず、<QuartzCore/QuartzCore.h> をimportします。そうすると、UITextViewのプロパティのlayerにいろいろと設定できるようになります。こんな具合にやります。
txtView.layer.borderWidth = 1.0f;    //ボーダーの幅
txtView.layer.cornerRadius = 10.0f;    //ボーダーの角の丸み
txtView.layer.borderColor = [[UIColor lightGrayColor] CGColor]; //ボーダーの色 
これだけです。
<QuartzCore/QuartzCore.h> をimportしていないとlayerをいじれないので、そこだけ注意が必要です。

一挙に英語翻訳 for Android

一挙に英語翻訳』をAndroidマーケットで公開しました。

一挙に英語翻訳


『一挙に英語翻訳』なら、文章を一度入力するだけで、複数の翻訳サイトを調べられます。たとえば、Google翻訳が意味不明な場合にも、タブを切り替えるだけでExcite翻訳やLivedoor翻訳の結果を見ることができるんです。

オンライン翻訳サイトは便利ですが、まだまだ精度が低くて意味の通じる翻訳結果が得られないこともありますよね。でも、複数の翻訳サービスの結果を見比べていけば、だいたい意味が通じてくるものです。いろいろな翻訳サイトを調べるのはめんどうくさいと思うかもしれませんが、『一挙に英語翻訳』をインストールしておけば心配いりません。タブを切り替えるだけで、同じ文章に対するGoogle翻訳、Excite翻訳、Livedoor翻訳、@nifty翻訳、Dictionary.com翻訳などの翻訳結果を簡単に見比べることができます。

辞書を引きたくなった場合もNAVER英語辞書に収録されている、英和・和英・英英辞典を簡単に調べられます。
日本語から英語にも、英語から日本語にも翻訳可能です。学習に、仕事に、あるいは海外の友達とのコミュリケーションに、『一挙に英語翻訳』を役立ててください!


■ 近日中に次のアプリをリリース予定です。ご期待ください!
  • 一挙に英語翻訳 iPhone版
  • 一挙に韓国語翻訳、一挙に中国語翻訳、一挙に英独翻訳など
  • 一挙に英語ページ翻訳(英文のページをまるごと日本語に翻訳)

2012-03-01

Androidにデバッグ設定用の隠しActivityをつくると便利

Andridアプリをリリースする前に、テスト用サーバーに接続したり、内部処理で使うパラメータを一時的に変更して動作確認をすることはよくあることだと思います。
このとき、一時的にせよ直接コードを変更して動作確認を行うのは嫌なものです。ビリドをやり直すのが面倒くさいですし、間違えてデバッグ用の設定のままアプリをリリースしてしまうリスクがあります。
そこで、デバッグ用の設定を行う隠しアクティビティを用意しておくといいと思います。特別なIntentを投げない限り表示されないActivityを定義しておいて、そこで接続するサーバーのURLや各種のパラメータを設定できるようにしておきます。この設定画面は、ユーザーが通常の使い方をしている限りは表示されない画面なので、呼出し用アプリを専用につくっておくことになります。(ユーザーが絶対に表示できないというわけではないので、本当に大事なデータは編集できないようにしておいた方がいいです。)

Android Marketで公開準備中apkの削除ボタンに気をつける

Androidアプリが完成してAndroidマーケットにアップロードした後で、必要な画像や文章が揃っていなかったと気づくことがあります。そんなときは、アプリを公開せずに下書きのまま保存して、必要な画像などを準備することになるかと思いますが、このとき注意しなければいけないのは、下書きのままアップロードしてあるapkを削除しないということです。バグを修正したapkをアップロードしようとして、下書きのapkを削除するとアプリの紹介文や画像なども一緒に消えて、最初からアプリの登録をやり直さないといけなくなります。大した問題でないと言えば大した問題ではないですが、これが結構めんどうくさいです。バグ修正したapkをアップロードする場合は、アプリのバージョンをあげて更新ということにしないといけません。

軽い気持ちでAndroidデベロッパー名を変更して失敗

AppStoreの場合は、一度登録した開発者名を変更できませんが、Androidでは可能です。
そこで、軽い気持ちで登録してあるプロフィールの大文字と小文字を変更してみたのですが、これが大失敗でした。いままでリリースしたアプリの中に、デベロッパー名でAndroid Marketを検索するリンクを埋め込んでいたからです。当然のように検索結果が何も表示されなくなっていました。
何にせよ、深く考えずに変更を行うと、思わぬところで副作用がでるものですね。今後は気をつけなければ。

AdStir Android SDKについて

AdStirのAndroid SDKが頻繁に更新されています。AdStirは、複数の広告ネットワークを組み合わせて広告配信できるサービスです。海外だとAdWhirlが有名なようですが、これは国内の広告ネットワークにデフォルトでは対応していません。

将来性のあるサービスだと思うのですが、AdStir Android SDK v1.2.8までは、ActivityのonPauseが一度呼ばれると、マニュアルの指定どおりにonResumeで処理を行っても広告が再表示されないという、かなり致命的なバグがありました。v1.2.9ではこれに対応したとのことですが、onPauseとonResumeでアプリ開発者が書かねばいけない処理が変更されたので、これまですでにAdStirを組み込んでいたりテスト用のコードを書いていた人は、それを書き直す必要があるようです。コードを変更せずにSDKだけ差し替えた場合はどうなってしまうのかについては知りません。

今回リリースするアプリは、すでにAdMob一本に差し替えてしまったので、とりあえずこれでリリースする予定です。AdStirもだいぶ安定してきた印象を受けますが、まだちょっと不安が残ります。

Conyacに翻訳を依頼

Androidアプリがそろそろ完成なので、アプリの英語の紹介文をつくってAndroidマーケットにアプッロードする最終準備に入りたいと思います。
自分で書いた英語にはまったく自信がないので、翻訳を依頼します。今回はConyac(コニャック)という格安のソーシャル翻訳サービスを利用する予定です。海外のサービスかと思ったら、株式会社エニドアの運営する日本のサービスなんですね。

jQueryMobileのListviewの中で文章を改行して表示

jQuery MobileのListviewを使ったとき、長くて一行に収まりきらない文章は「...」でちょん切られちゃうんですけど、改行して文章全体が表示されるようにしたい場合があります。項目のタイトルと説明からなるリストだと、説明の末尾を「...」で切らないで複数行にわたって文章を表示した方がいい場合の方が多いように思います。

なんでちょん切られているのかというと、CSSで white-space: nowrap; が指定されているからです。こんな指定がされています。
.ui-li .ui-btn-text a.ui-link-inherit { 
  text-overflow: ellipsis;
  overflow: hidden;
  white-space: nowrap;
}

white-space: normal; を指定し直せば、ちゃんと改行されるようになります。
個人的には、.ui-li-desc はデフォルトで white-space: normal; でいいような気がします。

そろそろCentOS6の時代

CentOS5で、PostgreSQLをyumでインストールしてみました。yumを使っただけです。インストールされるバージョン8.1.23は、ダウンロードページからすでに消えているような枯れたバージョンのようです。この枯れたDBに、老いぼれたpython2.4で接続してなんてことはあまりやりたくないです。けど、インストール時に依存関係やらビルドオプションやらで予期せぬトラブルに見舞われて時間を消耗するのもいやです。

CentOS5のレポジトリで管理されているバージョンが古くさいのが嫌ってことなんですが、CentOS5自体がもう古いんですよね。
そろそろCentOS6がメインになってくる気がします。たとえば、さくらVPSのページをみたら、標準インストールはCentOS6になっていました。

CentOS6だとレポジトリはどういうことになっているのか、こんどチェックしてみます。

***
CentOS6では、PostgreSQLのバージョンは、8.4.9でした。