最近ionicというオープンソースのモバイルSDKを使ってhybridアプリケーションを作っているので、最初のキャッチアップにこれはざっと知りたいなという内容をまとめてみます。
目次
想定している読者の方
僕は今までwebアプリケーションやiOSネイティブアプリケーションを少しずつ作ってきた経験があるので、その経験を持った上で初めてhybridアプリを作ってみて、もしくはionicを触って特徴的だと思ったことや注意したい点などを書きます。
なので想定している読者の方としては
・少ないリソースでiOSやandroidなど複数プラットフォームで作れるアプリを作りたい
・web(HTML、css、JavaScript)の知識はある
といった方です。
また、ionicはHTML5とAngularJSの技術を用いて作られているものですが、AngularJSは初めて触りました。
そのため、内容としてはionicとAngularJSの使い方が混ざったようなものになっています。
AngularJSに詳しい方からすると普通の内容かと思いますので、ご容赦ください。
チュートリアルを動かす
ionicは最初にまず作ってみるサンプルアプリが3種類用意されています。
3種類はそれぞれ
・blank:シンプルなワンページのアプリ
・tabs:画面下のタブバーのメニューごとに内部がおおまかに分かれているアプリ(instagramみたいなイメージです)
・sidemenu:左上のメニューボタンを押すと出るサイドバー内のメニューごとに内部がおおまかに分かれているアプリ
といった感じになっています。最初はこのどれかを使って自分のアプリを作っていくことになると思います。
tabsを動かす
今回は上記3種類の内、tabsの構造を見ながら全体像を理解して行きたいと思います。
チュートリアルページに応じて下記の通りターミナルでの作業を進めていくとすぐにmyAppが立ち上がります。
なお、チュートリアルページではプラットフォームをiOSとして解説されていますが、
・お持ちのパソコンがmacじゃないかも
・xcodeがインストールされていないかも
・iosのbuildとか結構時間かかる
といった点を考えてブラウザ(GoogleChrome)ですぐにアプリが立ち上がるようにコマンドを打っていきます。
まずionicをインストールします
$ npm install -g cordova ionic
続いてmyAppという名前でタブ形式のアプリを作ります。
$ ionic start myApp tabs
出来上がったmyAppというアプリの中に入ります。
$ cd myApp
アプリを使えるプラットフォームを追加します。
$ ionic platform add browser
ここではbrowserを指定していますが、これだけではiPhoneやandroidで動作しません。実際にアプリを開発するときはionic platform add ios でiOSを追加したり、 ionic platform add android でandroidを追加したりします。
次に追加したプラットフォームで動作させるようにビルドします。
$ ionic build browser
browserに関しては本当は必要ないんですが、一応コマンドはこんな感じです。
こちらも実際の開発時にはiOSやandroid向けにもビルドをすると思います。
追加してビルドしたプラットフォームでシミュレーションします。
$ ionic emulate browser
ここまでうまくいくと、GoogleChromeが立ち上がりこんな画面が出てくると思います。
このアプリの構成としては
・tab1:Status Hello Ionic!みたいな画面
・tab2:Chats 連絡先一覧みたいな画面(左にスワイプすると削除できる)
Chat 個別の連絡先の人とのチャット画面
・tab3:Account 設定画面
という作りになっています。
それでは、このそれぞれのタブや画面、その裏のロジックやデータがどこでどう書かれているかを見ていこうと思います。
チュートリアルアプリのファイルを見ながらionicで作るアプリの全体像を理解する
アプリのファイル構成
今作ったmyAppのファイル構成を見てみると、こんな感じになっています。
結構いろいろあるんですが、この中で触るのは基本的にwwwという名前のフォルダ以下です。
それ以外だとconfig.xmlファイルが設定ファイルになっていてアプリの名前とかAppStoreやPlayストアでアプリを公開するときの説明文とかを指定するファイルが重要なんですが、一旦今回は割愛します。
大事なのはwwwフォルダだけなので、例えばgithubでよさそうなアプリを見つけて試しにそれをローカルで動かしてみたい、といった時などはこのwwwフォルダだけ入れ替えることで、結構すぐ動きます。
iOSのネイティブアプリの時は同じようなことをやろうとするとバージョンが合わなかったり、CocoaPodsがうまく入らなかったりでなかなか動かせないことが多かったのですが、今回はすっと進められるのでストレスがありません。
続いてその大事なwwwフォルダの中身のうち開発を進める上で触るフォルダやファイルを中心に説明します。
www/index.html
全ての大元はやっぱりこのファイルです。中身を見ると必要なjsファイルやcssファイル、ionic自身などを最初に読み込んでいるのがわかります。
bodyタグの中身もちょっとだけ書かれているんですが、そこにはナビゲーションバー(アプリでいうヘッダーみたいな部分)と画面全体のビューが”入れ物”みたいな形で定義されています。
そしてこの”入れ物”の中に他のファイルで書いた内容がレンダリングされて順番にポンポン放り込まれるような感じです。
なのでindex.html自体は常に(見えないけど)画面の中にあって、アプリ全体を支えているようなイメージを個人的には持っています。
これはAngurarJSに備わっているシングルページアプリケーション(SPA)という特徴があるからこそできているのですが、AngularJSで作るアプリケーションには一般的にwebでは欠かせないようなページの遷移がなく、代わりに独自に定義されているルーティング機能によってあたかもページ遷移をしているかのうように見せることができます。そのためAngularJSを使ったhybridアプリもindex.htmlから他のhtmlファイルへ遷移はしないけど、画面を切り替えることができているようです。
ちなみにこのシングルページアプリケーションがあると画面の切り替えがスムーズにできるので、AngularJSはhybridアプリや管理画面がブラウザで提供されているようなアプリケーションに向いているとのことです。
index.htmlの中身はこんな感じです。気になるところに日本語を追加してあります。
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no, width=device-width"> <title></title> <link href="lib/ionic/css/ionic.css" rel="stylesheet"> <link href="css/style.css" rel="stylesheet"> <!-- IF using Sass (run gulp sass first), then uncomment below and remove the CSS includes above <link href="css/ionic.app.css" rel="stylesheet"> --> <!-- ionic/angularjs js --> <script src="lib/ionic/js/ionic.bundle.js"></script> <!-- cordova script (this will be a 404 during development) --> <script src="cordova.js"></script> <!-- www/js フォルダ以下のjsファイルを読み込む。 アプリが巨大になってjsファイルを分けるときはここに追記します。 --> <script src="js/app.js"></script> <script src="js/controllers.js"></script> <script src="js/services.js"></script> </head> <!-- ここで"starter"というモジュールを呼び出している --> <body ng-app="starter"> <!-- 全ての画面で共通して必要なナビゲーションバーと戻るボタンはここに記載されてます。 ナビゲーションバー内のタイトルとか追加のボタンなどは www/tenplates 以下にある htmlファイル(各画面に対応しています)内で追加で記載します。 --> <ion-nav-bar class="bar-stable"> <ion-nav-back-button> </ion-nav-back-button> </ion-nav-bar> <!-- 画面全体は www/tenplates 以下にあるhtmlファイルの内容がこの <ion-nav-view>タグの中にレンダーされます。 index.html自体に直接記載もできますが、その場合全てのページに共通するパーツになります。 --> <ion-nav-view> </ion-nav-view> </body> </html>
www/js/
上に記載したようなAngularJSの特徴的な点はもちろんjsフォルダ内の各ファイルに凝縮されています。
各ファイルをそれぞれ見ていきたいんですが、その前にAngularJSのMVC(もしくはMVW)がどんなキーワードで実現されているか簡単に触れてみます。
・M(Model):モデルはビジネスロジック、データの入れ物、サーバーサイドとの通信といった部分を司る部分ですが、AngularJSでは総称してServiceと呼んでいます。この中にはservice(), factory(), value(), constant(), provider()といった様々な種類のServiceがあるのですが、それぞれが用途に合わせてデータを格納したり演算したりするためにあります。ちなみに総称Serviceの中に個別のservice()があり、結構わかりにくいです。
・V(View):今回は単純にhtmlファイルが画面の数+タブ1枚分、合計5つあります。これがそのままViewとして画面を作るファイルになっています。
・C(Controller):AngularJSではC、もしくはW(Whatever)と表現されますが、MとVをつなげる部分ですね。myAppではcontroller()という名称で出てきます。
では、個別のファイルを見てみます。
www/js/app.js
ここではアプリ内で使うcontrollerとserviceを紐付けるのと、urlに画面(htmlファイル)とcontrollerを紐付けるルーティング設定が書かれています。
controllerやserviceはこのapp.js内に記載することもできるのですが、やがてファイルが肥大化してしまうため、AngularJSに備わっているモジュール機能を作って、各機能を分割してcontroller.jsとservice.jsに分けています。
実際のapp.jsの中身です。
// Ionic Starter App // 'starter'というモジュールを作成して、第二引数で // 'starter'が依存するモジュールを複数指定しています。 // 依存するモジュールのうち'starter.controllers', 'starter.services'は // 同階層のcontroller.js, services.js内で定義され、これらもindex.htmlで呼び出されます。 // 'starter'モジュール自体はindex.htmlのbodyタグで呼び出されています。 angular.module('starter', ['ionic', 'starter.controllers', 'starter.services']) .run(function($ionicPlatform) { $ionicPlatform.ready(function() { // Hide the accessory bar by default (remove this to show the accessory bar above the keyboard // for form inputs) if (window.cordova && window.cordova.plugins && window.cordova.plugins.Keyboard) { cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true); cordova.plugins.Keyboard.disableScroll(true); } if (window.StatusBar) { // org.apache.cordova.statusbar required StatusBar.styleDefault(); } }); }) // ここからアプリの設定の記載がされています。 // 実際はurlに画面とコントローラーと紐付けるルーティング設定のみです。 .config(function($stateProvider, $urlRouterProvider) { // Ionic uses AngularUI Router which uses the concept of states // Learn more here: https://github.com/angular-ui/ui-router // Set up the various states which the app can be in. // Each state's controller can be found in controllers.js $stateProvider // タブバー用の抽象的なステートを設定します。 .state('tab', { url: '/tab', abstract: true, templateUrl: 'templates/tabs.html' }) // 各タブはそれぞれの履歴を保持しています: // dashタブ(statusタブ)配下 .state('tab.dash', { url: '/dash', views: { 'tab-dash': { templateUrl: 'templates/tab-dash.html', controller: 'DashCtrl' } } }) // chatsタブ配下 .state('tab.chats', { url: '/chats', views: { 'tab-chats': { templateUrl: 'templates/tab-chats.html', controller: 'ChatsCtrl' } } }) .state('tab.chat-detail', { url: '/chats/:chatId', views: { 'tab-chats': { templateUrl: 'templates/chat-detail.html', controller: 'ChatDetailCtrl' } } }) // accountタブ配下 .state('tab.account', { url: '/account', views: { 'tab-account': { templateUrl: 'templates/tab-account.html', controller: 'AccountCtrl' } } }); // 上のどのurlも該当しない場合はdashページに飛ばすようにしてあり、 // 一番最初画面が表示される時もこのルーティングに従っています。 $urlRouterProvider.otherwise('/tab/dash'); });
このファイルで最初ちょっとわかりにかかったのは$stateProvider以下の画面遷移周りの設定です。今回のようにタブバーが画面の下にあって、それぞれのタブを選択することでメニューを切り替えるといったような構成のアプリだとタブと各画面の兼ね合いみたいなものに少し注意がいります。
タブバーを利用しない場合だと、state設定は下記のようになります。
$stateProvider .state('sample', { // stateの名前 url: '/sample', // どのurlの時に表示するか templateUrl: 'sample.html', // 表示する画面 controller: 'SampleCtrl' // 利用するcontroller })
そして、このように用意したstateを使って画面遷移を行うのですが、画面遷移の方法にはいくつかあり、
・html側:
href属性でurlを指定
ui-sref属性でsteteの名前を指定
・js側:
$state.goでstateの名前を指定
など、urlにパラメーターも含める、もしくはtapイベントで画面遷移を行う、など用途に応じてやり方を選択するようです。
具体的な方法はこちらが参考になりました。
以上はタブバーを利用しない場合のstate設定でしたが、今回のmyAppではタブバーが前提になるのでルーティングの記載の方法が少々異なります。
まずタブバー用のstateを用意しています。
// タブバー用の抽象的なstateを設定します。 .state('tab', { url: '/tab', abstract: true, templateUrl: 'templates/tabs.html' })
そしてこのタブバー配下に画面を表示させる場合、それを明記した形のコードになります。
試しにchats画面を司るstateを見てみると下記のようになっています。
.state('tab.chats', { // タブを使っていることが明示されています。 url: '/chats', views: { 'tab-chats': { // タブバーの中のどのタブの配下にあるページか明示されています。 templateUrl: 'templates/tab-chats.html', controller: 'ChatsCtrl' } } })
このタブバーとページの関係性はちょっと複雑で、
・各タブごとに独自の画面遷移履歴を持っている
・タブ間を横断した画面遷移が難しい
といった点があります。
そのため、タブごとに全く違う枝分かれで画面が遷移していくのなら良いのですが、あるタブから違うタブに遷移が必要なアプリなどは注意が必要です。
個人的にはそういった複数のタブからアクセスする可能性のある画面は、タブバー関係なく独立したページとして用意するほうがいいかな、と思っています。
もしくは、このサンプルでは頑張って他のタブ配下にある画面に遷移できるようにしているようなので、こういったものを参考に工夫する必要がありそうです。
www/js/service.js
app.jsで分割されたserviceはservice.jsに記載されています。コードはこんな感じです。
angular.module('starter.services', []) // チャット一覧画面とチャット個別画面で使われるfactory .factory('Chats', function() { // Might use a resource here that returns a JSON array // サンプルデータ var chats = [{ id: 0, name: 'Ben Sparrow', lastText: 'You on your way?', face: 'img/ben.png' }, { id: 1, name: 'Max Lynx', lastText: 'Hey, it\'s me', face: 'img/max.png' }, { id: 2, name: 'Adam Bradleyson', lastText: 'I should buy a boat', face: 'img/adam.jpg' }, { id: 3, name: 'Perry Governor', lastText: 'Look at my mukluks!', face: 'img/perry.png' }, { id: 4, name: 'Mike Harrington', lastText: 'This is wicked good ice cream.', face: 'img/mike.png' }]; // データを渡したり消したりする関数群 return { all: function() { return chats; }, remove: function(chat) { chats.splice(chats.indexOf(chat), 1); }, get: function(chatId) { for (var i = 0; i < chats.length; i++) { if (chats[i].id === parseInt(chatId)) { return chats[i]; } } return null; } }; });
‘Chats’という名前でfactory(MVCで言うモデルであるServiceの中の一つ)が用意されています。この’Chats’はおおまかに’chats’という名前でサンプルデータを用意する部分、そのデータを渡したり消したりする関数群に分かれています。そしてこの関数群が各controllerで呼び出されて、画面(view)に渡されていきます。
本格的なアプリとかになってくると、こういったServiceの中でサーバーとの通信などが実施されていくと思います。
www/js/controller.js
service.jsの’Chats’を呼び出してデータを画面(view)に渡すのはこのcontroller.jsの役割です。まず内容を見てみると下記のようになっています。
angular.module('starter.controllers', []) // 最初に表示されるダッシュボード画面で使われるコントローラー // この画面ではテキストを表示するだけなので、なんの記載もされていません。 .controller('DashCtrl', function($scope) {}) // チャット一覧画面で使われるコントローラー .controller('ChatsCtrl', function($scope, Chats) { // With the new view caching in Ionic, Controllers are only called // when they are recreated or on app start, instead of every page change. // To listen for when this page is active (for example, to refresh data), // listen for the $ionicView.enter event: // //$scope.$on('$ionicView.enter', function(e) { //}); $scope.chats = Chats.all(); $scope.remove = function(chat) { Chats.remove(chat); }; }) // チャット個別画面で使われるコントローラー .controller('ChatDetailCtrl', function($scope, $stateParams, Chats) { $scope.chat = Chats.get($stateParams.chatId); }) // アカウント画面で使われるコントローラー .controller('AccountCtrl', function($scope) { $scope.settings = { enableFriends: true }; });
myAppでは各画面にcontrollerが割り当てられています。
controllerは
.controller.('コントローラ名', function(依存サービス){ // ここに処理内容を書いていく });
という形で記載するのですが、ここで少し注意が必要なのは依存サービスです。
例えばcontrollerとviewの間でデータや関数の橋渡しになる$scopeやcontroller内で呼び出したいservice等が依存サービスに当たりますが、開発を進めていくと単純にここにサービス名を書き忘れてしまうことが多いので、まずエラーが出たらこれを疑うといいと思います。
www/templates/
最後に、画面表示のためのviewはtemplatesフォルダに格納されています。
これはhtmlファイルなのでとてもとっつきやすいのですが、controllerで用意した$scopeを画面に表示するバインディングの記載だけ、AngularJSを触ったことのない方には新しいと思います。
一例としてファイルでのバインディングを見てみると、
<ion-view view-title="Chats"> <ion-content> <ion-list> <!-- ng-repeat="chat in chats"ではcontrollerからchatsを取ってきてそれをループして一つずつデータを取り出しています。 --> <ion-item class="item-remove-animate item-avatar item-icon-right" ng-repeat="chat in chats" type="item-text-wrap" href="#/tab/chats/{{chat.id}}"> <!-- 二重の波括弧で囲ってcontroller内の$scopeを呼び出して表示しています。 --> <img ng-src="{{chat.face}}"> <h2>{{chat.name}}</h2> <p>{{chat.lastText}}</p> <i class="icon ion-chevron-right icon-accessory"></i> <ion-option-button class="button-assertive" ng-click="remove(chat)"> Delete </ion-option-button> </ion-item> </ion-list> </ion-content> </ion-view>
こんな感じです。{{ }}で囲ってある部分が、controllerとのつなぎの部分になっているんですね。
AngularJSの説明の記事を読むと、ng-controller=””という属性をhtmlタグに埋め込んで、どのcontrollerを活用するかを明記しているものが多いんですが、
myAppの場合すでに$stateProviderでhtmlファイルとcontrollerが紐付けられているので、この明記の必要はないようです。
ionic開発の特徴
ionicを使って開発をする上で特徴的な点がいくつかあるので、ざっとご紹介します。
ionic独自の部品
ionicでは独自のタグがたくさん用意されていて、このタグを呼び出すことでボタンやリスト、カードなどを画面上に配置できます。
基本的なアプリであれば、独自で用意されている部品で十分事足りるかなーと思います。
特殊なアプリを作る場合は、どんな部品が用意されているかまず公式サイトで見てみることをおすすめします。
特殊なことがしたい時
上記の公式サイトなどでやりたいことが見つからない時は、third partyから提供されている部品を探すか、ない場合自分で作ることになると思います。
ionicは全世界で使っている人が大勢いるので、コミュニティが活発だったり、いろんなコードをいろんな人が上げてくれています。
例えばメッセンジャーアプリみたいなものを作りたいと思ったら、『ionic messanger example』みたいなワードで検索すると結構ソースコードがヒットします。例えばこんなサイトです。
このcodepenというサイトに多くのサンプルがあるので、かなり参考になると思います。
アイコン
ionicではアイコンも意されています。
結構種類が多いのでここで大体は事足りると思いますが、独自のアイコンを使いたいといった場合にも対応しているようです。
また、アイコンの色は基本的に背後のカラー(赤いボタンなら赤、青いボタンなら青など)に依存するのですが、これもionicのソースコード自体を掘り下げていけば好きな色に変えることができるみたいです。
※このあたりは実際に試していません。
フォーラム
ionicはフォーラムも活発です。
普段プログラムを書いていてわからないことがあった時には、だいたいQiita、ブログ、stackoverflowに行き着くかと思います。
ionicは日本語の情報があまりないため、stackoverflowで情報収集することがほとんどかと思いきや、ionicのフォーラム
https://forum.ionicframework.com/
に多くの情報が集まっているため、8:2くらいでフォーラムに行き着きます。
ionic view
開発しているアプリを確認する方法はいくつかあります。
・Chromeで見る:先ほど行ったようにChromeで立ち上げて見る方法です。
・エミュレーターで見る:JavaSDKやAndroidエミュレーター、XcodeがインストールされていればPC上でエミュレーターで見れます。
・実機に落として見る:Android、iPhoneなどをPCとつなぎ、スマホにアプリを落として見れます。
※iPhoneの場合 Apple Developper Programでの登録や証明書発行、端末登録、Provisioning Profileのインストールなどが必要です。
といった方法に加え、ionicで用意されているionic viewというスマホアプリで見る方法があります。
これは作成したアプリをクラウドに上げ、ionic view経由でそのアプリをインストールし、スマホ上で見るというものです。
個人的にはこれがとてもおすすめで、流れとしてはソース書いて、アップロードのコマンドを叩いて、ionic viewでダウンロードして、すぐスマホで見れる、という感じです。
hybridアプリを作っていると、どうしてもプラットフォームごとに表示や挙動が違うことが発生します。そのため、PCのエミュレーターで表示するかスマホの実機にインストールしてデバッグをしながら、プラットフォーム間の差を見つけ、埋めていくという進め方になります。
しかしPCのエミュレーターは用意するのがけっこう大変で特にiOSの場合ビルドに時間がかかったり、スマホの実機にインストールするのもiOSの場合それまでの手順が煩雑だったりします。
なのでionic viewを使って実際のスマホでの動作を見たりデバッグできるのは、とても助かるのです。
イメージ的にはMonacaデバッガーみたいな感じかと思います。
例えば受託でアプリ開発の仕事を取ってきて、ある程度できたのでお客さんに進捗を説明するとき、なんかはお客さんにionic viewダウンロードしてもらって作り途中のアプリを実際に触ってもらう、みたいなこともできちゃいます。
(共有されたアプリを見るための専用アカウントみたいなものがあるので、お客さんにはこのアカウントを割り当てて、説明のタイミングごとに共有して見せることができます。)
以上、ionicで開発を始める前に読んでもらえると。少しでも開発工数が下がると思われる内容をまとめてみました。
どうもありがとうございました。