hishidaの開発blog

EBシリーズ(EBPocket,EBWin,EBMac,EBStudio),KWIC Finder,xdoc2txt,読書尚友の開発者ブログ

EBPocket for iOS のWKWebView対応について

iOSではウェブブラウザ用のUIKitとして長らくUIWebViewが提供されてきたが、セキュリティ等の問題があり、iOS8からWKWebViewが提供されるようになった。UIWebViewは現在ではdeprecated (非推奨)となっており、2020年4月からUIWebViewを使用したアプリのAppStoreへの新規の申請ができなくなっている。また、2020年12月末からはUIWebViewを使用したアプリのアップデートの申請もできなくなる。

developer.apple.com

このため、UIWebViewを使用したアプリは、WKWebViewへの置き換えが必須になっている。
EBPocketもUIWebViewを使用していたために、今回、思い切ってWKWebViewへの移行作業を行った。私の経験もどなたかの参考になるかもしれないので、作業記録を書き残しておきたい。

基本的な手順としては、次のようになる。

 

1) Build Phases->Link Binary With Librariesに、 WebKit.framework を追加する

iOS13以降では、明示的にWebKit.frameworkを加えていないと実行時にクラッシュする。

 2) UIWebViewのインスタンスを全てWKWebViewに変更する

ここで問題点が2つ生じた。

  • ストーリーボード上でWKWebViewを使用すると、targetOSの下限が11になる

古いXcodeでは、ストーリーボードでWKWebViewが使用できないという問題があったが、Xcode11から使えるようになった。ストーリーボードでUIWebViewを使用していた場合は、Xcode11以降であれば、ストーリーボード上でWKWebViewに変更できる。ただし、xibの"Builds for"を "iOS 11.0 and Later"にする必要がある。

実はこの方法には副作用があり、ストーリーボードからWKWebViewを初期化する場合、iOS10以下だとクラッシュする。このため、プロジェクトのTargetOSの下限も11以上にする必要がある。

TargetOSをどうしても11未満にしたい場合は、ストーリーボードを使わずに、プログラムコードでWKWebViewを初期化する必要がある。
EBPocketでは、Web検索の画面がこれに相当する。TargetOSを8にしたかったので、ストーリーボードを使わずにコードで初期化するようにした。

  • WKWebViewを継承したカスタムクラスをストーリーボードから初期化した場合に、クラッシュする。

WKWebViewの初期化にはWKWebViewConfigurationを引数に使用する必要がある。ストーリーボードからWKWebViewを初期化した場合はデフォルトのWKWebViewConfigurationが作成されるが、WKWebViewのカスタムクラスの場合は、WKWebViewConfigurationが nil になり、実行時にクラッシュしてしまう。
EBPocketでは、辞書の本文を表示する画面がこれに相当し、もともとUIWebViewのカスタムクラスだった。これを単純にWKWebViewに置き換えただけだと、見事にクラッシュする。
この問題にはかなり悩んだが、結局、次のコードをWKWebViewのカスタムクラスに追加することでうまくいった。ストーリーボードからの初期化の場合、initWithCoder:が実行されるので、その中でWKWebViewConfigurationを作成すればよい。

- (instancetype)initWithCoder:(NSCoder *)aDecoder
{
    WKWebViewConfiguration *_configuration = [[WKWebViewConfiguration alloc] init];
    CGRect frame = [[UIScreen mainScreen] bounds];
    self = [super initWithFrame:frame configuration:_configuration];
    self.translatesAutoresizingMaskIntoConstraints = NO;
    return self;
}

3) delegate関数をWKWebViewの関数に置き換える

UIWebViewのデリゲートのUIWebViewDelegateは、WKWebViewではWKNavigationDelegate,WKUIDelegateの二つに分かれている。

UIWebView:
webView.delegate = self

WKWebView:
webView.navigationDelegate = self
webView.uiDelegate = self

UIWebViewのデリゲート関数を、適宜、WKWebViewのデリゲート関数に置き換える。

UIWebView:
- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
 navigationType:(UIWebViewNavigationType)navigationType

WKWebView:
 - (void)webView:(WKWebView *)webView
 decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
 decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler

UIWebView:
- (void)webViewDidFinishLoad:(UIWebView *)webView

WKWebView:
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation

4) javaScriptの実行がUIWebViewでは同期処理(処理が終わるまで帰ってこない)だが、WKWebViewでは非同期処理に変わる(実行後にコールバックされる) 

UIWebView:
    [webView stringByEvaluatingJavaScriptFromString:script];

WKWebView:
    [webView evaluateJavaScript: script
          completionHandler:^(NSString *result, NSError *error){
	// JavaScript実行後の処理を書く
    }];

5) scalesPageToFitプロパティ廃止への対応

 UIWebViewにあったscalesPageToFitプロパティが廃止されており、画面サイズが変更されたときにレイアウトを変更してくれない。結局、次のように親ビューに制約を加えるとうまくいった。

   [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.webView attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual toItem:self.bottomLayoutGuide attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.webView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.topLayoutGuide attribute:NSLayoutAttributeBottom multiplier:1.0 constant:0]];
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.webView attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:0]];
    [self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.webView attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:0]];

 以上でWKWebViewへの移行がすんだが、テスト不足のため、WKWebView対応版のEBPocket for iOSのリリースは延期することにした。現在AppStoreにある1.46.2は、まだUIWebViewを使用しており、iOS13以後に発覚したbugの修正のみを行ったバージョンである。次にリリースするタイミングからWKWebView版に切り替わる予定である。(2020/11/19)

番外:iOS13以降、動画再生ができない問題への対処

iOS13以降、広辞苑等の動画の再生ができなくなったという問題を修正した。
EBPocketでは動画の再生にMPMoviePlayerViewControllerを使用していた。このAPIもiOS10からDeprecatedになり、代わりにiOS8から登場したAVPlayerViewControllerを使用することが推奨されている。iOS13からは実機からもMPMoviePlayerViewControllerが削除されたため、動画が再生できなくなった。AVPlayerViewControllerに変更することで、動画が再生されるようになった。