2012-11-19

VirtualBoxにインストールしたAndroid-x86をネットにつなぐ

Androidを起動したあと、ALT + F1 を押して、コンソールで、
# netcfg eth0 dhcp
# setprop net.dns1 8.8.8.8
とやる。元の画面に戻るには、ALT + F7 を押す。

このとき。VirtualBoxのネットワークの設定は、次のとおり、
Adapter 1: PCnet-FAST III (Bridged adapter, en0: Ethernet)
WiFiでなくて、ケーブルで繋いでいるのでちょっと不便かもしれないが、これでネットワークに繋がるはず。

Androidで、ALT + F1 のあと、
# netcfg eth0
とやればIPアドレスを取得できるので、そのIPアドレスを使ってホストマシーンで、
adb connect {取得したIPアドレス}
とすれば、デバッグなどもできるようになる。


Android-x86のダウンロードはこちら(広告が大きいので注意)
インストールなどは、次の記事が参考になる。
http://d.hatena.ne.jp/goriponsoft/20110212/1297510921
http://d.hatena.ne.jp/kikupi084/20100712/1278948166
http://stackoverflow.com/questions/10069121/no-network-in-android-x86-on-virtualbox-4-1-2

***
と、書いてきたのだが、次のサイトでVirtualBox用に最適化されたAndroid-x86が配布されている模様。これを使うのが良さそうだ。
http://www.buildroid.org/blog/?page_id=121

2012-10-25

iPad mini対応は何もやらなくてOK

最近ようやく自作のアプリのいくつかをiPadに対応させました。たとえば一挙に韓国語翻訳などです。
新たにiPad miniなるものが世に出るということで、これの対応も考えなくてはいけないのかと思って、Mac App StoreでXcodeのアップデートを確認して見ましたが、リリースはありませんでした。

これからアップデートがあるかもしれないと思って調べてみたのですが、どうやらiPad mini対応をする必要はまったくなさそうです。シミュレータは新しいのが出るでしょうが、Xcodeの更新はないでしょう。
なぜかというと、iPad miniは画面のサイズがこれまでよりも小さい7.9インチになるものの、画素数はiPad 2と同じ1024×768ドットだからです。つまり、ボタンなどのコントロールや文字がちょっと小さく表示されるだけで、他は従来のiPadなんら変わるところはないということになります。これまで動いていたプログラムは、少しちいさくなった画面の中でまったく同じように動くことでしょう。

2012-10-12

DjangoにDBのマイグレーションを行うプラグインを入れる

Djangoで manage.py syncdb をした場合、すでに存在するテーブルについては何も変更されません。models.pyをいじってスキーマを変更しても、それがDBに反映されません。
そこで、マイグレーションを行うプラグインの出番となります。どうもSouthが定番のようなので、これを入れます。

$ sudo pip install South

インストールに成功したら、settings.py を若干変更します。
INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'myapp', #自作のアプリ
    'south', #southをインストールする
    )
個人的な趣味としては、
import sys
import os
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "south_migrations"))
SOUTH_MIGRATION_MODULES = {
    'myapp': 'migrations.myapp',
}
という具合にして、マイグレーションのときに生成されるファイルが、プロジェクトのディレクトリの外につくられるようにしておくのがいいと思います。その方が、ローカルのプロジェクトをサーバー上にあげるときに都合がいいからです。

settings.py の編集が終わったら、普通に、manage.py syncdb をします。これによって、southが利用するDBのテーブルがつくられます。SOUTH_MIGRATION_MODULESを明示的に指定した場合は、親となるモジュール(上記の例ではmigrations)が存在しないとエラーになるので、ここでつくっておきます。ここまでできたら、

$ python ./manage.py convert_to_south myapp

を実行します。これにより、既存のアプリをsouthで管理するように設定できます。今後、モデルに変更があった場合は、

$ python ./manage.py schemamigration myapp --auto
$ python ./manage.py migrate myapp

とやれば、たいていはsouthが何とかしてくれるはずです。

2012-10-06

CentOSにPostgreSQLをインストール

yumでインストールします。
$ sudo yum -y install postgresql-server
依存関係により、

  • postgresql-server
  • postgresql-libs
  • postgresql-server

の3つがインストールされることになります。現在はバージョンは、8.4.13-1.e16_3です。

これでインストールはできているのですが、データベースサーバーが起動していないので、まだ利用することはできません。最初の起動のため、次の2つコマンドを打ちます。
$ sudo service postgresql initdb
$ sudo service postgresql start
ここで、initdbをせずにpostgresqlを起動しようとすると、次のようなエラーになります。
/var/lib/pgsql/data is missing. Use "service postgresql initdb" to initialize the cluster first.
                                                           [失敗]

CentOSを再起動したときにpostgresqlも自動的に起動されるように設定しておきます。
$ sudo chkconfig postgresql on

これでデータベースサーバーの起動はOKです。後は、ユーザーの設定を行います。
PostgreSQLのインストールと同時にpostgresというユーザーがつくられるようです。
$ id postgres
uid=26(postgres) gid=26(postgres) 所属グループ=26(postgres)
このユーザー "postgres" にパスワードを設定します。
$ sudo passwd postgres
パスワードが設定できたら、ユーザーを変更します。デフォルトでは、Ident認証という方式で認証を行うため、CentOSのユーザーとPostgreSQLのユーザーが一致していないとエラーになるようです。
$ su - postgres
後は、createdbでデータベースを作成したり、 psqlで接続したりできます。

Ident認証ではなく、パスワード認証に変更したい場合は、postgresqlにユーザーを追加した後で、設定を変更します。
postgresユーザーでパスワード認証を行いたい場合は、次のようにしてパスワードを設定します。
ALTER  USER  postgres  WITH PASSWORD  'newpassword';
という具合です。postgresユーザーは最初から存在するのでALTER USERですが、新規ユーザーを追加するのであれば代わりに、CREATE USER文を使います。

パスワード認証を行うためには、DBの設定も変更する必要があります。設定ファイルなどは、たいてい「/var/lib/pgsql/data/」以下にあるようです。今回修正するのは、
/var/lib/pgsql/data/pg_hba.conf
というファイルです。「local    all    ident」という行をコメントアウトして「local    all    password」を追加します。
#local   all         all                               ident
local   all         all                               password
これでパスワードで認証することができます。たとえばユーザーがpostgresのときに、
psql  -U  postgres
とやれば、postgresユーザーで接続できます。


DjangoからPostgreSQLに接続する場合は、データベースの設定に、django.db.backends.postgresql_psycopg2を指定します。さらに次のコマンドで必要なモジュールをインストールしなくてはいけません。
$ sudo yum -y install python-psycopg2

2012-10-04

DjangoでImageFieldやFileFieldが空でないか調べる

たとえば、次のようなモデルがあったとします。
class MyModel(models.Model):
    thumbnail = ImageField(upload_to="path", null=True, blank=True)

ここで、ImageFieldにファイルがセットされているかどうか見分けて処理を切り替えたい場合の判定方法ですが、単に、
m = MyModel.objects.get(id=1)
if m.thumbnail:
    file_is_set() #ファイルあり
else:
    file_is_empty() #ファイルが空
という具合に判定すればOKなようです。

