PhoneGapを使ってみた
最近は、HTML5で作られたモバイル向けWebアプリケーションをベースに、ネイティブアプリケーションを開発する、ハイブリッド型のアーキテクチャを採用するケースも増えてきているようです。
今さらですが、とりあえず、その代表的なプラットフォームであるPhoneGapを使ってみようと思いたち、最近のバージョンをベースに簡単に日本語で説明されている記事を探してみましたが、見つからなかったので、PhoneGapのサイトにある「Getting Started with iOS」を参考に、少し触ってみました。
Getting Started with iOS
インストールから、HelloWorldアプリケーションを動かすところまでの手順が説明されています。
ここでは、簡単に、そこで説明されている手順をたどってみます。
Requirements
実機を使用しないのであれば、事前に準備しておく必要があるのはは、Xcodeのみですが、ここで説明されている通り、Xcode Command Line Toolsをインストールしておく必要があります。
Install Cordova
以下のページから最新版をダウンロードして、任意の場所に展開します。
http://phonegap.com/download/
注: この記事を書いた時点の最新版は2.8.0ですが、私が試したところでは、Camera APIの一部の機能が正しく動作しなかったので、2.7.0を使っています。
Project Creation
- Determine Project Location
このチュートリアルで作成するHelloWorldプロジェクトを配置するディレクトリを準備します。
$ mkdir ~/Documents/Cordova27
- Open Terminal
説明の対象になっているバージョンが2.3.0となっていますが、現在はディレクトリ構成が変わっており、phonegap-2.7.0/lib/ios/bin になっているようです。
ターミナルを開いて、そのディレクトリに移動しておきます。
- Create New Project
HelloWorldプロジェクトを作成します。
$ ./create ~/Documents/Cordova27/HelloWorld org.apache.cordova.HelloWorld HelloWorld
プロジェクトをXcodeで開くことで、実行することができます。
オプション: Camera APIのサンプルを実行してみる
HelloWorldだけでは面白くないので、もう少しPhoneGapの機能がわかるサンプルを実行してみましょう。
PhoneGapのアプリケーションは、UIWebViewにHTMLをロードすることで実行されます。デフォルトでは、作成されたプロジェクトのwww/index.htmlがアプリケーション起動時に実行されるスタートページとして設定されています。その指定は、config.xmlというファイルで行われています。
<content src="index.html" />
通常、独自のアプリケーションを作成するときには、このindex.htmlを置換えることになります。とりあえず、PhoneGapのドキュメントに記載されている、Camera APIのサンプルを実行してみましょう。
以下の場所にある、「Full Example」の内容をコピーし、index.htmlにペーストして置換えてください。
http://docs.phonegap.com/en/2.7.0/cordova_camera_camera.md.html#camera.getPicture_full_example
Xcodeから実行すると、以下の画面に置き換わっているはずです。
デバイス固有の機能は、deviceReadyイベント後でないと使用できないので、最初は各ボタンを無効にしておいて、deviceReadyが来たら有効にするといった処理を付け加えてもいいかもしれません。
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script> // Cordova is ready to be used! // function onDeviceReady() { pictureSource=navigator.camera.PictureSourceType; destinationType=navigator.camera.DestinationType; $(".camerabutton").removeAttr("disabled"); } <body> <button class="camerabutton" disabled onclick="capturePhoto();">Capture Photo</button> <br> <button class="camerabutton" disabled onclick="capturePhotoEdit();">Capture Editable Photo</button> <br> <button class="camerabutton" disabled onclick="getPhoto(pictureSource.PHOTOLIBRARY);">From Photo Library</button><br> <button class="camerabutton" disabled onclick="getPhoto(pictureSource.SAVEDPHOTOALBUM);">From Photo Album</button><br> <img style="display:none;width:60px;height:60px;" id="smallImage" src="" /> <img style="display:none;" id="largeImage" src="" /> </body>
注:
iOS Simulatorで実行する場合には、カメラの撮影機能を動作させることはできませんのでご了承ください。
フォトアルバムに写真が入っていない場合は、事前に登録しておく必要があります。登録方法は、以下の記事が参考になるかと思います。
iPhoneシュミレータに画像を登録する方法 - satoko's blog - s21g
JavaScriptから簡単にネイティブの機能が使えるので、たしかにObjective-Cは知らなくても、iOSアプリケーションが作れてしまいますね。
尚、今回は、とりあえず使ってみることが目的だったので、UIについては二の次になっています。実際のアプリケーションでは、jQuery MobileやSencha Touchといった、モバイルWebアプリケーションフレームワークと組合せることで、もっと洗練されたUIを実現することになります。そのあたりも面白そうなので、また時間があるときに調べてみたいと思います。
iPhoneのRetina4ディスプレイ判定方法
Retina4か否かを判定する方法をいろいろ調べてみました。
正しいディスプレイのサイズを取得するためには、+[UIScreen mainScreen]メソッドで得られるオブジェクト使うのですが、以下のように3つの値が取得できます。
NSLog(@"mainScreen bounds height %f", [[UIScreen mainScreen] bounds].size.height); NSLog(@"mainScreen applicationFrame height %f", [[UIScreen mainScreen] applicationFrame].size.height); NSLog(@"mainScreen currentMode height %f", [[UIScreen mainScreen] currentMode].size.height);
それぞれの結果を見てみると、以下の通りになりました。
- mainScreen bounds height 568.000000
Retina以前のディスプレイと、互換のあるサイズ表記 - mainScreen applicationFrame height 528.000000
1と同様だけど、ステータスバーを含まない高さであり、ステータスバーの状態によって値が変わる。この例は、着信ステータスバーがONになっている状態なので、1より40px少ない値になっている。 - mainScreen currentMode height 1136.000000
物理的なpixel値。解像度の判定まで必要な場合は、この値を使うのが適切かもしれない。
SysStats Monitor/Liteでは、現在2を使っているので、ステータスバーの状態によって取得される値が変わってしまい、着信中やテザリング中にアプリが起動されると、正しく判定できていないことがわかりました。
今回の目的では1と3のどちらでもいいと思いますが、とりあえず1を採用し、以下の形でチェックすることにしました。
#define R4_DISPLAY_HEIGHT 568.0 + (BOOL)is4InRetinaDisplay { if ([[UIScreen mainScreen] bounds].size.height == R4_DISPLAY_HEIGHT) { return YES; } else { return NO; } }
SysStats Monitor iOS6/iPhone5 (Retina 4) 対応のまとめ
現在、SysStats MonitorをiOS6/iPhone5 (Retina 4) 対応にするための改修を、コツコツとやっています。iOS6がリリースされてから2ヶ月ほど経つので、あまり新鮮な情報ではないですが、その作業が概ね完了したので、一通りやったことをまとめてみました。
大きく分けると、以下の3項目になります。
iPhone5 (Retina4) への対応
こちらは、見た目の調整であり、以下の2点が大きな目的となります。
- Retina4ディスプレイのサイズに合わせること
- 旧機種では、今まで通り表示されること
実際には、単純に画面の縦幅を延ばせばいいというわけではなく、操作に関する問題もいくつか出ました。
画面サイズの変更
640 x 1136 pixelsのLaunch Imageを作成して、プロジェクトに追加することで、画面サイズが変更されます。
Main Interfaceに設定されているNIBの変更
Main Interfaceに設定されているNIBファイル(通常はMainWindow.xib)のWindowオブジェクトの"Full Screen at Launch"プロパティをONにする。
それをやらないと、UITabBarControllerを使っている場合、tab barが反応しなくなってしまうようです。
ios - UITabBarController does not respond on iPhone 5 simulator: 4-inch retina display - Stack Overflow
Autosizing Maskの調整
iPhoneとiPadの両方にユニバーサルバイナリ方式で対応しているアプリの場合は、すでに正しく設定されていることと思いますが、SysStats Monitorの場合、UIを共通化していなかったので、iPhone用のUIには、自動的にリサイズされるような設定はしていませんでした。
いくつかの画面で、表示が上の方に偏ったり、重なってしまったりしたものがあったので、正しく表示されるよう調整しました。
UIActionSheetの調整
ここまでの対応をしたところ、画面の見た目上は、正しく表示されるようになりましたが、UIActionSheetについては、2つの問題が発生しました。
- iPhone5だと、一番下のボタンが反応しない。
- 旧機種だと、一番下のボタンが表示されない。
UIActionSheetを表示させるメソッドがいくつか提供されており、これまでは、tab barから出したり、現在のviewから出したりで、統一できていませんでした。今回は、すべてを以下のメソッドに置換えることで解消されました。
[actionSheet showInView:self.view.window];
同時に、"Presenting action sheet clipped by its superview"というワーニングも出なくなりました。
参考: Objective-Cと戦うブログ: UIActionSheetで「Presenting action sheet clipped by its superview.」というエラーについて
iOS6への対応
SDKの対応アーキテクチャの変更
最新のXcodeでは、armv6(iPhone 3G)と、iOS 4.3よりも前のOS向けのバイナリの生成が出来なくなっています。
Xcode Release Notes
これにより、私の所有している環境では、iOS5.1以降のみ実機テストが可能な状態になってしまいました。
そのため、以下の対応を行いました。
デバイスの回転への対応
iOS6から、デバイスが回転したときのイベントのハンドリング方法が変わりました。
UIViewControllerのデフォルトの実装では、全方向の回転を受付けるのがデフォルトになっているようです。
SysStats Monitorの場合、iPad版はそれで問題ないのですが、iPhone版はデバイスの回転をサポートしていません。
それを抑止するため、iPhone版のApplicationDelegateクラスに、以下のメソッドを追加しました。
- (NSUInteger)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window { return UIInterfaceOrientationPortrait | UIInterfaceOrientationPortraitUpsideDown; }
尚、起動時のRootViewControllerの登録を、古い方法で記述していると、iOS6では回転したときのイベントが発生しなくなってしまうようです。
iphone - iOS 6 shouldAutorotate: is NOT being called - Stack Overflow
iOS5でやり残していたものへの対応
UDIDを取得するメソッドがdeprecatedになった
iOS5で、すでにUIDevice.uniqueIdentifierプロパティは、deprecatedになっていましたが、対応が漏れていました。
将来的に削除されたときに備えて、以下のようにプロパティの存在チェックをしてから呼び出すように変更しました。
// prepared for that uniqueIdentifier is deprecated. uniqueIdentifierLabel.text = [currentDevice respondsToSelector:@selector(uniqueIdentifier)] ? currentDevice.uniqueIdentifier : @"N/A";
とりあえず、アプリの画面サイズを、Retina4 (640 x 1136 pixels) に対応させる方法
まず、結論から言ってしまうと、640 x 1136 pixels の Launch Image が設定されていれば、Retina4 デバイスでアプリが実行されたときに、画面サイズが640 x 1136pixelsになるようです。
具体的な設定方法は、 iOS App Programming Guide を参照していただくとして、最も簡単な方法は以下の通りです。
1) Xcodeで、Retina4 用の Launch Image が設定されていないプロジェクトを開き、左ペインをIssue Navigatorに切替えると、以下のようなWarningが表示されます。
2) この Warning メッセージをクリックすると、以下の Alert パネルが表示されます。
3) Add をクリックすると、とりあえず、真っ黒な Default-568h@2x.png ファイルがプロジェクト内に作成され、Launch Image として認識されます。
この状態で、とりあえずRetina4デバイスの実機かSimulatorでアプリを実行すると、それまで上下にあった隙間が埋められ、画面サイズが640 x 1136 pixelsで実行されるようになります。
autoresizing 機能により、ほとんどの画面は、最適化されて表示してくれるはずですが、絶対座標が指定されていたり、autoresizing の設定が適切でないコンポーネントは、思った通りにならない場合もあるので、一通りの動作確認をして、想定通りに機能するよう、調整する必要があります。
実際に、私の場合は、TabBarをタップしても反応しないという問題や、表示がズレる問題が発生しました。
いくつかハマりどころがありそうです。
それらについては、また後日書いてみたいと思います。
補足
iOS Human Interface Guidelines の Launch Image の説明によると、
For apps that run on iPhone 5 and iPod touch (5th generation), create a launch image that measures 640 x 1136 pixels.
と書かれていますので、Appleのマニュアルの中では、これが、画面サイズを640 x 1136pixelsにするための条件を、暗黙的に示しているのかもしれません。
MacPortsのupgradeでハマったこと
久しぶりに、MacPortsのupgradeを実行して、ハマったところがあったので、メモを兼ねて書いておきます。
makeがない??
まず最初は、初歩的な問題でした。
$ sudo port upgrade outdated .... 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? ....
これは、Xcode 4.3.xをインストールした後に、Command Line Toolのインストール*1をし忘れてたことによります。すぐに気付いて、インストールしてクリアしました。
ncursesがactivateできない??
次は、以下の問題が起きました。
$ sudo port upgrade outdated .... ---> Activating ncurses @5.9_1 Error: org.macports.activate for port nurses returned: Image error: /opt/local/bin/captoinfo is being used by the active ncursesw port. Please deactivate this port first, or use 'port -f activate nurses' to force the activation. Please see the log file for port nurses for details: ....
何か不整合が起きているために、エラーになったようです。
以下のレポートを参考に、強制的にuninstallしてから、やり直したら、とりあえず、先に進んでくれたようです。
#34872 (Failed to install ncurses) – MacPorts
$ sudo port -f uninstall ncursesw .... Warning: Deactivate forced. Proceeding despite dependencies. ---> Uninstalling ncursesw @5.7_0+darwin_10
perl5も失敗??
とりあえず、先に進んだのですが、今度はperl5のactivateでもエラーになりました。
$ sudo port upgrade outdated .... ---> Activating perl5 @5.12.4_0+perl5_12 Error: org.macports.activate for port perl5 returned: Image error: /opt/local/bin/a2p is being used by the active perl5.8 port. Please deactivate this port first, or use 'port -f activate perl5' to force the activation. Error: Failed to install perl5 Please see the log file for port perl5 for details: ....
こちらも、以下のレポートを参考にいくつかのバージョンをuninstallしました。
#28831 (port upgrad outdated fails with error: /opt/local/bin/a2p is being used by the active perl5.8 port.) – MacPorts
$ sudo port installed | grep perl p5-libwww-perl @5.834_0 (active) perl5 @5.8.9_0 perl5 @5.12.4_0+perl5_12 perl5.8 @5.8.9_3 (active) perl5.12 @5.12.4_1 (active) subversion-perlbindings @1.6.9_0 (active) $ sudo port uninstall perl5 @5.8.9_0 ---> Uninstalling perl5 @5.8.9_0 $ sudo port upgrade perl5.8 .... $ sudo port installed | grep perl p5-libwww-perl @5.834_0 (active) perl5 @5.12.4_0+perl5_12 perl5.8 @5.8.9_3 perl5.8 @5.8.9_8 (active) perl5.12 @5.12.4_1 (active) subversion-perlbindings @1.6.9_0 (active) $ sudo port uninstall perl5.8 @5.8.9_3 ---> Uninstalling perl5.8 @5.8.9_3
この後、再実行したら、エラーがなく無事終了しました。
$ sudo port upgrade outdated .... ---> Updating database of binaries: 100.0% ---> Scanning binaries for linking errors: 100.0% ---> No broken files found.
環境によって、ここで説明した方法が当てはまらないこともあるかもしれませんが、もしも、同じような状況になったときに、参考になればと思います。
Retinaディスプレイで画像リソースをキレイに表示するには?
iPadがRetinaディスプレイになったことで、また1つ、解像度の異なるiOSデバイスが増えました。
どれだけの種類があるかというのは、List of iOS devices - Wikipedia, the free encyclopedia
にまとめられています。
ここから、ディスプレイの解像度に関する情報だけ抜き出してみると、以下の4パターンになります。
- 480 × 320 px - 〜iPhone 3GS, 〜iPod touch 3G
- 960 × 640 px(Retina) - iPhone 4/4S, iPod touch 4G
- 1024×768 px - iPad, iPad2
- 2048×1536 px(Retina) - new iPad
アプリのユーザインタフェースでは、さまざまな箇所で画像リソースを使用していますが、各デバイスの解像度に適合したサイズのものを用意しないと、それらがキレイに表示されなくなってしまいます。
実際にどのような考慮が必要なのか、例を挙げてまとめてみました。
Application icon
各解像度に対して、以下の4種類のサイズが必要になります。
解像度 | デバイス | アイコン画像サイズ |
---|---|---|
480 × 320 px | 〜iPhone 3GS, 〜iPod touch 3G | 57 x 57 |
960 × 640 px(Retina) | iPhone 4/4S, iPod touch 4G | 114 x 114 |
1024×768 px | iPad, iPad2 | 72 x 72 |
2048×1536 px(Retina) | new iPad | 144 x 144 |
簡単に言ってしまうと、
- iPhone/iPod touchとiPadは、画面の大きさが違うので、表示するアイコンのサイズが違う。
- Retinaは2倍の解像度なので、サイズも2倍にしないと、粗い画像になってしまう。
ということですね。
SysStats Monitorというアプリの場合は、480 × 320 pxのデバイスしか存在しないころにリリースしたものなので、新しい解像度のデバイスが出るたびに、アイコンを追加しています。
上記のアイコンは「Small App Icon」と呼ばれるもので、アプリの中にバンドルするものです。
それに加えて、もう一つApp Storeで表示するときのアートワーク画像用として、512 x 512の「Large App Icon」も用意する必要があります*1。こちらは、iTunes Connectでアプリの申請をするときのメタデータとして登録します。
私の場合、それを縮小して各アイコンを作っているので、これに関してはあまり手間をかけずにできています。
裏を返せば、まず最初に「Large App Icon」を作成しておく方が、よいということですね。
これから、iPhoneアプリ開発を始める方は、ご注意ください。
Tab bar icon
それ以外にも、アプリの中のTab barのボタンに使う画像も、解像度を考慮する必要があります。
こちらは、iPhoneとiPadの違いはないので、Retinaか、そうでないかの2種類になります。
私の場合、当初、30 x 30で作成してしまったので、もう一度60 x 60を作り直す必要があり、やや手間がかかってしまいました。
なので、こちらも、最初から大きめのサイズで作っておくのが無難ですね。
その他の画像
これ以外にも、Launch image(起動時のスプラッシュ画像)や、Spotlightの検索結果や設定画面で表示されるアイコンなど、いくつか画像リソースの種類があります。
以下のドキュメントには、全般的な説明が書かれています。
iOS Human Interface Guidelines - Custom Icon and Image Creation Guidelines
その他の画像については、こちらを参照してください。
アプリのメタデータのデフォルト言語を変更する方法
iTunes Connectで、メタデータ(AppStoreに表示される紹介文など)のデフォルト言語をアプリごとに指定できるようになりました。
これまでは、最初のアプリ登録時に選択したデフォルト言語が、その後追加されるすべてのアプリに適用されてしまい、かつ、変更もできなくて、苦労していました。
以下に、変更の手順を、簡単に説明します。
各アプリの情報設定を行う画面左上にあるApp Informationというタイトルの横、[Edit]というボタンをクリックします。
すると、以下のような設定パネルが表示されるので、そこで、そのアプリのデフォルト言語を選択することができます。
Localizeしたリソースを登録していない地域のAppStoreでは、アプリの紹介文やスクリーンショットが、このデフォルト言語で表示されます。そのため、デフォルトを[Japanese]にしていると、そこで日本語の紹介文やスクリーショットが表示されてしまいます。
なので、デフォルト言語は[English]にしておき、Localizationで[Japanese]を追加するのが無難だと思います。
私も、早速、そのように変更して、正しく適用されていることを確認しました。
同じような状況の方は、ぜひ、試してみてください。