#6 ガワネイティブアプリをつくろう!!

ガワネイティブアプリをつくろう!

こんばんは。カヤックのAdvent Calendar6日目は、@fnobiがお送りします。

最近の業務ではいけてるSDKを作るため、Javaを書いたりC#を書いたりC++を書いたりしつつ、

Webを作るのも大好きなのでJavasciptをがりがりしたりしています。雑食エンジニアです。

さて今回のブログですが、専門的な話をしても、弊社のもっと専門的な人に踏み潰されそうな予感がするので、 こちらも雑食な話をしようと思います。

テーマはズバリ、 ガワネイティブアプリです!

ガワネイティブアプリとは

ガワネイティブアプリとはつまり、 ガワ(外側)はスマホアプリとして書くけれど、コンテンツはほとんどWebViewっていうアプリのことです。

こういう作りにすることによって、

  • ネイティブアプリなので、通知やデバイスの動きや課金などなど、 OSの機能をフルに使える
  • コンテンツはWebサイトとして作っているので、 いつでも更新・修正が可能

という、2つのプラットフォームのいいとこどりができるわけです。

ソーシャルゲームなどは特に、こういう作りになっているものが多いですし、

ぼくのチームで開発しているLobiアプリでも、WebViewは有効に活用されています。

そんなわけで今回は、

  • この記事を読むだけで、
  • Android・iOSに両対応した
  • ガワネイティブアプリが作れるよ!

というのを目指したいと思います。 WebとかiOSとかAndroidとか、どれかはやってるけどどれかはやってない、みたいな方におすすめです。

1, HTML編

まずは、さくっとHTMLを書いてみます。いったん深く考えなくてOKです。 しいていえば、viewportをちゃんと設定して、スマホでまともなサイズに出るように気をつけましょう。

<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">

また今回は、公開のためにデプロイ環境をうんぬん等するのが面倒なので、GitHub Pagesの仕組みを使ってみました。 「gh-pages」ブランチ切るだけでWebサイトの公開ができるので便利ですよー。

2, iOS編

さて、まずiOSのアプリです。 いま作ったURLを開くだけの簡単なやつを書いてみましょう。

fnobiさんiOSは全然書いたことないのでよくわかりませんが、今回はSwiftとかいうのを使ってみます。 プロジェクト作成時のいろいろはここでは省きますが、コード弄るべきファイルはなんと1つだけです。

import UIKit

class ViewController: UIViewController, UIWebViewDelegate {
    // webview宣言
    @IBOutlet weak var mainWebView: UIWebView!
    
    // 読み込みたいURLを定数にしておく
    var URL_STRING = "http://fnobi.github.io/sugoi-webview/web/"
    
    // viewがloadされたら
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 定数にしておいたURLをリクエスト
        let url = NSURL(string:URL_STRING)
        let req = NSURLRequest(URL: url!)
        
        // webviewにリクエストなげてもらう
        mainWebView.loadRequest(req)
    }
}

やってることは全然シンプルで、コード内のコメントでほぼ全てですね。すごいじゃんSwift。

storyboard

さてさて、iOSでいまいち調べづらいのは、むしろUIを組むstoryboardとかの部分ですね。

今回は、

  • UIの中に「UIWebView」を配置する
    • 右下のリストから、「Web View」というのを見つけて、画面中央にドラッグ&ドロップ
  • 先ほどの「ViewController」が、WebViewの仕事までまるっと引き受ける(delegate)ので、それを指定してあげる
    • ↑で放り込んだ「UIWebView」から、上にちょこんと付いてるアイコンの「ViewController」へ線を結んで、「delegate」を選ぶ
  • 先ほどコード内に書いた「mainWebView」が、UI上でどれになるのか指定してあげる
    • こちらも「UIWebView」から、「ViewController」へ線を結んで、「mainWebView」を選ぶ

という3点、storyboardで作業する必要があるようです。

全画面WebViewで埋め尽くすには、AutoLayoutはこんな感じになるもよう。(marginsのところのチェックにも注意)

こうなるはず

線を結ぶ系は、ちゃんとできてればこんな感じになるもよう。(この画面から線結んでいったほうがわかりやすかった)

こうなるはず

3, Android編

さて次はAndroidです。Androidは本業で書いてるのですぐです。

今度は、レイアウト定義のファイルとメインのコード、2つ書いてあげる必要がありますが。 レイアウト定義は見てもそんなに面白くないので省略です。