2012-10-03

ローカルなプロジェクトを元に共有gitレポジトリを作成する

ローカルに作業していたプロジェクトを管理するgitレポジトリをリモートサーバー上につくるには次のようにします。

まず、サーバー側に、空っぽのgitレポジトリを作成します。
mkdir repo.git
cd repo.git
git init --bare --shared

プロジェクトをローカルでもgitで管理していなかった場合は、git initをしてから、サーバーにデータを転送します。
cd <path_to_project>
git init
git add .
git commit -m "initial commit"
git remote add origin <username>@<host>:<path>/repo.git
git push -u origin master
みたいな感じにやります。リモートサーバーにはsshで接続するので、「<username>@<host>」の部分は、sshのときと同様にユーザ名とホスト名を指定します。「<path>/repo.git」には、先ほどつくったgitレポジトリへのパスを指定します。
pushするときに「-u」を指定していますが、これは、次回から「git push」だけで「git push origin master」と同じことを実行できるようにするためのものです。

上記は、gitレポジトリをローカルに新規作成する場合ですが、場合によってはすでにgitでプロジェクトを管理しているかもしれません。この場合は、
git remote -v
とやって、originが設定されているか調べます。originに関する情報が何も出力されなければ、上と同じくgit remote addをすればよいです。しかし、どこかからcloneしたレポジトリの場合は、
origin xxx.git (fetch)
origin xxx.git (push)
というような表示で、originに関する情報が表示されます。この場合は、すでにoriginが設定されているので、
次のようにしてoriginを書き換えます。
git remote set-url origin <username>@<host>:<path>/repo.git
これでoriginが正しく設定されるので、後は
git push -u origin master
を実行すればよいです。

さくらVPS512をセットアップ

昔の書きかけのメモです。いまは、さくらVPS512というプランは無いと思います。

---

当時のさくらの標準のOSは、CentOS 6.2 x86_64 です。git1.7 、python2.6がすでに入っています。

とりあえず、
yum -y update
します。

ユーザーを追加します。「myaccount」の部分がユーザー名です。
adduser myaccount
passwd myaccount
usermod -g apache myaccount

visudo で
myacount ALL=(ALL) ALL
を追加します。

djangoをapacheで動かせるように、mod_wsgiをインストールします。
yum install -y mod_wsgi

pipも、yumでインストールできるようです。
yum install -y python-pip
ただし、yumでインストールした場合は、「pip」ではなく「pip-python」というコマンドを使うことになるようです。

次に、memcachedと、python用のライブラリをインストールします。
yum install -y memcached
yum install -y python-memcached


/etc/httpd/conf/httpd.conf
を編集します。



yum -y update
yum install -y mod_wsgi
yum install -y python-pip
yum install -y memcached
yum install -y python-memcached

とりあえず、この時点でどのようなデーモンが入っているかを調べてみます。
$ chkconfig --list 
abrt-ccpp       0:off   1:off   2:off   3:off   4:off   5:off   6:off
abrt-oops       0:off   1:off   2:off   3:off   4:off   5:off   6:off
abrtd           0:off   1:off   2:off   3:off   4:off   5:off   6:off
acpid           0:off   1:off   2:on    3:on    4:on    5:on    6:off
atd             0:off   1:off   2:off   3:on    4:on    5:on    6:off
auditd          0:off   1:off   2:off   3:off   4:off   5:off   6:off
cpuspeed        0:off   1:on    2:off   3:off   4:off   5:off   6:off
crond           0:off   1:off   2:on    3:on    4:on    5:on    6:off
haldaemon       0:off   1:off   2:off   3:off   4:off   5:off   6:off
httpd           0:off   1:off   2:off   3:off   4:off   5:off   6:off
ip6tables       0:off   1:off   2:on    3:on    4:on    5:on    6:off
iptables        0:off   1:off   2:on    3:on    4:on    5:on    6:off
irqbalance      0:off   1:off   2:off   3:on    4:on    5:on    6:off
kdump           0:off   1:off   2:off   3:off   4:off   5:off   6:off
lvm2-monitor    0:off   1:on    2:off   3:off   4:off   5:off   6:off
mdmonitor       0:off   1:off   2:off   3:off   4:off   5:off   6:off
memcached       0:off   1:off   2:off   3:off   4:off   5:off   6:off
messagebus      0:off   1:off   2:off   3:off   4:off   5:off   6:off
netconsole      0:off   1:off   2:off   3:off   4:off   5:off   6:off
netfs           0:off   1:off   2:off   3:off   4:off   5:off   6:off
network         0:off   1:off   2:on    3:on    4:on    5:on    6:off
ntpd            0:off   1:off   2:on    3:on    4:on    5:on    6:off
ntpdate         0:off   1:off   2:on    3:on    4:on    5:on    6:off
irqbalance      0:off   1:off   2:off   3:on    4:on    5:on    6:off
kdump           0:off   1:off   2:off   3:off   4:off   5:off   6:off
lvm2-monitor    0:off   1:on    2:off   3:off   4:off   5:off   6:off
mdmonitor       0:off   1:off   2:off   3:off   4:off   5:off   6:off
memcached       0:off   1:off   2:off   3:off   4:off   5:off   6:off
messagebus      0:off   1:off   2:off   3:off   4:off   5:off   6:off
netconsole      0:off   1:off   2:off   3:off   4:off   5:off   6:off
netfs           0:off   1:off   2:off   3:off   4:off   5:off   6:off
network         0:off   1:off   2:on    3:on    4:on    5:on    6:off
ntpd            0:off   1:off   2:on    3:on    4:on    5:on    6:off
ntpdate         0:off   1:off   2:on    3:on    4:on    5:on    6:off
postfix         0:off   1:off   2:on    3:on    4:on    5:on    6:off
psacct          0:off   1:off   2:off   3:off   4:off   5:off   6:off
quota_nld       0:off   1:off   2:off   3:off   4:off   5:off   6:off
rdisc           0:off   1:off   2:off   3:off   4:off   5:off   6:off
restorecond     0:off   1:off   2:off   3:off   4:off   5:off   6:off
rngd            0:off   1:off   2:off   3:off   4:off   5:off   6:off
rsyslog         0:off   1:off   2:on    3:on    4:on    5:on    6:off
saslauthd       0:off   1:off   2:off   3:off   4:off   5:off   6:off
smartd          0:off   1:off   2:off   3:off   4:off   5:off   6:off
sshd            0:off   1:off   2:on    3:on    4:on    5:on    6:off
sysstat         0:off   1:on    2:on    3:on    4:on    5:on    6:off
udev-post       0:off   1:on    2:off   3:off   4:off   5:off   6:off

httpdとmemcachedが起動するように設定します。
$ sudo chkconfig httpd on
$ sudo service httpd start
$ sudo chkconfig memcached on
$ sudo service memcached start

