#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サイトの公開ができるので便利ですよー。
- 公開したもの : 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さんです。お楽しみに!