降りつぶし.net~同期するWebアプリ・スマホアプリの開発・運用~

第5回 iPhone用アプリケーション「i降りつぶし」の開発

この記事を読むのに必要な時間:およそ 9 分

お知らせ画面・設定画面

i降りつぶしにはあと2つの画面があります。

図13 お知らせ画面・設定画面

図13 お知らせ画面・設定画面

お知らせ画面は,単に降りつぶし.net公式Twitterアカウント@oritsubushiの最新ツイートを表示するだけで,現在はWebアプリ内のページをWebビューで表示するだけの内容となっています第3回3ページ⁠。ただ,Webビュー内のHTMLリンクをタップした場合に,Safariを起動してそちらで表示させる処理のみ入れてあります。またリロードボタンをナビゲーションバー右上に配置しています。

そしてこれも同期画面と同じく,すべてのリンクを無条件にSafariに渡すことができません。現在のWebアプリ側でのこのHTMLページの表示が,Twitter公式のウィジェットを貼り付けただけのものだからです。同ウィジェットはJavaScriptで動的に生成したIFRAMEにTwitter側のコンテンツを表示させており,iOSのWebビューはIFRAMEのオープンについてはリンクタップに準じる扱いをするためです。これまた同期画面と同様に,ウィジェットの内容を解析し,WebビューとSafariでの更新を出し分けています。

本来この機能は,IFRAMEウィジェットに任せず,Webアプリ側で@oritsubushiのツイートをポーリングし,i降りつぶし側と連携しての未読管理を提供すべきところです。それによってアプリに未読数バッジを表示し,お知らせの存在を周知することができるからです。今後の追加実装の検討課題です。

一方,設定画面は,現在のところ地図に同時表示できる最大のピン数を指定するのみとなっています

この設定項目は,次項で説明する,地図の実装に大きく関わります。

実装のポイント1~地図への駅のプロット

ここからは実装寄りの説明となります。

スマホの地図アプリ,とくに地点データベース(i降りつぶしの駅データベースもその一種)にとって,最大の問題は,実はそれら地点をどのように地図上にプロットするか,なのです。

スマホの地図は,スワイプやフリックで軽快にスクロールでき,ピンチやダブルタップで自由自在にズームできてこそ,の存在として広く認知されています。その上に10,000駅弱ある駅の位置をそのままプロットしたらどうなるか。日本全国を表示しているところで約10,000のピンが表示されているところからスワイプしたらどうなるか。