$ chkconfig --list | grep 3:on
acpid           0:off 1:off 2:on 3:on 4:on 5:on 6:off
atd             0:off 1:off 2:off 3:on 4:on 5:on 6:off
crond           0:off 1:off 2:on 3:on 4:on 5:on 6:off
httpd           0:off 1:off 2:on 3:on 4:on 5:on 6:off
ip6tables       0:off 1:off 2:on 3:on 4:on 5:on 6:off
iptables        0:off 1:off 2:on 3:on 4:on 5:on 6:off
irqbalance      0:off 1:off 2:off 3:on 4:on 5:on 6:off
memcached       0:off 1:off 2:on 3:on 4:on 5:on 6:off
network         0:off 1:off 2:on 3:on 4:on 5:on 6:off
ntpd            0:off 1:off 2:on 3:on 4:on 5:on 6:off
ntpdate         0:off 1:off 2:on 3:on 4:on 5:on 6:off
postfix         0:off 1:off 2:on 3:on 4:on 5:on 6:off
rsyslog         0:off 1:off 2:on 3:on 4:on 5:on 6:off
sshd            0:off 1:off 2:on 3:on 4:on 5:on 6:off
sysstat         0:off 1:on 2:on 3:on 4:on 5:on 6:off

2012-07-12

UISearchBarの検索文字に初期値を設定する

UISearchBarで検索を行う場合、通常は検索バーの初期値が空欄で、ユーザが検索文字を入力します。この挙動を変更して、検索文字の初期値に空文字以外を指定できるようにするのは思いのほか大変でした。

問題は、searchResultsTableViewが正しく表示されないことです。searchBar.text = defaultSearchWord; という処理を、searchBarTextDidBeginEditing: 、searchBarShouldBeginEditing: 、searchDisplayControllerWillBeginSearch: などに記述したのですが、これではsearchResultsTableViewが無効で検索も正しく行われませんでした。
searchDisplayControllerDidBeginSearch: でsearchBar.text を設定してやらないとダメでした。

2012-07-11

UITableViewの「結果なし」ラベルを隠す

UISearchTableViewで、検索結果がヒットしない場合に「結果なし」とか「No Result」とかいったラベルが表示されますが、これを隠したかったのでいろいろ調べてみました。

どうやら、この文字列を直接操作するプロパティなどは存在しないようです。
UILabelを探し出して非表示にするという方法が考えられましたが、これはちょっと複雑なことになりそうだったので、表示するデータがないときはダミーの行を表示するというアプローチを試みました。

表示すべきデータがない場合は、0行ではなく、空文字のダミー行が1つだけ存在するということにします。さらに、このダミー行を選択することができないようにします。これで目的は達せられたようです。
だいたい次のようなコードになります。

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    return MAX(self.dataArray.count, 1);
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString* CellIdentifier = @"SearchCandidateCell";
    UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
  cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
    }
    if (self.dataArray.count > 0) {
        Data* data = [self.dataArray objectAtIndex:indexPath.row];
        cell.textLabel.text = data.text;
    }
    else {
        cell.textLabel.text = @"";
    }
    return cell;
}

- (NSIndexPath*)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (self.dataArray.count > 0) {
        return indexPath;
    }
    else {
        return nil;
    }
}

2012-04-04

米Yahoo!のMojitoが公開されました

前に噂を聴いて気になっていた米YahooのMojitoが公開されました。
JavaScriptでクライアント側とサーバー側の両方のプログラミングを行うためのフレームワークです。サーバーサイドでは、Node.jsを使っています。YUIがベースとなっているので、クライアント側の開発にもYUIを用いるのが自然でしょう。

Node.jsの定番フレームワークとなるかどうかはまだ分かりませんが、期待するところ大です。

2012-04-03

djangoでcron

Bing Translateのアクセストークンを取得する処理に結構時間がかかるので、9分おきに自動でトークンが更新されるようにcronを設定することにしました。

まず、トークンを更新する処理をdjango-adminに追加します。名前を refresh_token とします。

そのあと、
sudo crontab -e -u root
という具合にして、root権限で実行される処理を定義するためのエディタを開いて、そこに次の記述を追加しました。

0-59/9 * * * * /path/to/manage.py refresh_token

これで9分おきに manage.py refresh_token が実行されるのですが、残念ながら上手く動きませんでした。カレントディレクトリが /root であるために、refresh_tokenが見えていないみたいです。

おそらく、cronを実行するときのディレクトリを変えるという手もあったのでしょうが、もう一個shellscriptを書いて、カレントディレクトリを変更してからmanage.py refresh_tokenを実行するようにしました。ShellScriptの中でsudoを使ったらエラーになって、ここでも若干手こずりましたが、無事に動作することを確認しました。実行するユーザーはrootでない方がいいかもしれませんが。

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でした。

2012-02-29

MacのX11で日本語入力可能にする

Inkscape、Gimpだけじゃなく、Wineもある程度使えるかもしれないので、Mac OS XのX11で日本語入力を可能にすることに挑戦します。

ネットでやり方を調べたんですが、ちょっと難しいそうでした。そこで調べたことをまとめてみます。
なお、変換ができるようになると、Shift + Space で日本語モードの切替になります。かなキーを押して変換するわけじゃないんですね。
いろいろ試したんですが、Inkscapeで日本語入力を行うことは未だにできていません。Gimpでは日本語入力できます。Wineで導入したPupSQLiteでも大丈夫でした。xtermでもできますが、入力中は文字化けしています。


変換クライアントと変換サーバー


そもそもLinuxでの日本語入力を可能にするためには、インプットメソッド(変換クライアント)と変換エンジン(変換サーバー) の二つが必要ということです。変換クライアントがユーザーと対話を行う部分で、変換エンジンは変換する漢字の候補リストを探すみたいな役割分担だと思います。この二つの組み合わせには制限があるみたいです。変換クライアントをkinput2にすると、変換エンジンにはcannaを使わないといけないようですが、cannaというのはちょっと古いみたいです。
変換クライアントがMacUIMならば、変換エンジンとしてAnthy、PRIME、SKK、Cannaなどを選べるようなので、こっちを使うのが良さそうです。ただしxtermだと文字化けします。


.bashrc と .bach_profile


.bashrc と .bach_profileは、両方ともLinuxの設定ファイルで、ユーザのホームディレクトリにあります(隠しファイルなので、デフォルトのFinderの設定では見えないです。ターミナルで ls -a とやれば表示されます)。.bach_profileは、ログイン時だけに実行されるファイルです。一方、.bashrcは別のシェルが起動したときにも実行されます。ログイン時にも .bach_profileから読み込ませるという形で実行するのが一般的なようです。


launchctl


launchctlは、Macでデーモンを管理するプログラムらしいです。
たとえば、sudo port load canna とやれば、cannaをデーモンとして起動・登録することができます。解除する場合は、sudo port unload canna です。


---
書いている途中で力尽きました

AppStoreとAndroid Marketへのリンク

新しく作成するアプリの告知ために、以前につくったスマホ用Webページに、Android Marketへのリンクを追加する予定です。

しかし、最近のAndroid Marketは、新登場のアプリに冷たい気がします。以前は、新しいアプリを一覧表示するページがあって、そのページビュー数がなかなかのものだったと思うんですが、いまはAndroid Marketのつくりが変わって新しいアプリの紹介ページのページビューが減っているんじゃないかと思われるうえに、新しくリリースされたもののうち人気のあるものしか一覧に載らないみたいです。たぶん、人気があって品質の高そうなアプリをユーザに紹介するという考え方なんでしょうが、勝者がすべてを独占することになります。ここに食い込むのはなかなか厳しそうです。アプリの広告を打ってもコンバージョンがあまり見込めないので、リスクをしょって大量に広告を打つことでランキングに食い込むことを目指すという方針でないとたぶん採算がとれません。以前にリリースしたアプリで告知を行えば、余計なお金はかからないのでやってみます。こういうことはある意味、アプリに機能を付け足すことより大事かもしれないです。

