読者です 読者をやめる 読者になる 読者になる

アプリのバージョンごとにWebViewのデザインを出し分ける方法

こんにちは。雨でハーフマラソンの大会をサボったiOSエンジニア庄司です。 本来大会で走っているであろう頃にこのブログを書いています。 今回はiQONアプリのWebViewで使っている技術についてです。 iOSでもAndroidでも使える内容なので、"UIWebView"ではなく"WebView"です。

実装の経緯

少ない開発リソースでマルチプラットフォームに対応するため、WebViewを利用することがよくあると思います。 iQONでも一部の機能において、iOSアプリ、Androidアプリ、スマートフォンブラウザで同一のWebViewを使って実装しているところがあります。 このWebViewについて、プラットフォームごとに別々のタイミングで変更があると、「Androidの特定のバージョンにカメラを起動するボタンを設置したい。」「でも、iOSはアップデート申請が通るまでボタンは表示できない」といったように、プラットフォームやバージョンごとにモジュールを出し分けたいといった要望が出てきました。

概要

以下の要件について実装しました。

  • アプリのプラットフォームやバージョンごとにWebView内で表示するモジュールを変える。
  • 少しのデザイン修正なら、別のURLを切るのは面倒。同一のURLで済ませたい。
  • 同じ内容のページなら、iOSでもAndroidでも同じERBテンプレートを使いたい。

実装

アプリの実装はiOSを例に説明します。 Androidについても基本的なロジックは変わりません。

フロー図

flowchart

アプリから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で実装したり、そもそもネイティブで実装したほうが管理しやすいでしょう。 適切に判断して使ってみてください。