まったくおすすめできませんが,i降りつぶしの設定画面で駅数を「無制限(危険!⁠⁠」にしてみればすぐわかります。地図が広域を表示していた場合,そもそもメモリ不足でアプリは落ちます。そうでなくても,スワイプやフリックの度に恐ろしい負荷がかかります。

しかし世にあるiOS地図アプリの中には,この問題に何ら対処していないものも散見されます。あるいは,表示地域を限定することで(最初から地図を表示せず,地域絞り込み画面で先に選択させる,など)問題を回避しているアプリもあります。

前者はユーザビリティとして論外だと断ぜざるを得ません。後者については,とりわけ商用アプリにおいては「そうせざるを得ない」場合もあり得るでしょう。しかし,できれば,エリアが限定されないスクロールやズームと,スムーズなピン表示の切り換えの両立を考えたいものです。

筆者は,その両立のため,駅に優先度を設けることとしました(駅データベースカラムとしては第2回2ページに解説⁠⁠。

具体的には,新幹線駅,各路線の終端駅や結節駅,各自治体の玄関駅などに強い重みをつけ,残る駅には密度に応じたランダムな重みをつけています第2回のとおり,カラム名が意味と逆になってしまっており,値が小さいほうが優先度が高い⁠⁠。前者については現在有効な9,571駅のうちの1,409駅に対し,筆者が完全人力で1~9までの値を割り振り,残りの駅については日本全国をメッシュ化してそこからランダムに数駅ずつ拾ってくるPHPスクリプトを書き,約3,000駅に対して機械的に11および12の値を割り当て,残りの5,000余駅には13を割り当てました。

そして地図の表示領域がスクロールやズームで変化した場合,その領域内を範囲とし,設定画面で設定された最大駅数の範囲内で,重み(優先度)を第1ソートキー,⁠地図の中心からの距離の2乗(地球が平らだとした概算では,駅の経緯度と中心の経緯度とのそれぞれの差の2乗の和を求めればよい(三平方の定理⁠⁠」を第2ソートキー,として駅テーブルを検索しています。もちろんこれも別スレッドのキューに検索コードを置いて行います。

最大ピン数150(デフォルト値)での地図画面の操作

この実装の結果が上記の動画です。優先度を第1ソートキーとしたため,わずかなスクロールによってピンが消えたりする(スクロールインしてきた優先度の高い駅によって,スクロール後もエリア内に残るはずの優先度の低い駅が消されてしまう⁠⁠,という問題はありますが,それを補ってあまりあるスムーズ操作を実現できました。なおこれは実機に比して爆速と言えるiPhoneシミュレータでの状況ですが,筆者が所持するiPod touch 第4世代でも,この動画とほとんど変わらない操作ができています。

この優先度の考え方,これは各地点に「順位をつける」ということに他ならず,商用アプリでは採用しづらいものです。i降りつぶしでは,各路線の終端駅に高い優先度をつけることにより,結果として駅数の少ない鉄道事業者でも必ずピンが表示されるため,⁠なぜウチが表示されずヨソが表示されているのだ」という問題は発生しませんが,やはりこれは駅ゆえの特異ケースです。

これをも解決するには,SQLiteではなく独自の地図検索エンジンを実装しそれを用いる,優先度を一定のタイミングで変化させる,などの策が考えられます。

実装のポイント2~乗下車記録のアプリ内同期

前述した同期画面は,Webアプリとi降りつぶしの間で,乗下車記録を同期させるものでした。

しかし,同期の実装はこれだけではありません。アプリ内部でも,乗下車記録を同期させなければならないからです。

具体的には,駅情報画面から乗下車日画面を開くなり「今日,乗下車しました!」をタップするなりした場合,すなわち個別の駅の乗下車記録が変更された場合の動作です。このとき,以下のような,他画面への副作用が発生し得ます。

  • 他の画面から同じ駅の駅情報画面を複数開いている場合,その内容を変化させなければならない
  • 地図画面において,当該駅のピンの色を変化させなければならない
  • 各一覧画面において,セルに表示される緑丸または赤棒線のアイコンを変化させなければならない
  • 各一覧画面において,ナビゲーションバーの下のヘッダに表示される統計情報を変化させなければならない
  • 地図画面において,ツールバーのセグメンティッドコントロールで乗下車済駅のみまたは未乗下車駅のみを表示している場合,ピンを削除するかまたは新たに立てなければならない
  • 乗下車年別画面およびその下層の画面において,その階層自体が消滅する場合があり,その場合の表示について考慮しなければならない(2013年に降りていた駅が9月1日の東京駅のみだったとして,そこから駅情報画面を開いて記録を未乗下車に変更すると,2013年どころか9月の階層も1日の階層も消滅する)

これらについては,iOSの通知センターにより,すべての画面にデータベース更新を通知することで解決しています。そもそも駅データベースの更新は,i降りつぶしでは次の3パターンでしか発生しません。

  1. 起動時,データベースがないか古かった場合,データベースファイル自体を上書きして更新
  2. 駅情報画面からの,completionsテーブルへの駅単位での書き込み
  3. 同期画面での,Webアプリから送信されてきたSQL文の実行

これらのうち1.は,画面表示の前に行われるため,同期は不要です。

2.はまさに上のケースですが,この場合,書き込みが行われた後,各画面は更新された駅を通知で受け取ります。各画面では,それが現在の表示内容に関係する駅なのかどうかをそれぞれ調べ,関係があった場合は適宜表示内容を更新する,という処理を行っています。この際,渡されるのが駅コードではなく駅そのものだという点がポイントで,その内容を調べることにより,非表示の画面からデータベースにアクセスする必要がなくなっています。前述した,SQLiteのマルチスレッド問題も回避できるわけです。

3.については,駅をnil(CでいうNULL,Javaでいうnullとほぼ同じ)として,2.と同じ通知を各画面が受け取ります。この場合,そもそもの駅データベースに変更がある可能性もあるので,各画面でロードしている駅データなどをすべてリセットし,次回表示時にロードしなおしています。

著者プロフィール

よねざわいずみ

合資会社ダブルエスエフ代表社員。学習塾講師やら芸能ライターやら劇団主宰やらいろいろ経て現在はよろず請負プログラマ。最近の開発はPHP,JavaScript,Java,MTプラグインなど。お仕事随時募集中。

Twitter:@yonezawaizumi

鉄道旅行ブログ:http://feelfine.blog.izumichan.com/