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

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

スマホ用アプリ

降りつぶし.netは、⁠日本の鉄道駅」にいつ乗下車したのか、を管理するソリューションです。前回まで、過去の記録を管理するためのWebアプリケーションと、そのWebアプリケーションとデータを同期する仕組みについて説明しました。そして今回と次回にわたり、リアルタイムに記録を更新することができる、スマホアプリの開発について説明していきます。今回は、最初に開発・リリースを行った、iPhone用アプリケーション「i降りつぶし」についてです。

iPhoneアプリの開発

i降りつぶしは、2011年12月に最初のバージョンをリリースしています。Webアプリ版は同年11月末の公開でしたので、わずか半月で開発……できるわけはありません。

実際には、Webアプリの開発と同時並行で進めていました。このためにMacBook Airを同年8月末に購入、ただちに着手していましたので、3ヶ月かかったことになります。

もちろん、それまで何も経験がないところからでは、3ヶ月での開発も不可能です。実はそれ以前、2010年末から2011年夏にかけ、iPad用の商品カタログアプリ(Ad-Hoc版⁠⁠、地図を操作するアプリ(App Store経由での広告付無料配布)の開発を受託したことがあり(開発機は貸与⁠⁠、Objective-Cはその際に覚えました。ちょうど、単なる画面切り替えアプリを手探りで作り、次にMapKitでの位置情報サービスアプリの開発で細かいノウハウを吸収する、という手順で、まさに「お金をもらいながらの勉強」をさせていただきました。

筆者のプログラミング歴は、未成年時代のBASICは別として、MS-DOS用のいわゆるフリーソフトウェアを1988年に作りはじめてからとなります。もちろん当時はCで開発するしかなく、その後プロになりちょうどWindows 3.1の開発に関わるようになってC++を覚え、以来2001年まではほぼC++での開発をメインに行なっていました。その経験もあり、Objective-Cとはじめて接したときも、そのきわめて柔軟な仕様にはプラスイメージしか感じず、久々に「言語を操ること」じたいを楽しみながら開発できました。

そんな中、実質3本めのiOSアプリ開発となったのがこのi降りつぶしです。

地図画面

i降りつぶしは、iPhoneアプリとしてはごくごく一般的な、ポートレート(縦長)固定画面のタブバー切り換えアプリケーションです。そしてアプリ起動直後の表示は地図画面となります。

図1 地図画面
図1 地図画面

きわめてオーソドックスな地図画面です。もちろん、スワイプやピンチで表示領域を変化させることができます。

上部には現在位置移動ボタン(タップにより中心が現在位置に移動)および検索バーがあり、下にツールバーがあります。そして地図部分をシングルタップすると、トグルでその2つのバーの表示・非表示が切り換わります。決して広くない地図画面を少しでも広げるための、地図アプリではオーソドックスな工夫です。

また、画面上には、i降りつぶしが管理する鉄道駅が、ピンでプロットされています(iOSでは地図上のマーカをアノテーションと呼びますが、以下、ピンとします⁠⁠。赤いピンは乗下車していない駅、緑のピンは乗下車済の駅です。

これらのピンをタップすると、コールアウト(吹き出し)が表示されます。さらにこのコールアウト右側の情報ボタンをタップすると、後述する「駅情報画面」が表示されます。

図2 地図画面での表示の切り換え
図2 地図画面での表示の切り換え

下部のツールバーには2セットのセグメンティッドコントロールが配置されています。

左側のコントロールは、画面上に表示するピンの種類を、⁠全駅」⁠乗下車済駅のみ」⁠未乗下車駅のみ」と切り換えます。自分が乗下車した駅がどのくらいあるのか、残りは何駅あるのか、を見て、ニヤニヤするためだけの機能です(笑⁠⁠。

右側のコントロールのうち、⁠地図」⁠空撮」⁠複合」は、Googleマップなどと同じ意味です。唯一特殊なのが「一覧」で、これをタップすると、地図上に表示されている駅を、地図の表示の中心位置から近い順に、テーブルビューで一覧表示する、という機能です。

これは降りつぶしアプリには必須の機能なのです。同じ駅に複数の鉄道事業者が乗り入れている場合、また同じ事業者でも新幹線駅と在来線駅がある場合(降りつぶし.netでは、新幹線の駅は在来線の駅とは「別の駅」と扱う仕様です⁠⁠、厳密な「停車場中心」を調べきれていないため第2回参照⁠⁠、駅の位置情報が同じになっている場合があります(図2の小田原駅がまさにそれ⁠⁠。この場合、地図上のピンをタップしてそれぞれの駅の吹き出しを出し分けることは不可能です。そんなとき、この「一覧」を選択することによって、ピンが重なっていてもすべてを表示し分けることが可能となっています。もちろん、この各項目をタップすると、地図のコールアウトをタップしたのと同じく、⁠駅情報画面」が表示されます。

図3 検索画面とその結果
図3 検索画面とその結果

上部の検索バーをタップすると地図検索モードとなり、スコープバーもどきが表示されます。これはiOS標準のスコープバーではなく、同じビジュアルのビューを自前で表示しているものです。

セグメンティッドコントロールが2つ表示されていますが、上のコントロールは検索種別の指定です。下のコントロールは「駅名」⁠読み」⁠路線」を上で選択したときに限り有効で、検索語の前方一致・部分一致を選択できます。

そして上のコントロールで「地点」を選択して検索を実行すると、入力された語句をGoogle Maps APIで検索し、検索結果のうちユーザが選択した地点を地図の中心に移動させます。この機能は地図表示にのみ影響するもので、駅データベースへの操作を含みません。

一方、その他の検索種別を指定した場合は、アプリ内部のSQLiteデータベースから条件に一致する駅を検索し、地図上にそれらのみを表示させるデータベース検索のフィルタとして動作することになります。

このような、同じ操作にまったく異なる2つの動作が混在しているUIは、一般的には好ましいものではありません。ただ、一度試してみればその違いは直感的に明らかとなり、使い分けも自然と行えるだろう、と判断しての実装となっています。

駅情報画面・乗下車日画面・メモ画面

地図画面のコールアウト(吹き出し)をタップした場合、あるいは地図画面または他の画面にて表示されている駅の一覧から特定の駅をタップした場合、この画面が開きます。

図4 駅情報画面・乗下車日画面・メモ画面
図4 駅情報画面・乗下車日画面・メモ画面

言うまでもなく、駅データベースとしてのi降りつぶしの、まさにデータベースレコードを表示します、という画面です。そしてもちろん、乗下車記録の管理もここから行います。

「乗下車」をタップすると乗下車日画面が開きます。乗下車済か未乗下車かの切り換えはスイッチコントロールで行い、年・月・日に「不明」を含み、スイッチが「オン」になっていれば、フリックやスワイプで無限に回転するようにカスタマイズした日付ピッカーコントロールで指定された日付が記録されます。

なお、この画面が閉じられる際、日付ピッカーの位置は保存され、次にこの画面を開いた駅が「未乗下車」状態だった場合、日付ピッカーは前の駅で入力したのと同じ日付になります。これは、スマホから過去の記録を一括入力する場合の利便性を考えて実装した機能です。入力し忘れていた昨日降りた駅を連続して入力したい、というような場合、日付ピッカーの操作が一切不要となります。

また、⁠メモ」をタップするとメモ画面が開きます。説明すべきことは……とくにありません。

そして筆者が個人的に重宝しているのが、駅情報画面の残り3つの項目です。

「今日、乗下車しました!」をタップすると、乗下車日画面を開くことなく、ただちに現在日が乗下車日として設定され、この項目自体が消滅します。この消滅はiOSのUIとしてはやや問題ありですが、実際に駅で記録をつける際に絶大な威力を発揮しています。

「この駅をウィキペディアで見る…」をタップすると、Safariが開いてウィキペディアの当該駅の項目を読めます。これもまた、駅構造や駅営業時間、駅前の様子などでふと思った疑問を現場で調べるのに重宝しています。

「この駅を地図の中心に表示…」は、これをタップしても何も起こりませんが、その後で地図画面に戻る、あるいは開くと、この駅が画面中央に来ています。これは、他の一覧画面から駅を調べ、その駅の周囲の地図を確認する場合、たとえば通勤中などに次回の旅の計画を考えたりずるときに有用です。

全事業者画面・事業者画面・路線画面・駅一覧画面

タブバーの「全事業者」をタップすると開く画面です。テーブルビューによる一覧表示で、Webアプリと全く同じ遷移第3回参照)を行います。各項目の表示順もWebアプリと完全に同一です。

図5 全事業者画面・事業者画面・路線画面・駅一覧画面
図5 全事業者画面・事業者画面・路線画面・駅一覧画面

この画面のレイアウトは、一覧表示系のすべての画面で共通です。プログラミング的には、UINavigationControllerを継承したGroupsViewControllerクラスを共通の基底クラスにして処理しています。

標準UIのナビゲーションバーの下には、独自のヘッダがあります。このヘッダには、その画面で表示されているすべての駅の数と、そのうち乗下車済駅の数、乗下車済率、残りの駅数が表示されます。

テーブルビューの各セル(行)は、左端に乗下車済かどうかがわかるアイコン(緑丸が乗下車済、赤棒線が未乗下車。非同期ロード中は灰色の三点リーダ⁠⁠、大きめの太字で事業者種別・事業者名・路線名・駅名、その下に小さめの細字で情報、というレイアウトになっています。この情報は、駅一覧画面では乗下車日を、それ以外ではヘッダと同じ統計情報を、それぞれ表示しています。

すべてのセルはタップ可能で、駅一覧画面以外ではより深い階層の画面が開かれ、駅一覧画面では先の駅情報画面が開かれます。

なお、これら4画面のうち、路線画面のみ、ナビゲーションバーの右上に「地図選択」というボタンが表示されます。これをタップすると、地図画面の検索バーに、この画面で表示されている路線の駅のみのフィルタリングが設定されます。もちろん、地図上のピン表示も、この路線の駅のみとなります。

全都道府県画面・駅一覧画面

図6 全都道府県画面・駅一覧画面
図6 全都道府県画面・駅一覧画面

都道府県別の統計を見ることができます。2階層のみの構成で、都道府県をタップすると、その都道府県に位置する全駅の一覧が表示されます。駅一覧は、市郡名以下の住所の文字列による文字コード順で並べているため、実用性はあまりありません。全都道府県画面で各都道府県別の統計情報を見る、という用途が主となります。

駅一覧画面の各セルの情報表示は、乗下車日と住所をコロンで並べたものです。

また駅一覧画面のナビゲーションバーの右上には「地図選択」ボタンがあり、タップすると地図画面にて当該都道府県に位置する駅のピンのみがフィルタリング表示されるようになります。

読み画面・読み画面2・駅一覧画面

図7 読み画面・駅一覧画面
図7 読み画面・駅一覧画面

読みがな別の統計を見ることができます。3階層構成で、読みがなの先頭1文字をまず選択し、次に読みがなの先頭2文字を選択して、3階層めで読みがな先頭2文字が一致する駅の一覧となります。

まるで実用性がなく、むしろ降りつぶしとは関係ない雑学豆知識ネタ(⁠⁠ぴ」ではじまる駅はあるのに「ぷ」ではじまる駅はない、など)としての活用が主となるでしょう(笑⁠⁠。

駅一覧画面のソート順は読みがな順、各セルの情報表示は乗下車日・フルの読みがな・事業者名を並べたものです。ナビゲーションバー右上の「地図選択」ボタンは、タップすると地図画面にて当該読みがな1文字または2文字ではじまる駅のピンのみがフィルタリング表示されるようになります。

なお、この画面には現在、不具合があります。鉄道ファンや多くの三重県民にとっては「あるある」話ですが、JR西日本・近鉄・伊勢鉄道の津駅の扱いです。なにせ読みがなは「つ⁠⁠、2文字目が存在しません。しかしこの画面での一覧はあくまで「読みがなが○○ではじまる駅」ですから、⁠つ(2文字目なし⁠⁠」という読みがな2文字の駅一覧を単純にデータベースから検索すると、⁠つ」で始まるすべての駅が表示されてしまいます。アプリの次期更新で、1文字駅を特別扱いするコードを追加し、この不具合を解消する予定です。

乗下車年別画面・乗下車月別画面・乗下車日別画面・駅一覧画面

図8 乗下車年別画面・乗下車月別画面・乗下車日別画面・駅一覧画面
図8 乗下車年別画面・乗下車月別画面・乗下車日別画面・駅一覧画面

乗下車日別の統計を見ることができます。最大4階層構成で、乗下車年を選び、次に月を選び、さらに日を選ぶと、最大4階層めでその日付に乗下車した駅の一覧が表示されます。

この画面の実装上の最大の特異は、階層が4階層固定にならないことです。降りつぶし.netは、乗下車した年月日を年・月・日それぞれのレベルで、あいまいに入力することができます。そのうち、乗下車年不明・年はわかっているが月日不明、という2つのパターンにおいて、階層がそれぞれ減ることになります。また、未乗下車の駅一覧も、最上位階層の次はすぐ駅一覧画面になります。

このため、乗下車年別画面で「年月日不明」⁠未乗下車」⁠月日不明」がタップされた場合を、各セルに対応する日付値第2回2ページ目を見ることで判別し、該当した場合は駅一覧画面を開き、それ以外の場合は1階層低い画面を開く、という実装をしています。

駅一覧画面のソート順は、とくにキーがないため、エンドユーザには意味不明なのですが、駅コード順です。

同期画面

前回解説した、本ソリューション最大のキモである同期機能を、実際に実行する画面です。

図9 初期の同期画面
図9 初期の同期画面

初期状態では、画面には「同期を開始する」というボタンがあるのみです。一方、既に同期機能を利用したことがある場合、ログイン状態が保持されていれば、その他にも「同期日時をクリア」⁠ログアウト」というボタンが表示されています。

実際には、これらのボタンが貼り付けられた全画面のビューと、Webページを表示する全画面のWebビューの2つが重ねられており、状態遷移によっていずれかのみを表示するようになっています。またログイン状態と非ログイン状態でのボタンの表示の差異は、単に各ボタンの表示・非表示を切り換えているだけです。

同期開始ボタンをタップすると、Webアプリに非同期通信でアクセスします。Webアプリ側での動作は第3回2ページに記載されているとおりで、認証済であればテキストでユーザ名を返し、認証されていなければログイン画面をHTMLで返します。i降りつぶしは、ユーザ名を受信できたらそのまま前回解説の同期機能を実行し、ログイン画面を受信した場合はそのHTMLをWebビューに注入して表示を切り換えます。

図10 Webアプリからの認証画面
図10 Webアプリからの認証画面

この表示は、スマホアプリに組み込まれたUIではなく、Webアプリによる同期専用の認証画面のHTMLです。

ここでユーザ名とパスワードを入力し、⁠ログイン」をタップすると、Webアプリの認証が行われます。認証が成功すればボタンのビューに切り換えて同期を実行し、成功しなければふたたびこの画面を表示します。

またこの表示の下部には「Login with Twitter」ボタンがあります。これがタップされた場合、Twitterによる認証画面が表示されます。

図11 Twitterからの認証画面
図11 Twitterからの認証画面

前回説明しました(3ページ)が、このOAuth認証の利用が、同期画面の実装をややこしくしています。

このTwitterの画面には「Twitterに登録する」「プライバシーポリシー」など、i降りつぶしの同期機能には直接関係のないリンクがたくさん潜んでいます。そしてこれらがタップされた場合、i降りつぶし内部ではなく、Safariを起動してそちらでリンク先を開く必要があります。i降りつぶしの画面はあくまでもWebビューであってフルスペックのWebブラウザではないため、その先のリンク遷移を十分に行えないからです。

一方、⁠連携アプリを認証」「キャンセル」がタップされた場合は、これをSafariに渡してはいけません。引き続きi降りつぶし内でハンドリングし、OAuthの手順に従ってリダイレクトを処理することで、最終的にはWebアプリへのログインが認められることになるからです。

結局、i降りつぶしは、⁠TwitterのOAuth認証ページ内のリンクのうち、どのリクエストがOAuth認証に直接必要なものなのか」を知っている必要があります。そしてそのURLへの遷移は認証の遷移としてアプリ内で管理し、それ以外へのリンクはSafariを起動して処理しなければなりません。

この実装は、どうしてもOAuthサービスプロバイダ(認証を行う側)の実装に依存してしまいます。

図12 同期実行中の画面
図12 同期実行中の画面

認証が成功すれば、後は同期機能を実行するのみです。それぞれの処理はメインスレッドから起動され、現在の実装では、iOSの機能であるGCD(Global Central Dispatch)を用いて、別スレッドによる並行処理をしています。

ここで問題になるのが、⁠SQLiteは1つの接続を複数スレッドから同時に使うことはできない」という大きな制限です。これは、厳密に言えば「不可能」なのではなく「1つの接続に関してマルチスレッド対策が一切取られていない」というべきものです。取られていないのですから、同時に複数スレッドから参照することが絶対にないようにすれば問題ないはずです。

意図して実装しない限り、フォアグラウンドにある画面でしかコードが実行されない、というiOSならではの制限を逆に生かし、i降りつぶしでは、ビューが表示されるときに並行処理キューを生成し、ビューが消える直前にキャンセルする、という処理を入れることで、SQLiteのマルチスレッド問題を回避しています(キャンセルには多少手間がかかっています⁠⁠。

そしてこのことは、同期画面から他の画面へと移動した場合、同期の実行を継続できない、ということも意味しています。実際、ナビゲーションバー右上のキャンセルボタンをタップすると、その時点で同期はキャンセルされますし、タブバーで別画面を選んだ場合やホームボタン押しでアプリがバックグラウンド化した際にも、アラートビューにより中断を通知しつつ、同期はキャンセルされます。

お知らせ画面・設定画面

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.と同じ通知を各画面が受け取ります。この場合、そもそもの駅データベースに変更がある可能性もあるので、各画面でロードしている駅データなどをすべてリセットし、次回表示時にロードしなおしています。

苦慮したiOS7対応

最後に、せっかくのタイミングですので、iOS7対応について軽く触れておきます。

図14 iOS7における地図画面・一覧表示画面
図14 iOS7における地図画面・一覧表示画面

最近の筆者はAndroid端末を常用しており、iPod touchはまさにiPodとして使っていたため、最新情報に疎く、⁠フラットデザインに変わる」という情報しか頭に残っていませんでした。それはそれで対応はいずれ必要であっても、とりあえず動くだろうから放置して後日対応しようかな、とたかをくくっていたのですが、結果、してやられてしまいました。

実際にiOS7で従来のi降りつぶしを動かしてみたところ、以下のような問題が発生しました。より深刻だった順です。

  1. 検索バーが表示されず、クリックしても無反応となり、検索機能が利用できない
  2. 1.を解消しても、地点検索で複数の候補が検索された場合、候補の一覧表示が出ず、地点を選択できない
  3. インターフェースビルダ(画面設計をGUIで行う、Xcode内蔵のツール)ではなくコードで画面を作っていると、ビューの位置がおかしくなり、上部がナビゲーションバーに隠れてしまったり、下部に余分な空白が出たりする
  4. スイッチコントロールの幅が大きく変わったため、配置の見栄えが悪化
  5. 角丸矩形ボタンのデザインが変わり、枠線がなくなった結果、同期画面の視認性が著しく悪化した
  6. ステータスバーやナビゲーションバーの後ろが透け、テーブルビューのスクロールが見えているのに、各種一覧画面でステータスバーの下部につけた独自ヘッダの部分だけ透けておらず、違和感がある
図15 iOS6.1とiOS7でのアラートビュー
図15 iOS6.1とiOS7でのアラートビュー

1.は、iOS6以下で、検索バーを半透明なナビゲーションバーに入れるためのハック、および空欄の際に仮想キーボードの「検索」ボタンをタップできないようにするためのハックを入れていたためでした。幸いにしてこれらのハックじたいがiOS7では不要となったため、iOS7ではハックコードを実行しないようにして解決しました。

2.は、アラートビューの内部に任意のビューを入れるというハックがiOS7で一切動作しなくなったため、候補一覧を収めたテーブルビューが挿入できず、表示が出なくなった、というものでした。これは解決策がなかったため、iOS7の場合、見かけ上の大きさを背景を半透明にすることでアラートビューと同じにするというモーダルビューを表示し、ごまかすことにしました。このUI、標準のマップアプリでは実現しているので、何か非公開APIはあるのかもしれませんが……。

3.と4.は、UIをコードでデザインすることの危険性を、改めて体感することとなりました。これはフラットデザイン化で、画面の表示領域がステータスバーの領域まで広がったことによります。インターフェースビルダで設定すると自動的に調整されるのですが、コードだとベタ書きするしかなかったのです。

図16 iOS7における旧バージョンと最新バージョンの同期画面
図16 iOS7における旧バージョンと最新バージョンの同期画面

そして5.は、結局まともな対処方法を編み出すことができず、ご覧のとおり、システムのボタンの幅を横100%にしてごまかすことにしました。

図17 iOS7での背景透過のまねごと。独自ヘッダ部分では緑丸アイコンと「門司駅」がうっすら見えている。ステータスバーおよびナビゲーションバーではblurによりアイコンの緑色がぼんやり大きく広がっている
図17 iOS7での背景透過のまねごと。独自ヘッダ部分では緑丸アイコンと「門司駅」がうっすら見えている。ステータスバーおよびナビゲーションバーではblurによりアイコンの緑色がぼんやり大きく広がっている

6.は、単に透けさせるだけなら、色と透明度の調整だけです。しかしよく見ると、ステータスバーやナビゲーションバーの背後の表示内容には、かなりのblur効果がかかっています。これを実現する公開APIは存在しないため、世にある各種コードを使ってリアルタイムでのblurを試みたのですが、iOS7の開発バージョンではいずれもメモリリークするという状態だったため、泣く泣く断念しています。

次回はAndroidアプリ編

次回は連載最終回、Android版アプリ「降りつぶしroid」の紹介です。

i降りつぶしと大きく実装が異なる部分を中心に説明する予定です。

おすすめ記事

記事・ニュース一覧