こんにちは。雨でハーフマラソンの大会をサボったiOSエンジニア庄司です。 本来大会で走っているであろう頃にこのブログを書いています。 今回はiQONアプリのWebViewで使っている技術についてです。 iOSでもAndroidでも使える内容なので、"UIWebView"ではなく"WebView"です。
実装の経緯
少ない開発リソースでマルチプラットフォームに対応するため、WebViewを利用することがよくあると思います。 iQONでも一部の機能において、iOSアプリ、Androidアプリ、スマートフォンブラウザで同一のWebViewを使って実装しているところがあります。 このWebViewについて、プラットフォームごとに別々のタイミングで変更があると、「Androidの特定のバージョンにカメラを起動するボタンを設置したい。」「でも、iOSはアップデート申請が通るまでボタンは表示できない」といったように、プラットフォームやバージョンごとにモジュールを出し分けたいといった要望が出てきました。
概要
以下の要件について実装しました。
- アプリのプラットフォームやバージョンごとにWebView内で表示するモジュールを変える。
- 少しのデザイン修正なら、別のURLを切るのは面倒。同一のURLで済ませたい。
- 同じ内容のページなら、iOSでもAndroidでも同じERBテンプレートを使いたい。
実装
アプリの実装はiOSを例に説明します。 Androidについても基本的なロジックは変わりません。
フロー図
アプリからWebViewにHTTPリクエストする際に、UserAgentに "iQON/1.5.1" のようにバージョン情報を付加します。 Webサーバは、UserAgentからアプリのバージョンをチェックしてモジュールの出し分けを行いHTMLを返します。
iOSアプリの実装
UserAgentUtil.m
Webサーバにリクエストする際に送るUserAgentにアプリのバージョン情報を付加します。
#import "UserAgentUtil.h" #import "UIDevice-Hardware.h" #import "ASIHTTPRequest.h" @implementation UserAgentUtil + (void)setCustomUserAgent { NSString *defaultAgent = @"Mozilla/5.0 (iPhone; CPU iPhone OS 5_0_1 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Mobile/9A405"; NSString *iosDevice = UIDevice.currentDevice.platformString; NSString *osVersion = UIDevice.currentDevice.systemVersion; NSString *appVersion = NSBundle.mainBundle.infoDictionary[@"CFBundleShortVersionString"]; NSString *useragent = [NSString stringWithFormat:@"%@ iQON/%@ (%@; iOS %@)", defaultAgent, appVersion, iosDevice, osVersion]; // UserAgentにアプリの情報を含めて設定 ASIHTTPRequest.defaultUserAgentString = useragent; } @end
AppDelegate.m
アプリの起動時にカスタマイズしたUserAgentを設定します。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { ... // アプリ起動時にUserAgentを設定 [UserAgentUtil setCustomUserAgent]; ... return YES; }
Webサーバ側の実装 (Ruby on Rails)
ApplicationHelper
アプリのバージョンチェック用のヘルパー バージョンチェックにはRubyネイティブのGem::Versionを使っています。
module ApplicationHelper # UserAgentからiOSかどうかを判断 def ios? @ua ||= request.user_agent !!(/iOS|iPhone|iPod/ =~ @ua) end # UserAgentからAndroidかどうかを判断 def android? @ua ||= request.user_agent !!(/Android.*Mobile/ =~ @ua) end # iOSアプリのバージョンチェックメソッド def ios_app_version?(operator, version) ios? && valid_app_version?(operator, version) end # Androidアプリのバージョンチェックメソッド def android_app_version?(operator, version) android? && valid_app_version?(operator, version) end # バージョンチェック処理 def valid_app_version?(operator, version) unless %r!iQON/([^ ]+)! =~ @ua return false end # $1: 正規表現でチェックしたユーザのアプリのバージョン。"1.5.1"のような文字列 # version: 制限対象バージョン # operator 制限対象バージョンに対する演算子 compared = Gem::Version.new($1) <=> Gem::Version.new(version) case operator.to_sym when :>=, :<=, :>, :<, :== return compared.try(operator, 0) else return false end end end
ERBテンプレート側の実装例
バージョンチェックのヘルパーを使ってモジュールの出し分けます。
<% if ios_app_version?('>=', '1.4.0') || android_app_version?('>=', '1.0.31') %> <%# iOSアプリ1.4.0以上、または、Androidアプリ1.0.31以上のみ、カメラ起動ボタンを表示 %><a href="iqon://camera/">カメラ起動</a> <% end %>
まとめ
今回はアプリで表示するWebViewで同一URLでもバージョンごとにデザインを分ける方法について書きました。 マルチプラットフォームのサービスのiQONでは、こういった方法でデザイン実装工数を減らすことができました。 大きなデザインの変更の場合は、別のURLで実装したり、そもそもネイティブで実装したほうが管理しやすいでしょう。 適切に判断して使ってみてください。