ガワネイティブアプリをつくろう!
こんばんは。カヤックの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サイトの公開ができるので便利ですよー。
- 公開したもの : http://fnobi.github.io/sugoi-webview/web
- 対応ブランチ : https://github.com/fnobi/sugoi-webview/tree/gh-pages
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さんです。お楽しみに!