WebページはjQuery Mobileでつくっていますが、サムネイルつきのリストを作成するには簡単なマークアップを行うだけです。これだけなら楽勝です。
しかし、リンク先がAndroid MarketだったりAppStoreだったりすると、ちょっと話がややこしくなります。ユーザーエージェントを見てリンク先を変えないと、ページを読み込めずにエラーになってしまうからです。WbViewやUIWebViewを組み込んだアプリでも、自前で処理を追加しないとページを読み込めずにエラーになります。どのブラウザからのアクセスをどこにリンクさせるか、という方針を決めないとわけが分からなくなりそうです。

まず、Android Marketには、Web版と、アプリ版(?)の二つがあります。
Web版はどんなブラウザからも表示できますが、アプリ版はAndroid端末でないと開けません。Android端末でWeb版アクセスするとアプリ版にリダイレクトされるという機能が以前はあったような気がするのですが、いまはないようです。Android端末からでも、デフォルトブラウザであればアプリ版へのリンクを開けますが、それ以外のブラウザやアプリからは開けない可能性もあります。

ということで、次のように場合分けして考えます。
  1. iPhone/iPad/iPodからのアクセスならばリンクを表示しない
  2. Androidマーケットへのリンクを開けそうな場合は、アプリ版のマーケットへのリンクを表示
  3. それ以外はWeb版へのリンクを表示
それほど複雑じゃなかったですね。

ちなみに、WbViewでAndroid Marketへのリンクを開くためには、WebViewClientの、shouldOverrideUrlLoading(WebView, String) をオーバーライドします。URL文字列が "market://" で始まるようであれば、次のようにしてIntentを投げます。FLAG_ACTIVITY_NEW_TASKなどのフラグは不要だと思います。
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(urlStr));
activity.startActivity(intent);
return true;

UIWebViewでAppStoreのリンクを開く場合も考え方は同じです。UIWebViewDelegateの、webView:shouldStartLoadWithRequest:navigationType: で、AppStoreへのリンクであるかを判定して
[[UIApplication sharedApplication] openURL:url];
とやります。AppStoreへのリンクであれば、@"http://itunes.apple.com/"か、@"http://phobos.apple.com" でURL文字列が始まるはずです。

Wineで日本語表示

Wineで、PupSQLiteというSQLiteのクライアントソフトが使えるか試そうと思い立ちました。スマホのアプリや、小規模のWebサービスだとSQLiteが結構便利なんですが、Mac/Linuxのクライアントソフトだと、DBテーブルの再定義ができません(列を追加したり名前を変える程度ならできますが、それが限界です)。SQLite自体に、テーブルを再定義する構文があまり備わっていないので当然といえば当然なのですが、PupSQLiteだと、派手にテーブルを変更することができるんです。たぶん、テーブルを新しくつくりなおして、それに既存のデータを挿入するという力技で処理しているんだと思います。

PupSQLiteには、.NET Framework 4.0ベースのものと2.0ベースのものがありますが、wineだと2.0ベースの方が安定して動きそうな気がして、こちらをダウンロードしました。winetricksで、.NET Framework 2.0をインストールした後に、PupSQLiteを起動したらいちおう立ち上がってくれました。wineだと、起動時にエラーにならないだけでも嬉しくなります。
しかし、メニューがみんな文字化けして□になっていてとても使えたものではありません。wineの日本語環境を整えないといけないみたいです。

ネットで調べたらレジストリを変更する方法や、~/.wine/user.regに設定を追加する方法などが見つかったのですが、なんだかめんどうで難しそうでした。しかし、文字化けの原因は、MSゴシックやMS明朝などがwineにインストールされていないことだろうと見当がつきました。だったら、Windowsからこれらのフォントをコピーしてwineにも置いてやればいいはずです。ライセンス的に許されているのかどうか知りませんが、¥windows¥Fontsに置かれているフォントファイルをコピペして再起動したら、メニューを無事に表示できました。

試しにデータベースを読み込ませてみたらちゃんと表示できました。韓国語のフォントが入っていないためか、韓国語の部分が文字化けしていましたけど。
複雑なことはやっていませんが、タブを開いたり閉じたりはできました。もしかしたら実用レベルなんじゃないかと、期待がもてるくらいです。

こうなってくると、日本語で入力する環境も整えたくなってきます。MacでUnix環境が整うと、実は結構うれしいことが多いかもしれないです。

2012-02-28

UIWebViewのエラーハンドリングを見直す

iPhoneアプリで、UIWebViewのwebView:didFailLoadWithError: を、いままで次のように書いていました。
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    if (error.code != -999) {
        // ユーザによる意図的な中止でない場合はエラーダイアログ表示
  NSString *errMsg = [error.userInfo objectForKey:@"NSLocalizedDescription"];
        UIAlertView *dlg = [[[UIAlertView alloc] initWithTitle:@"Error" message:errMsg delegate:nil cancelButtonTitle:@"OK" otherButtonTitles: nil] autorelease];
        [dlg show];
 }
}

しかし、これはあまり良くないですね。ユーザーが中止ボタンを押した場合以外にも正常な読み込み中断はあるのに、それらに対してもErrorダイアログが表示されてしまいます。
正常な処理なのにwebView:didFailLoadWithError:が呼ばれる例としては、音声データなどをプラグインで処理した場合(エラーコード 204)や、AppStoreへのリンクをopenURL: した場合(エラーコード 102)があります。思いつかないだけで他にもあるかもしれません。
エラーコード 204と102の場合はダイアログを表示する必要がなくて、それ以外の場合も本当にエラーなのか怪しいので、エラーメッセージを取得できなかったときにだけダイアログのタイトルに「エラー」と表示するという感じにしたいと思います。

MySQLとPostgreSQLのライセンス

MySQLとPostgreSQLでは、ライセンスが違います。
MySQLは、GPLライセンスと商用ライセンスの二つがあって、どちらにするか選べます。
PostgreSQLは、BSDライセンスです。

つまり、MySQLはオープンソースのプロジェクトであれば無料で使えますが、そうでなければ商用ライセンスを買わなければなりません。一方PostgreSQLは、どんなプロジェクトでも無料で使えます。

サーバー上で動かすプログラムをオープンソースにするのは、セキュリティ上の懸念もあって考えものです。DBが利用できればどれでもいい場合には、MySQLではなく、PostgreSQLを選んでおいた方が、ライセンス的にめんどうなことがなくて良さそうです。
また、DjangoでDBを使う場合は、特にPostgreSQLが推奨されているようです。

Android Market用のプロモーション画像を作成しました

Androidマーケットにアプリを登録する際、オプションで宣伝用の画像をアプロードできます。1024x500ピクセルのものと、180x120ピクセルのものの二つです。余計な手間をかけさせやがってと思いながらもいちおう作成しました。

