しばらくiOSから離れていてSwiftはほとんど触ったことないのでとりあえずObjective-Cでやりました。このあとSwiftで実装し直そうと思ってるけれど、一旦エントリにします。
いつだったかDHHが既存のWebサイトを活かしてアプリ作るよーみたいなことを言ってたのを覚えていたので、今回は「いや、実際どうやってやるんだろうな」というのを考えてみたわけです。
何をやりたいか
こういうページ遷移がある普通のWebサイトがあるとして、
このページをiOSのUIWebViewで表示するんだけど、リンクをタップしたときに新しいViewControllerを作ってNavigationで遷移するということをやってみます。普通にやるとリンクをタップしたら同一WebView内画面遷移するところを、あたかもiOSのネイティブのように動かしてみようということね。
結論からいうとそれは可能です。↓のアニGIFを見てもらえばリンクを押したときにナビゲーションで次のビューがスタックしてくるのがわかると思います。
こうすることでWebにコンテンツを自由にデプロイしつつ、ユーザー体験はネイティブに近づけられるんじゃないかと。
こんなことはすでに昔からみんながやってたりしたかもしれないけど、最近は端末も速くなっているし、個人的にはWebView+ナビゲーションってあまり見たことがない気がするのでその部分の実験ということで見てもらえると良いです。
Webページの準備
まずはHTML側のソースです。ここで見るべきは、<a>
タグについてるcallback-to-ios
クラスぐらい。
<!DOCTYPE html>
<html>
<body>
<p>1ページ目</p>
<a href="/secondpage.html" class="callback-to-ios">2ページ目ヘ</a>
</body>
</html>
続いてJavascriptです。こっちが肝ね。
$ ->
reportBackToIOS = (href)->
iframe = $('<iframe />').attr('src', "callback://" + href)
$('body').append(iframe)
iframe.remove()
iframe = null
$('.callback-to-ios').click ->
reportBackToIOS($(@).attr("href"))
false
まずリンクのクリック時にreportBackToIOS
を呼び出した上で、falseを返してリンクを辿らないようにしてあります。
reportBackToIOS
は名前の通りiOSにリンクがタップされたことを知らせる役割を担っています。iOSのUIWebViewではWebView内でURLの読み込みが発生する直前にコールバックを通るんですが、ここではそれを利用しています。
具体的には、iOSに渡したい情報をURL(の一部)として持ったiframe
を作って一旦HTMLのbodyに追加し、すぐに削除していますね。iframe
がbodyに追加されたとき、src
アトリビュートで持っているURL(ここでは、‘callback://’ + 遷移先のURL)をiframe内で開こうとするため、WebViewのコールバックが呼ばれることになるわけです。
iOS側の対応
ViewController
に適当にUIWebViewを置いてあり、ViewController
→ViewController
の遷移をするSegueがnext
という名前で定義されているとします(実際にはViewControllerからViewControllerへのSegueは定義できないので、隠しボタンからViewControllerへのSegueです。)。
#define HOST @"http://localhost:4000"
- (void)viewDidLoad {
[super viewDidLoad];
self.webView.delegate = self;
NSString* urlstring = NULL;
NSString* path = self.segueOptions.stringValue;
if(path) {
urlstring = [NSString stringWithFormat:@"%@%@", HOST, path];
} else {
urlstring = [NSString stringWithFormat:@"%@%@", HOST, @"/"];
}
NSURL* url = [NSURL URLWithString:urlstring];
NSURLRequest* urlRequest = [NSURLRequest requestWithURL:url];
[self.webView loadRequest:urlRequest];
}
肝心のコールバックはこんな感じで実装しておきます。
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
if ([[[request URL] scheme] isEqualToString:@"callback"]) {
NSString* href = [[[request URL] absoluteString] substringFromIndex:11];
[self performSegueWithIdentifier:@"next" options:href];
return NO;
}
return YES;
}
shouldStartLoadWithRequest
は新しいリクエストが来た時に通るコールバックで、何もしないときはYES
を返すことで読み込みをスタートさせることができるわけですが、今回はcallback://
で始まるURLの場合だけはnext
という名前のSegueを実行してNO
を返すことにします。NOを返せばURLの読み込みは行われないので、callback://****
みたいなURLを処理しちゃってエラー、ということも起こりません。
これで最初に紹介したGIFアニメのようなページ遷移ができるようになりました。便利なような、使いどころ難しいようなそんな感じですが、ネイティブっぽく動かすという目的はなんとなく果たせていますね。
参考リンク
reportBackToIOS
のところはStackOverflowのこの質問を参考にしました。
ios - How can I reliably detect a link click in UIWebView? - Stack Overflow