インフィニットループ 技術ブログ

2013年03月01日 (金)

著者 : take

IT 勉強会カレンダー for Android を iOS ユニバーサルアプリ化するまで

IT 勉強会カレンダー for Android & iOS

こんにちは! JavaScript 担当のアルバイト take です。
この度、 IT 勉強会カレンダー for Android を iPod touch, iPhone, iPad に対応させた IT 勉強会カレンダー for iOS をリリースしました。
公式サイト:https://www.infiniteloop.co.jp/it-study-calendar/
Get it on Google Play Download on the App Store
昨年の 6 月に公開した IT 勉強会カレンダー for Android ですが、思いの外ダウンロードされ、現在は約 300 人のユーザに使われているようです。
昨年のリリース時点では iOS に対応しておらず、 Android でも幾つか不具合が見られました。
そこで、 Android 版の不具合を修正すると共に、折角なので iOS にも対応させることにしました。
ここでは Android でしか動かなかった Titanium mobile で作られたアプリを iOS 対応させるまでに行った手順を紹介します。

1.メニューの改良

まずは、 iOS でも同じコードで動くように、メニューの UI を改良します。
Titanium mobile での UI と画面遷移 で書いたコードを一部使いました。
このような形で書けます。

// set menu
var menu = {
	"検索": {
		click: function () {
			g.createWindow.Search().open();
		}
	},
	"更新": {
		click: function () {
			scroll.fireEvent('reload', {cache: false});
		}
	},
	"設定": {
		click: function () {
			g.createWindow.Settings().open();
		}
	}
};
g.createMenu(wrapper, menu, false);

2. Android でしか動かないコードを iOS に対応させる

例えば…

// Android 2.2 ~
new Date("2013-03-01");
=> Fri Mar 01 2013 09:00:00 GMT+0900 (JST)
// iOS 5
new Date("2013-03-01");
=> Invalid Date

このようなことが起こります。
iOS で動かした際に予期しないバグを生み出すことがあるので、本当にそのコードが iOS でも正常に動くのか、コードを細かく追っていく必要があるでしょう。

// iOS 6
new Date("2013-03-01");
=> Fri Mar 01 2013 09:00:00 GMT+0900 (JST)

ちなみに… iOS 6 では動きます。

3.ネイティブの機能の実装

IT 勉強会カレンダー for Android にはカレンダーへ登録する機能があります。
Titanium には iOS のカレンダーを操作するための API が用意されていないため、モジュールを使う必要があります。
探してみたところ、こちらのモジュールが使いやすそうでしたので、拝借しました。
エミュレータで試していると気付きませんが、実機で動かそうとしたところ、 iOS 6 から追加されたプライバシー設定により、カレンダーを呼び出すことができなくなりました。
調べてみたところ、一度アプリからユーザへ許可を求めて認証するだけで書き込めるということが分かったため、自分で実装することに。
初めての Objective-C でしたが、 GitHub に上がっている他の方のコードを参考にしながら実装しました。

#define SYSTEM_VERSION_GREATER_THAN_OR_EQUAL(v) ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
-(void)authorization:(id)args
{
	ENSURE_UI_THREAD_1_ARG(args);
	ENSURE_SINGLE_ARG(args, NSDictionary);
	id success = [args objectForKey:@"success"];
	id failure = [args objectForKey:@"failure"];
	RELEASE_TO_NIL(successCallback);
	RELEASE_TO_NIL(failureCallback);
	successCallback = [success retain];
	failureCallback = [failure retain];
	EKEventStore *eventStore = [[[EKEventStore alloc] init] autorelease];
	if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL(@"6.0"))
		[eventStore requestAccessToEntityType:EKEntityTypeEvent completion:^(BOOL granted, NSError *error) {
			if (granted)
				[self _fireEventToListener:@"success" withObject:nil listener:successCallback thisObject:nil];
			else if (failureCallback != nil)
				[self _fireEventToListener:@"failure" withObject:nil listener:failureCallback thisObject:nil];
		}];
	else
		[self _fireEventToListener:@"success" withObject:nil listener:successCallback thisObject:nil];
}

認証部分はたったこれだけです。
success callback と failure callback を受け取り、 iOS 6.0 以降なら API を呼び出して、ユーザに許可を求めています。
モジュールは GitHub で公開しています。
ilnt/Titanium-Calendar-Permission

4.ユニバーサルアプリ化

iPhone アプリは iPad で動かすことができますが、ウィンドウが小さく、拡大表示すると画素が潰れて表示されてしまいます。そのため、 iPhone でも iPad でも同じように動作させるためにユニバーサルアプリにしました。
Titanium では特に意識しなくてもユニバーサルアプリに対応させてくれるため、次の 2 点を気を付けるだけで iPad 対応することができます。

  • Xcode でビルドする前に、 titanium clean して、 iPhone, iPad Simulator それぞれで動かす (それぞれに build する)
  • Archive するときは Targets は -iPad や -universal というsuffix が付かないものを選び、 Devices が iPhone になっているものを Universal に変更してビルドします。