2012-02-27

Ubuntuでnode.jsを試してみる、あとCloud9でも

Ubuntu 11.10にnode.jsをインストールしてみました。apt-getでいけます。パッケージマネージャのnpmもapt-getでいけます。
$ sudo apt-get install nodejs-dev
$ sudo apt-get install npm
サンプルコードとして、次のようなコードを書いて、hellonode.jsという名前で保存します。
var sys = require("sys");
var http = require("http");

var server = http.createServer(
  function(req, res)
  {
    res.writeHead(200, {"Content-Type": "text/plain"});
    res.write("Hello World!");
    res.end();
  }
);
server.listen(8080);
sys.log("Server is running at http:127.0.0.1:8080");
で、terminalで、
$ node hellonode.js
と入力したらとちゃんと動いているのをブラウザで確認できました。まあ、ちょっと遊んでみただけです。

調子に乗ってexpressもインストールしてみたけど、node.jsで遊ぶんならCloud9が手軽でいいかもしれません。ブラウザ上で動くIDEですが、意外と快適です。
ただ、正しくコマンドを打ったはずなのに原因不明のエラーになったり、あるはずのモジュールが見つからなかったりしました。しばらく待ってページを開き直して、また同じことを試してみたら上手くいきましたが。 

なお、Macでも、portを使ってnode.jsとnpmをインストールできるみたいです。

AndroidのWebViewで音声を再生する

Webページに音声を再生する仕組みが組み込まれている場合があるのですが、AndroidのWebViewは、そのままではそれを再生してくれません。何かプログラムを書いてやらなければいけないようです。

参考にすべきは、Androidの公式ドキュメントのこの記事でしょう。

音声を再生する場合、利用する主要なクラスはMediaPlayerとAudioManagerです。これらのクラスの機能を利用するために、マニフェストに記述しなければならないことは特になさそうですが、インターネット経由で音声ファイルにアクセスする場合や、音声の再生中にデバイスがスリープしてしまうのを防ぎたい場合は、それぞれINTERNETとWAKE_LOCKパーミッションを許可する必要があります。 ネット上の音声ファイルを再生するには、次のようにしてUriを指定します。

try {
    String url = "http://........"; // your URL here
    MediaPlayer mediaPlayer = new MediaPlayer();
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mediaPlayer.setDataSource(url);
    mediaPlayer.prepare(); // might take long! (for buffering, etc)
    mediaPlayer.start();
} catch(IllegalArgumentException ex1) {
    // handle error
} catch (IOException ex2) {
    // handle error
}
prepare()メソッドは時間がかかる場合があるので、UIスレッドで呼んでユーザを待たせてはいけません。あまり待たせるとアプリケーションがハングしたとシステムに判断されてしまうこともあるようです。「never call it from your application's UI thread」と太字でドキュメントに書かれています。 しかし、必ずしも開発者が別スレッドを用意しなくていけないわけではなくて、代わりにprepareAsync()メソッドを呼ぶという手もあります。この場合は、MediaPlayerの準備が整った後で実行するべき処理をsetOnPreparedListener()で登録しておく必要があります。 MediaPlayerは状態管理を行っていて、準備が整わないのにいきなり音声を再生することはできません。次のような状態遷移図を頭に入れておけとのことです。

MediaPlayerはリソースを消費するので、使い終わったらちゃんと解放してやらなければなりません。ActivityにMediaPlayerを保持している場合は、onStop()が呼ばれたら解放しないとダメです。解放するときは、
this.mediaPlayer.release();
this.mediaPlayer = null;
という具合にreleaseした後でnullを代入します。


状態遷移図を見ると、一度setDataSource()を実行してInitializedされたMediaPlayerに対して、もう一度setDataSource()を実行することはできないようです。同じMediaPlayerは、同じデータを繰り返し再生できますが、別のデータを再生する場合は、新たにインスタンスをつくらないといけないということでしょう。
MediaPlayerをActivityではなくService利用する場合は、毎回解放する必要はないみたいですが、とりあえず今回はServiceを利用しないので関係のない話です。

後は、ちょっとプログラムをいじってちゃんと音声を再生できるか試してみます。
つぎのようなコードを書いてみました。わずか数秒の音声データの再生が目的なので、最後まで再生して終了というだけのシンプルなロジックです。これを、WebViewClientのonLoadResource(WebView view, String url) の中で実行しています。URLをみて音声データだと判定できた場合にのみ実行する処理です。レスポンスのMimeTypeをチェックできたら、もっといいんですが、それっぽいイベントが見つかりませんでした。
try {
    Log.d(TAG, "Initialize MediaPlayer");
    MediaPlayer mediaPlayer = new MediaPlayer(); //idle
    mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
    mediaPlayer.setLooping(false);
    mediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
        @Override
        public void onPrepared(MediaPlayer mp) { //prepared
            Log.d(TAG, "MediaPlayer prepared");
            mp.start();
        }
    });
    mediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
        @Override
        public void onCompletion(MediaPlayer mp) { // playback completed
            Log.d(TAG, "MediaPlayer completed");
            mp.stop(); //stopped
            mp.release();
        }
    });
    mediaPlayer.setDataSource(url); // initialized
    mediaPlayer.prepareAsync(); //preparing
} 
catch (Exception ex) {
    Log.w(TAG, ex.toString(), ex);
}

軽くテストしてみたところ、いろいろと問題が発覚しました。
まず、Android1.6のエミュレータだと音声がループしてしまいます。ログを見たところonCompletionが呼ばれていないです。代わりに、「E/PlayerDriver(31): Invalid percentage value ####」というログがずっと出力され続けています。####の部分は数値で、この数値がだんだん増加していきます。
Android 2.1では、再生ができないです。「W/MediaPlayer(223): info/warning (1, 44) 」というログが出力されています。OnInfoListenerでは、MEDIA_INFO_UNKNOWN == 1が通知されます。
Android2.3だとループなく再生できますが、音声の最後が少しだけ途切れてしまいます。
上のコードでは、OnErrorListenerがセットされていませんが、onErrorListenerが定義されていなければonCompletionが呼ばれるということなので、これは問題ないはずです。
さらに調査していたら、ちゃんと再生できるデータとそうでないデータがあるということが分かりました。インターネット上のデータに依存する問題ということであきらめざる得ない部分も多そうです。リピートに関してはバックボタンで再生を中止できるようにしてお茶をにごし、再生できないデータについては潔くあきらめる、というあたりで手を打ちます。音声の最後が途切れるという問題は、ちょっとスリープしてからstop()を実行することで対処できるといえばできますが、こんなことでいいのかどうか考えものです。onCompletion()はUIスレッドらしく、その中でスリープするとその間ユーザの操作をアプリが受け付けなくなってしまうので、やるとしたら別スレッドを用意しなくてはいけません。

あと、複数のMediaPlayerを同時に起動できてしまうという問題にも対処しておいた方がよさそうですね。イベントハンドラの中でtry/catchすることも必要そうです。


Androidで同じタブがクリックされたイベントを検知する