(見たい人はこちら)

メインのコードはこちら。

package com.fnobi.sugoiWebView;

import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.webkit.WebView;
import android.webkit.WebViewClient;

public class MainActivity extends Activity {

    // 読み込みたいURLを定数にしておく
    private final static String INITIAL_URL = "http://fnobi.github.io/sugoi-webview/web/";
    
    // webview宣言
    private WebView mWebView;
    
    // この画面が表示されたら、
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sugoi_activity_main);
        
        // webviewを見つけてきて、
        mWebView = (WebView) findViewById(R.id.sugoi_webview);
        mWebView.post(new Runnable() {
            @Override
            public void run() {
                // webview clientというのを設定 (あとあと大事)
                WebViewClient client = new WebViewClient();
                mWebView.setWebViewClient(client);

                // あとはURLよみこむ
                mWebView.loadUrl(INITIAL_URL);
            }
        });
    }
}

iOSよりちょっと長い! しかし比べてみると、ほぼほぼ同じようなことをやってるのが分かって面白いですよ。

4, WebView → ネイティブに何かさせよう

さてこれで、ガワネイティブアプリのiOS版・Android版ができちゃいましたね。すばらしい!

しかしもちろん、これではただブラウザでページを開くのと大して変わらないので、 WebViewからネイティブアプリに、何か命令を出してみましょう。 端末を振動させたり、直接別のアプリを呼び出したり、Webだけではできない好き勝手ができますよ!

まずHTMLを少し修正して、こんな感じのリンクを入れてみましょう。

<a class="sample-button" href="myscheme://sampleaction">PUSH!</a>

この「myscheme://sampleaction」というよくわからないリンクが、 Webからネイティブアプリに命令を出す鍵になります。

流れとしては、

  • ネイティブのWebViewの方で、URLをロードする前に、そのURLを確認するようにする
  • 「myscheme://」から始まるURLだったら、ロードするのをやめて
  • 何か別のことをする -> ネイティブアプリでしかできないこともできる!

という感じです。

iOS版は、先ほどのものにこんな感じのコードを追加します。 これで、リンクを押すと端末が振動!!するようになりますよー。

// URLを読み込む前に
func webView(webView: UIWebView!, shouldStartLoadWithRequest request: NSURLRequest!, navigationType: UIWebViewNavigationType) -> Bool {

    // 読み込もうとしていたURLと、そのschemeを確認
    let url = request.URL
    let scheme = url.scheme

    // schemeが"myscheme"だったら
    if (scheme == "myscheme") {
        // 端末の振動!!
        AudioServicesPlayAlertSound(SystemSoundID(kSystemSoundID_Vibrate))

        // URLを読み込まないで終了
        return false
    }
    
    // "myscheme"じゃなかったら、放っておいて終了
    return true
}

Android版では、先ほど「WebViewClient client = new WebViewClient()」としていたところを、以下のように直します。

WebViewClient client = new WebViewClient() {

    // URLを読み込む前に
    @Override
    public boolean shouldOverrideUrlLoading(WebView view, String url) {
        // 読み込もうとしていたURLと、そのschemeを確認
        Uri uri = Uri.parse(url);
        String scheme = uri.getScheme();
 
        // schemeが"myscheme"だったら
        if (scheme.equals("myscheme")) {
            // 端末の振動!!
            Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
            long[] pattern = { 0, 1000 }; // 0秒後に、2秒の振動
            vibrator.vibrate(pattern, -1);

            // URLを読み込まないで終了
            return true;
        }

        // "myscheme"じゃなかったら、放っておいて終了
        return false;
    }
};

はい、今回もほとんど両OSで同じような雰囲気になりましたね。

余談ですが、returnしているものがAndroidとiOSで逆なので注意しましょう。概念が逆なんですね。

5, まとめ

さていかがでしたか? 「長かった!!」という苦情は受け付けます。

記事中に出したソースコードの最終形は、こちらにおいてます。 ここで出した形より、もうすこし丁寧に・業務っぽいコードになっております。

自分は実際、こういう案件があった時に、初めてiOS開発にトライしてみたので、 入門としてはなかなか楽しいんじゃないかと思います。 また、Webサイト・HTML5の活用の形としても無視できないジャンルですね!

明日のブログは、そんなHTML5の既成概念を破り続ける@ki_230さんです。お楽しみに!