Xcode

※ -iPad, -universal を選択すると、何故か Validate で失敗します。

5.Android 版の不具合修正

昨年公開したバージョンには不具合がありました。
それは、カレンダーへ登録しようとすると、イベント名、場所、内容までは反映されるのに、日時だけが反映されない問題です。
原因はわかりませんが、 Titanium の Intent API で putExtra で渡した Number 型の値が Java 側でうまく Long 型に変換されていないのではないかと推測しています。
Titanium の API で出来ないのであればモジュールを使う他ありません。
Android のカレンダーを操作する API ならありますが、カレンダーにイベントを登録するだけのためにカレンダーを読み書きするためのパーミッションをアプリに追加するのはスマートではないという理由で、カレンダーに Intent を飛ばすだけのモジュールを作成しました。
こちらのモジュールも GitHub で公開しています。
ilnt/Titanium-Calendar-Intent

6.アイコンとスプラッシュスクリーンの作成

Android ではアイコンは 1 つだけですが、 iPhone, iPad に対応するには 4 つ必要です。
Resources/iphone の中に置きます。
appicon.png (iPhone/iPod touch)
appicon@2x.png (iPhone/iPod touch Retina)
appicon-72.png (iPad)
appicon-72@2x.png (iPad Retina)
Android では設置したアイコンがそのままアプリのアイコンになりますが、 iOS では角丸の枠で囲まれる加工が施されるので、基本的に余白なしでサイズぴったりにデザインするとちょうどよくレンダリングされます。
枠を意識したデザインが必要になります。
デフォルトの設定で入る光沢を消したい場合は tiapp.xml の prerendered-icon を true に設定します。

<prerendered-icon>true</prerendered-icon>

スプラッシュスクリーンは起動時に表示される画像です。
用意するサイズは次の通りです。
Resources/android と Resources/iphone の中に置きます。 (アイコンと同じ場所)
Android
default.png … 320×480
images/res-long-land-hdpi/default.png … 800×480
images/res-long-land-ldpi/default.png … 400×240
images/res-long-port-hdpi/default.png … 480×800
images/res-long-port-ldpi/default.png … 240×400
images/res-notlong-land-hdpi/default.png … 800×480
images/res-notlong-land-ldpi/default.png … 320×240
images/res-notlong-land-mdpi/default.png … 480×320
images/res-notlong-port-hdpi/default.png … 480×800
images/res-notlong-port-ldpi/default.png … 240×320
images/res-notlong-port-mdpi/default.png … 320×480
iPhone
Default.png … 320×480 (iPhone/iPod touch)
Default@2x.png … 640×960 (iPhone/iPod touch 3.5inch Retina)
Default-568h@2x.png … 640×1136 (iPhone/iPod touch 4inch Retina)
Default-Portrait.png … 768×1004 (iPad Portrait)
Default-Portrait@2x.png … 1536×2008 (iPad Retina Portrait)
Default-Landscape.png … 1024×768 (iPad Landscape)
Default-Landscape@2x.png … 2048×1496 (iPad Retina Landscape)
Android の方がかなり多いように感じますが、色分けした通り同じサイズのものが 3 組あるので、実際に作成するのは 8 枚です。
Android 8枚 + iOS 7 枚 (重複 1 枚) = 14 種類のサイズを作ることになります。
解像度の大きい iPad Retina Portrait に合わせて作成して、トリム・縮小してそれぞれの大きさに合わせて作りました。
Android の方は、最悪 default.png だけで問題ないですが、 iPhone では、 Default-568h@2x.png が存在しないと Titanium の方でこのアプリは iPhone 5 に対応していないと判断され、上下に黒筋が入るという残念な状態になります。
逆に言うと、 Default-568h@2x.png があれば勝手に iPhone 5 に対応してくれるので便利です。

7.フォントサイズと UI

大画面の端末もサポートするということで、 フォントサイズや UI がそのままだと少し見にくいかもしれません。 Titanium には iPad だけ使える API もあるので、それらを活用すると iPad に最適化されたアプリを作ることが可能になるでしょう。
今回は、最低限フォントサイズだけなんとかしようと思い、設定画面からフォントサイズを大・中・小の三段階で選べるようにしました。
 
以上 7 つが Android アプリを iOS ユニバーサルアプリ化するまでに行ったことです。
Titanium mobile を使うなら、 Android, iPod touch, iPhone, iPad まで対応できるといいですね。

ブログ記事検索

このブログについて

このブログは、札幌市・仙台市の「株式会社インフィニットループ」が運営する技術ブログです。 お仕事で使えるITネタを社員たちが発信します!