Androidではイベントハンドラを1個しか登録できないので、onClickListenerをセットして同じタブがクリックされたイベントをとろうとすると、タブを切り替える処理が上書きされてしまうという問題があります。この問題を回避するには、setOnTouchListenerを利用すれば良いようです。 こんな具合です。
TabWidget tabWidget = tabHost.getTabWidget();
for (int i = 0; i < countOfTabs; i++) {
    View tab = tabWidget.getChildAt(i);
    tab.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            boolean consumedFlg = false;
            if (MotionEvent.ACTION_UP == event.getAction()) {
                View currTabView = tabHost.getCurrentTabView();
                if (v.equals(currTabView)) {
                    //なにかやる。v.getContext() でActivityを取得したり、tabAdapter.findFragmentAtPosition(i) でFragmentを取得したりできる
                    consumedFlg = true;
                }
            }
            return consumedFlg;
        }
    });
}

国旗アイコンづくり

昨日は、iOSアプリ用の小さなボタン画像を完成させる予定だったんですが、なんだかあまり集中できず、完成にこぎ着けることができませんでした。今日はその続きをやるべきとこかもしれないんですが、よく考えたら他にやらなきゃいけないことがたくさんありました。先日リリースしたAndroidアプリのアップグレード版をリリースして、さらに新しいアプリ用のアイコンを作成します。iOSアプリの完成までにはまだ少し時間がかかりそうなので、こっちを片付ける方が先だという判断です。

新しくつくっているのは翻訳アプリなので、日本とアメリカの国旗を二つ並べて、交互に矢印をひくというようなベタなデザインのアイコンをつくります。なぜイギリスではなくてアメリカなのかというと、アメリカ人の方が数が多いからです。

これまでは友達のデザイナさんにアイコン作りを依頼することが多かったのですが、彼が何やら忙しいらしいので自分でつくります。シリーズ化する予定のアプリなので、全部依頼していたらお金もそれなりにかかりそうですし。フリーのアイコンを利用するという手もありそうですが、イメージ通りのものはなかなか見つからなさそうなので自分でつくります。
こんなときに日本の国旗はシンプルで素晴らしいと思いますね!異国の旗はゴチャゴチャしていて困ります。

アメリカ国旗はWikipediaによると「白線と赤線の組み合わせの13本の横縞(赤7本と白6本、最初と最後は必ず赤)、四角に区切った左上部(カントン)は青地に50の白い星が配置されている。赤は勇気、白は真実、青は正義を表す。また縞模様は独立当時の13の入植地を表し、星は現在の州を表している。デザインは27回更新された。」とのことです。星は9段あって、6個並びの行と5個ならびの行が交互に来ているようです。正義の青は赤線4本分ですね。
どこまで忠実に再現するかは分かりません。アイコンなので多少単純化しても誰も文句は言わないでしょう。しかし、宣伝用に引き延ばした画像も用意するので、ある程度は現物に忠実なものを用意ておいた方が良さそうです。画像がつぶれてしまうようだったら、またつくりなおさないとダメですけど。

国旗の横縦の比率は3対2くらいでOKでしょう。国旗の背後には影を軽くつけます。表面になにか処理が必要かどうかは、シンプルなのを一つ作ってみてから判断します。矢印は立体的にしたいなあ(どうやればいいのかよく分かっていないけど)。Androidのアイコンなので、とりあえず背景は透明です。iPhoneアプリはアイコンの背景を透明にできないので、iPhone版を出すときは背景色をまた考えないといけません。

そんな感じでとりあえず作業に着手してみます。


こんなのができました。

このチュートリアルを読んで参考にしようと思ったのですが、結局ここで学んだテクニックは使いませんでした。Echo Icons Tips - Creating Echo Icons with On the Table Perspective 

MAC OSXでX11のアプリを切り替える

貧乏でPhotoshopやらIllustratorが買えないので、画像をつくるときは、InkscapeやGimpなどのフリーソフトを利用しています。
これらはもともとLinux環境用につくられたものなので、OS Xで使うときはX11の上で動作することになります。しかし、X11で起動したアプリは、は他のアプリと操作の仕方が違うのでなかなか使いにくいです。

X11で快適に作業を行うには、次のような設定とショートカットキーを組み合わせて使ったらいいのではないかと思います。

X11の環境設定ダイアログのウィンドウタブで[選択されていないウィンドウを直接クリック]にチェックを入れます。これをやらないとGimpが使いにくいです。

あと全画面でもX11を使う場合は、入力タブで[X11の代替キーを有効にする]にチェックを入れます。こうして置けば、X11内部でのウィンドウの切替を Cmd + F1 でできるようになります。各ウィンドウに番号が割り振られるようで、Cmd + 1 やら Cmd + 2 で、切り替えるウィンドウを指定することもできます。(なお、InkscapeやGimpなどのアプリ内でウィンドウを切り替えるには、Ctrl + Tabが使われることが多いようです。)
全画面から戻るときは、Cmd + Alt + A でいけます。タッチパッドをスワイプしても戻れますけど。全画面の場合は、F10で、アプリのメニューバーがフォーカスされます。

* 全画面モードにしなくても、Cmd + F10 でメニューバーを選択することができました。.xmodmapで左commandをcontrolに割り当てている場合は、右commandキーを利用する必要があります。

