こんにちは。
Sleipnir Mobile for iPhone / iPad 開発担当の宮本です。
調べてみると、Sleipnir Mobile の最初のコミットは 2010年9月6日で、もう3年近く開発しているようです。最初はこんな貧弱な API でブラウザなんて作れるわけ無いと思っていましたが、なんとかなるものですね。今では多くのユーザーさんに使っていただけるプロダクトになりました。
今回は、この3年間で、使ってきた UIWebView の技を紹介したいと思います。詳細な方法については書きませんが、UIWebView で何ができて何ができないのかはわかるかと思います。それぞれの詳細なやり方については機会があれば書いていきたいです。
目次
長くなりそうなので、目次を用意しました。
余裕があればアップデートするので、ブックマークでもしておいてください。
UIWebView の API でできること
- 戻る/進む
- リロード/中止
- HTMLをロードする
- JavaScript を実行する (これがあるので、なんでもできる!)
- ロードの開始前/開始/失敗/完了をフック
頑張ったらできること
- JavaScript のコードを埋め込む
- JavaScript から Objective-C にメッセージを送る
- サムネイルを作る
- タイトルを取得する
- ユーザーエージェントを変更する
- 新規タブリンクをハンドルする
- スクロールイベントをハンドルする
- タッチイベントをハンドルする
- リンク長押しをハンドルする
- Basic 認証に対応する
- パスワードの保存
- リソースフィルター
- プログレスを取得する
- 開いている画像、PDF を保存する
- ページ内検索
実現が難しいこと
頑張ったらできること
JavaScript のコードを埋め込む
基本です。貧弱な API は JavaScript でカバーするしかありません。
js ファイルをリソースで用意しておいて、ロード完了時に埋め込みます。
JavaScript から Objective-C にメッセージを送る
これができれば、色々応用できますね。
例えば、JavaScript側で
location.href=”sleipnir://send?text=hello” を実行します。
webView:shouldStartLoadWithRequest:navigationType:
で request.URL.scheme が ‘sleipnir’ なら、URL の scheme 以外の値に応じて何か処理して、NO を返す。
サムネイルを作る
UIWebView も UIView なので layer を renderInContext: できます。
問題は、タイミングです。ロード完了時にサムネイルを作りたい場合、webView:didFinishLoad: がきたからといって、レンダリングが終わったわけではありません。少し待ってから renderInContext: すると上手くいきます。
タイトルを取得する
[webView stringByEvaluatingJavaScriptFromString:@”document.title”];
カテゴリでも書いておきましょう。
ユーザーエージェントを変更する
iOS 4 と iOS 5, 6 で方法が違います。今後のバージョンでも変わるかもしれません。
- iOS 4
NSURLRequest の valueForHTTPHeaderField: を swizzle して違う値を返す。 - iOS 5, 6
NSUserDefaults の registerDefaults で “UserAgent” をキーに、値を入れておけば勝手に使ってくれる。
新規タブリンクをハンドルする
- JavaScript で最後に触った Element を保持できるようにしておく
- webContentView:shouldStartLoadWithRequest:navigationType:
のタイミングでその Element の target の値をチェックします。
スクロールイベントをハンドルする
スクロールでナビゲーションバーを一緒に動かすようなものを書く場合、必要です。
UIScrollViewDelegate のメソッドを swizzle してます。
タッチイベントをハンドルする
UIWindow のサブクラスを書いて、sendEvent: で全イベントをフックする。Sleipnir Mobile ではここで Notification をあげてます。
Sleipnir Mobile の L 閉じなどのジェスチャはこれでできます。タブ切り替えはもう少しややこしいです。
リンク長押しをハンドルする
- document.body.style.webkitTouchCallout=’none’ を実行して、長押しアクションシートを止める
- 上で取得できるタッチイベントの位置にあるリンク を JavaScirpt で取得する。
- アクションシートを出すなり、Hold And Go のように背面でリンクを開くなり好きにする。
少し古い記事ですが Sleipnir Mobile では下記の方法でリンク検出精度をあげてます。
https://blog.fenrir-inc.com/jp/2011/06/sleipnir_mobile_13_hold_and_go.html
Basic 認証に対応する
NSURLProtocol のサブクラスを書いて、registerClass する。
startLoading と stopLoading をオーバーライドして、自分で NSURLConnection 作るので、connection:didReceiveAuthenticationChallenge: をフックできる。
パスワードの保存
- 標準の delegate で POST はフックできるので、そこで JavaScript で入力値を取得
- ロード完了時に JavaScript で保存した値を入力
リソースフィルター
NSURLCache の setSharedURLCache でカスタムな NSURLCache を設定すれば、すべてのリソースロードをフックできるので、特定のドメインなどの条件で空データを返せばフィルタリングできます。
プログレスを取得する
WebKit にあるプログレスは取得できませんが、NJKWebViewProgress で十分そうですね。素晴らしい!
https://github.com/ninjinkun/NJKWebViewProgress
開いている画像、PDF を保存する
UIWebView で表示しているということはダウンロードできているということなのですが、データを取り出す方法がありません。保存する場合は、再度ダウンロードします。
ページ内検索
こっちに書きました。(2013/11/15)
【iOS】JavaScript を使って、UIWebView でページ内検索
頑張ってもできないこと
今のところ簡単なやり方がわかってないものです。何かと技はあるので、まだまだできるものがあるかもしれません。
Web ページを PDF にする
Macなら簡単にできるのに、できません。
証明書を取得する
かなり頑張ればできそうな気もするのですが、今のところできてません。Mac でも大変です。
戻る進む履歴を取得する
UIWebView からは取得できません。自前で戻る進む履歴を管理すればなんとかなるかもしれません。
UIWebView の property を監視
なぜかできません。Mac の WebView は監視できます。
文字コードの自動判別
Web ページで文字コードを正しく設定してないと化けることがあります。
UIWebView の loadData:MIMEType:textEncodingName:baseURL:
でエンコードを指定して NSData をロードすることはできます。
UIWebView で頑張るブラウザSleipnir Mobile for iPhone / iPad はこちら