* X11でいちばん厄介なのは、commandキーやaltキーが効かなくなることかもしれません。 これについては、さきほどちょっと書きました(MAC OS XのX11でALTキーを有効にする


MAC OS XのX11でALTキーを有効にする(Inkscape用)


InkscapeをMacで使う場合は、altキーを有効にして置かないと不便でどうしようもないです。
ホームに .xmodemap というファイルを作成し、それに以下のような記述して保存することで、X11でaltキーが有効になります。設定を読み込ませるにはX11の再起動が必要です。(MacBook Proには右altキーはないので、keycode 69 の設定は不要かもしれません。 .xmodemap の設定の意味がよく分かっていないので変な記述が含まれているかもしれません。)

keycode 66 = Alt_L
keycode 69 = Alt_R

ちなみに、つぎの記述で左commandキーをcontrolキーとして使えるようにしておけば、Macの他のアプリと同じようなキーバインディングが使えるようになって、誤操作が減るかと思います。

keycode 63 = Control_L
add control = Control_L

ひとつ忘れていました。altキーを有効にするには、X11の環境設定ダイアログの入力タブで、「3 マウスボタンをエミュレート」と「システムのキーボードレイアウトに従う」のチェックを外して、「X11の代替キーを有効にする」にチェックを入れておく必要もあったかと思います。

X11で日本語入力を可能にする方法を紹介しているブログも発見しましたが、これはだいぶ手間がかかりそうなので、たぶんやりません。むかし挑戦して懲りました。日本語入力を可能にしたところで、いろいろと制約があってそれほど使いやすくはならなかったし。

AndroidのIntent-Filterについてまとめてみる

自分でつくった複数のアプリ間の連携を行うために、AndroidのIntentについてまとめてみる。自分用のメモなので他の人には読みづらいかも。

Intent Filterは、Actionテスト、Categoryテスト、Dataテストの3種類がある。Intentが投げられた場合、この3つのテストを通過したActivityだけが起動の対象となる。

1.Actionテスト

manifest側のintent-filterには複数指定できるが、Intentオブジェクトには一つしか指定できない。
Intentオブジェクトで指定されたactionが、intent-filterに含まれていれば受け取れる。
(intent-filterにactionが指定されていないと、Intentを絶対に受け取れない。)


2.Categoryテスト

intent-filterにもIntentオブジェクトにも複数指定できる。
Intentオブジェクトに指定されたすべてのCategoryが、intent-filterに指定されていれば受け取れる。(intent-filter側にもっとたくさんのCategoryがふくまれていてもよい。)
IntentオブジェクトにCategoryを指定しないこともできるが、Activityが明示的に指定されない暗黙的Intentの場合は、intent-filterにandroid.intent.category.DEFAULTが含まれていなければ、テストをパスしない。


3.Dataテスト

intent-filterは、mimeType、shema、host、port、path、authority(hostとportをまとめてもの)といった要素を持つ。IntentオブジェクトにはUriやDataTypeを指定する。
テストは複雑なので詳細に調べていない。


ということで、Dataテストについては、ちゃんと調べていない。しかし、DataテストはデータをUriで渡す場合に、そのデータをちゃんと処理できるかどうかでフィルタリングするというのが意図なので、Uriでデータを渡さない場合は考慮しなくてよさそう。

起動されるアプリをフィルタリングしたい場合は、Categoryテストを用いるのが正しいだろう。
たとえば、ACTION_VIEWを渡しただけでは、実にさまざまなアプリが起動の候補になってしまう。これを絞り込むには、Categoryを追加してやれば良い。

CentOS5にpython2.7をインストールする

たしかこんな感じでできたはず。

$ sudo yum install zlib-devel

$ wget http://www.python.org/ftp/python/2.7.2/Python-2.7.2.tgz
$ tar zxvf Python-2.7.2.tgz
$ cd Python-2.7.2
$ ./configure --with-threads --enable-shared
$ sed #zlib/zlib Modules/Setup
$ make
$ sudo make install
$ echo /usr/local/lib > /etc/ld.so.conf.d/python2.7.conf

2012-02-26

UIWebViewにオリジナルの小さな操作ボタンをつける

UIWebViewに戻るボタンや更新ボタンをつける予定です。こういう場合、UIToolBarをUIWebViewの下だか上だかに配置してそこにボタンを並べるのが一般的だと思うのですが、あえてそうはしません。というのは、UIToolBarはスペースをとり過ぎるからです。今つくっているアプリにはタブがあるので、そこにさらにツールバーがのっかると、広告を表示するスペースがなくなってしまい収益化が困難になってしまうという大問題が発生します。(広告なんて邪魔なだけと思う人もいるかもしれませんが、開発者にとっては重要です。収益があがらなかったら開発を続けることができません!)

Androidアプリの場合は、電話機のメニューボタンが押されたときに「戻る」とか「更新」とかメニューを表示してやればいいので普段は画面のスペースを占拠せずに機能を実装することができるのですが、iPhoneアプリではこれができないので、画面のどこかにボタンを表示してやらなければなりません。なるべく小さく表示したいと思います。Mercuryというブラウザアプリで全画面機能をつかっているときに、ボタンがUIWebView内部の下の方に、ちっちゃなボタンが並ぶのですが、だいたいそんなイメージです。

実現する方法としては、二つほど考えられます。
  1. 背景の透明なUIToolbarをつくって、そこにボタンを並べる
  2. UIWebViewの上に直接ボタンを並べる
背景な透明なUIToolbarを利用する場合の懸念点は、透明ではあってもツールバーが存在する領域がユーザーを惑わすことにならないかということです。たとえば、ボタンとボタンの間に十分にスペースがあるように見えるために、ユーザーがそこでリンクをクリックしようとするかもしれません。特に、デバイスを横向きにしたときにはそうです。
それに対して、UIWebViewの上に直接ボタンを並べる方法には、懸念すべき点があまりありません。スクリーンの向きを変えたときのアニメーションが変になるかもしれないということぐらいでしょうか。実装するのはこちらの方が時間がかかりそうですが、この方法で行きたいと思います。

まず、ボタンの画像を粗くつくって、その後それをつかって機能を実装。それからボタン画像の完成度を高めるという手順で、今日中に完了させたいです。

アイコンはいつもInkscapeというソフトで作成しています。
必要なのは、戻るボタン、進むボタン、更新ボタン、中止ボタン、メニュー表示ボタン、以上の5つです。
画像大きさはだいたい20px(ratina用は40px)くらいで良さそうです。半透明の灰色のボタン枠をつくって、それを白で切り抜くということにします。選択状態のときの画像などは、デフォルトの挙動に任せます。
アイコンはいつも無料のInkscapeでつくるのですが、PNGで出力する画像全体を透明にする方法がよく分からないので、この部分はGIMPで処理した方が良いかもしれません。

*透過画像をGIMPでつくるのではなく、UIButtonにalphaをしてしてやれば済む話かもしれません。UIBarButtonItemにはalphaを指定できませんが、UIButtonなら可能です。(ちなみにUIBarButtonItemの親クラスは、UIButtonではなくてUIBarItemです。)

背景の透明なUIToolbarをつくる

背景が透明なUIToolbarをつくるには、alphaを設定してもダメです。これだと、UIToolbarの上にのってるボタンまで透明になってしまいます。

では、
toolbar.backgroundColor = [UIColor clearColor];
toolbar.opaque = NO;
toolbar.translucent = YES;
という具合にやればいいかというと、これもダメです。普通に背景が表示されてしまいます。

stackoverflowで解決策が紹介されていました。UIToolbarを継承して新しいクラスをつくればいいようです。次のような具合にやります。

TransparentToolbar.h
@interface TransparentToolbar : UIToolbar
@end

TransparentToolbar.m
@implementation TransparentToolbar

// Override draw rect to avoid
// background coloring
- (void)drawRect:(CGRect)rect {
    // do nothing in here
}

// Set properties to make background
// translucent.
- (void) applyTranslucentBackground
{
    self.backgroundColor = [UIColor clearColor];
    self.opaque = NO;
    self.translucent = YES;
}

// Override init.
- (id) init
{
    self = [super init];
    [self applyTranslucentBackground];
    return self;
}

// Override initWithFrame.
- (id) initWithFrame:(CGRect) frame
{
    self = [super initWithFrame:frame];
    [self applyTranslucentBackground];
    return self;
}

@end

2012-02-25

UIScrollViewに余白を設定する

今日は、UIScrollViewをいじっています。これまでに自分でViewに追加した経験がないので、はっきり言って使い方がいまいち分かっていません。簡単なサンプルプログラムをつくりながら理解を深めていく計画です。

UIScrollViewを使って今回実現したいことは、スクロールして表示するコンテンツの外に余白をつくることです。このとき重要なプロパティが二つあります。ひとつはcontentSize、もう一つはcontentInsetです。
contentSizeは、UIScrollviewに表示したいコンテンツの大きさです。たとえば、500x500ピクセルの写真を表示する場合は、contentSizeを500x500に設定することになります。そして、contentInsetというのが、contentSizeの上や下にスクロール可能な領域を広めるために使うプロパティになります。
なので、UIScrollViewの外側に余白をつくるには、contentInsetを設定してやればそれでミッション完了です。
次のようにして設定します。

      scrollView.contentInset=UIEdgeInsetsMake(50.0, 30.0, 44.0, 0);

これで、上に50.0、左に30.0、下に44.0、右に0の余白ができます。

しかし、このままだとスクロールビューの中での現在の表示位置を示すインジケータが余白部分も加味して表示されてしまいます。これが好ましくない場合は、scrollIndicatorInsetsを使って調整します。たとえば、こんな具合です。

      scrollView.scrollIndicatorInsets=UIEdgeInsetsMake(50.0, 30.0, 44.0, 0);

こうしてやれば、余白部分を考慮せずに、コンテンツ内での位置をインジケータで示すことができます。多くの場合には、contentInsetとscrollIndicatorInsetsに同じ値を設定することで望ましい挙動が得られるのではないかと思われます。

2012-02-24

Ubuntu11.10のデスクトップをUnityからLinux Mintと同じcinnamonに変更

このページを参考にcinnamonを導入した。cinnamonは、最近Ubuntuに代わって人気のあるLinux Mintのデスクトップ環境らしい。
sudo add-apt-repository ppa:merlwiz79/cinnamon-ppa
sudo apt-get update
sudo apt-get install cinnamon cinnamon-session cinnamon-settings
で終わり。いったんログアウトしてからログインしなおせば、cinnamonを選択できる。 UbuntuのUnityというのは使いにくかったが、cinnamonは違和感なく使える。

JavaScriptでページのトップに滑らかにスクロールさせる

JavaScriptでページのトップにまでアニメーション付きで滑らかにスクロースするコードを書いてみました。特徴は、jQueryなどのライブラリを必要とせず、ほんの数行のプログラムだという点です。ちゃんとテストしていないから、動かないブラウザもあるかもしれません。
(function()  {
    var getScrollTop = function() {
        return document.body.scrollTop || document.documentElement.scrollTop;
    };
    var getScrollLeft = function() {
        return document.body.scrollLeft || document.documentElement.scrollLeft;
    };
    var startTime = new Date() - 0;
    var scrollInterval = setInterval(function() {
        var currTime = new Date() - startTime;
        if (currTime > 500) {
            clearInterval(scrollInterval);
            scrollTo(0, 0); 
        }
        else {
            var x = getScrollLeft() * 3 / 4;
            var y = getScrollTop() * 3 / 4;
            scrollTo(x, y);
        }
    }, 30);
})();
なんでこんなコードを書いたかというと、AndroidのWebViewに滑らかにスクロールさせるメソッドが見つからなかったからです。scrollTo(int x, int y)だと一瞬でページのトップに来てしまいます。しかし、上のコードをJavaにそのまま移植すると、スレッドの関係で問題が生じる可能性がある気がします。そこらへんはまだ検証していません。

UIActionSheetのボタンをNSArrayで指定

iPhoneアプリの開発で、UIActionSheetを表示するとき項目を配列で指定したいことはよくあると思います。たとえば、iOS 5で追加されたTwitter APIを利用できるかによって、表示項目の数を動的に変更したい場合などです。
しかし不便なことに、initWithTitleのotherButtonTitlesには、配列を渡すことができません。
この問題に対処するためには、一度ボタンなしでUIActionSheetを初期化した後で、項目をaddButtonWithTitleで追加していくことになります。
ただしこのとき注意が必要なのは、初期化のときに、キャンセルボタンをつくってはいけないということです。他のボタンを追加した後でキャンセルボタンを指定しないと、iOS 4.xのバグでボタンのインデックスがメチャクチャになってしまい、actionSheet:clickedButtonAtIndex: の処理が正常に行えなくなります。(iOS 5では正常に動作するようです。)

だいたい次のようなコードになるかと思います。

// ボタンなしで一度初期化する
UIActionSheet* sheet =[ [[UIActionSheet alloc] initWithTitle:NSLocalizedString(@"ActionSheetTitle", nil) delegate:self cancelButtonTitle:nil destructiveButtonTitle:nil otherButtonTitles:nil] autorelease];
// ボタンを追加
NSArray* buttonTitleArray = [MyButtons array];
for (NSString* buttonTitle in buttonTitleArray) {
[sheet addButtonWithTitle:buttonTitle];
}
// 古いiOSには、キャンセルボタンも後から追加しないインデクスが狂うバグがある
sheet.cancelButtonIndex = [sheet addButtonWithTitle:NSLocalizedString(@"Cancel", nil)];

SyntaxHighliterをBloggerに導入

プログラムコードを表示するために、SyntaxHighliterを導入しています。SyntaxHighliterをブロガーに簡単にインストールできるように、下記のスクリプトを作成して埋め込んでいます。これをHTML/JavaScriptガジェットにコピペすれば導入完了です。(SyntaxHighliterの使い方の詳細については作者Webページを参照してください。)
<script src="http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shCore.js" type="text/javascript"></script>
<script src="http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shAutoloader.js" type="text/javascript"></script>
<script type="text/javascript">
    (function() {
        var loadCssFile = function(href) {
            var link = document.createElement("link");
            link.rel = "stylesheet";
            link.href = href;
            link.type = "text/css"
            document.getElementsByTagName('head')[0].appendChild(link);
        };
        loadCssFile("http://alexgorbatchev.com/pub/sh/3.0.83/styles/shThemeEclipse.css");
        loadCssFile("http://alexgorbatchev.com/pub/sh/3.0.83/styles/shCore.css");
        SyntaxHighlighter.autoloader(
            'applescript            http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushAppleScript.js',
            'actionscript3 as3      http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushAS3.js',
            'bash shell             http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushBash.js',
            'coldfusion cf          http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushColdFusion.js',
            'cpp c                  http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushCpp.js',
            'c# c-sharp csharp cs   http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushCSharp.js',
            'css                    http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushCss.js',
            'delphi pascal          http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushDelphi.js',
            'diff patch pas         http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushDiff.js',
            'erl erlang             http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushErlang.js',
            'groovy                 http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushGroovy.js',
            'java                   http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushJava.js',
            'jfx javafx             http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushJavaFX.js',
            'js jscript javascript  http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushJScript.js',
            'perl pl                http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushPerl.js',
            'php                    http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushPhp.js',
            'text plain             http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushPlain.js',
            'py python              http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushPython.js',
            'ruby rails ror rb      http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushRuby.js',
            'sass scss              http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushSass.js',
            'scala                  http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushScala.js',
            'sql                    http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushSql.js',
            'vb vbnet               http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushVb.js',
            'xml xhtml xslt html    http://alexgorbatchev.com/pub/sh/3.0.83/scripts/shBrushXml.js'
        );
        SyntaxHighlighter.config.stripBrs = true;
        SyntaxHighlighter.config.bloggerMode = true;
        SyntaxHighlighter.defaults.toolbar = false;
        SyntaxHighlighter.all();
    })();
</script>

2012-01-16

iPhoneのUIWebViewにSafariみたいなアドレスバーをつける

iPhoneで、Safariに付いているようなページ読み込みが終わったら自動的に隠れるアドレスバーを実装する方法を書いてある記事を見つけた。まだ試していないがこれは役に立ちそう。