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に変更することで、動画が再生されるようになった。

 

KWIC Finder 4 の最近の改良

ローカルファイルの検索ソフト KWIC Finder 4 に、ユーザからの要望に基づく改良をいくつか加えた。

  1. フレーズ検索を検索オプションに加えた
  2. エディタの切り替え

フレーズ検索

フレーズ検索とは、英単語の連語をそのまま検索する機能である。

(KWIC Finder 4も含め)大多数の検索システムでは、

personal computer

と検索すると、personal と computer を両方ふくんだ文書を検索する。これを、personal computer の出現通りに検索したい場合、フレーズ検索を使う。

KWIC Finder 4では

"personal computer"

のように前後をダブルクォーテーションで囲めばフレーズ検索ができる。だが、常時フレーズ検索を使いたい場合に、ダブルクォーテーションで囲むのは面倒ではある。

旧版のKWIC Finder 3.3 では、検索オプションに[フレーズ検索]があり、オプションを指定するとダブルクォーテーションで囲まなくてもフレーズ検索を実行できた。KWIC Finder 4にリニューアルする際に、不要と判断して省略したが、旧版のユーザから要望があったため、検索オプションの[フレーズ検索]を復活した。

エディタの切り替え

KWIC Finder は3.3までエディタにRichEditを使用していた。だがRichEditは機能が低いので、KWIC Fidner 4 ではフリーのエディタコンポーネントの Azuki Text Editorを使用している。Azuki は行番号やルーラーが表示できたり、プログラム言語の予約語をハイライト表示するなど便利な機能があるが、Azukiにすることで、旧版でできていたことの一部ができなくなった。

RichEditでは、クリップボードにコピーするときに属性もコピーされるので、検索一致個所のハイライトをそのままWordにペーストできた。ところがAzukiはテキストエディタなので、クリップボードにはテキストのみコピーされる。このためWordにペーストするとハイライトが消えてしまう。旧版のユーザから、「検索結果をもとに資料を作成するときにキーワードのハイライトをコピーしたい」という要望があった。

Azukiのソースに手を入れるのが困難なので、エディタ部品を動的にAzukiとRichEditで変更できるようにした。実装方法として、当初はFactoryMethodパターンを使えばできそうだと思っていたが、ドッキングウィンドウのクラスを継承しているためにうまくパターンにはまらず、結局 C# の dynamic 型を使えば動的にクラスを選択できることが分かった。

これでKWIC Finder 4の改良はいったんお休みにする。

次のお題

次はiOSのバージョンアップでいろいろ不都合が出始めているEBPocket for iOSに手を付けないといけない。いよいよUIWebViewからWkWebViewに切り替えないと2020年12月からAppStoreに提出ができなくなる。

あと課題として、青空文庫リーダーの読書尚友のiOS版の開発が残っている。ただiOS青空文庫リーダーも飽和状態なので、今から作っても遅いと思うが、理想の青空文庫リーダーをつくれば、多少は使ってくれる人もいるのではないか。

Microsoft Visual C++  再頒布可能パッケージのサポート期限切れへの対応

EBシリーズサポートページで配布しているWindows用のアプリケーション(EBWin4,EBStudio2,KWIC Finder4,xdoc2txt2)はMicrosoft Visual Studio で開発しているが、Visual C++ で開発したアプリを配布する場合は、「Microsoft Visual C++ 再頒布可能パッケージ」のインストールが別途必要になる。

この再配布可能パッケージはVisual Studio のバージョン毎に異なっており、それぞれ互換性がない。EBシリーズでは、利用者の混乱を避けるため、全ての製品で Visual C++ 2010 にバージョンを固定していた。

ところで、Windows本体やMicrosoft Officeにサポート期限があるように、この再頒布可能パッケージにもMicrosoftのサポート期間があり、Visual Studio 2010 再配布可能パッケージは2020年7月14日で延長サポートを終了してしまう。

forest.watch.impress.co.jp

 Visual Studio 2017(VC++ 15.0)

  • メインストリームサポートの終了日:2022年4月12日
  • 延長サポートの終了日:2027年4月13日

Visual Studio 2015(VC++ 14.0)

  • メインストリームサポートの終了日:2020年10月13日
  • 延長サポートの終了日:2025年10月14日

Visual Studio 2013(VC++ 12.0、msvcr120.dll)

  • メインストリームサポートの終了日:2019年4月9日
  • 延長サポートの終了日:2024年4月9日

Visual Studio 2012(VC++ 11.0、msvcr110.dll)

  • メインストリームサポートの終了日:2018年1月9日
  • 延長サポートの終了日:2023年1月10日

Visual Studio 2010(VC++ 10.0、msvcr100.dll)

  • メインストリームサポートの終了日:2015年7月14日
  • 延長サポートの終了日:2020年7月14日

そこでMicrosoft Visual Studio のバージョンを2017まで上げ、再配布可能パッケージのサポート期限を延ばすことにした。

なお、Visual Studio 2015以降2017,2019は再配布可能パッケージが共通化されている。Surfaceの現行モデルやWindows Server 2019にはプリインストールされているので、再配布可能パッケージを追加導入しなくてもアプリが利用できるようになるというメリットもある。

現在動作検証を行っており、6月中には全製品を「Visual Studio 2015、2017、および 2019 用 Microsoft Visual C++ 再頒布可能パッケージ」対応に変更する予定である。

 

EBPocket Pro for iOS dsl辞書サポート

EBpocket pro for iOS も、dsl辞書対応が終わり、AppStoreで無事公開できた。

f:id:hishida:20200129123039p:plain

f:id:hishida:20200129123101p:plain

ただし、iOS版のみ、dslのインデックスを作る機能がないので、母艦のEBMacまたはEBWin4で作成されるインデックス(.dsl.ebd)を、dsl辞書と一緒に転送してほしい。

EBPocket for iOSに辞書を転送して使用するには、母艦のMacかPCが必須なので、大きな制約ではないと 思う。

 

なお、今回の2.43では、非EPWING系辞書(PDIC、StarDict、MDict)のブックマーク・履歴にも対応した。これで昔年の課題が一つ解決した。

 

EBMac dsl 辞書サポート

昨年からEBシリーズのdsl形式辞書対応を進めているが、MacOS X版のEBMacも1.45.0でdsl対応ができた。

.dslまたは.dsl.dzファイルを辞書に追加すると、自動的にインデックスを作成して検索可能になる。

f:id:hishida:20200126134851p:plain

最近、利用者の方からテスト用のdsl辞書を提供していただき、画像や音声ファイルを.dsl.dz.files.zipというファイル名で圧縮する方法があることがわかった。ABBYY Lingvo の辞書フォーマットの解説にはないので、ひょっとするとGoldenDictの仕様なのかもしれない。普及しているかもしれないので、EBシリーズでも対応することにした。ただし10万件近くのファイルをzipした場合、ページの表示に若干の時間がかかる。

他にいくつか変更点がある。

1. EBMacではこれまで非EPWING系の辞書(PDIC,StarDict,MDict)でブックマークや履歴が登録できないという問題があったが、今回から登録できるようになった。

2. 前回の1.44からSandbox化を行ったが、いろいろと弊害もあるので、1.45では再びSandbox非対応とした。

Sandbox化すると、ファイルダイアログで選択したディレクトリかファイルしかアクセス権がないので、EPWINGの場合CATALOGSを選択しても、同じ階層にある辞書ディレクトリにアクセスができない。CATALOGSの一つ上のディレクトリを選択すると、選択したディレクトリ以下のファイルはアクセス許可があるので、辞書ディレクトリもアクセスできる。これでは操作が難しいので、結局Sandbox化を諦めることにした。

もともとAppStoreに登録するためにSandbox化を行ったが、AppStoreに登録しなくてもAppleからの公証を受ければ配布できることが分かったので、実害はないと思う。

 

あとはEBPocket for iOSdsl対応を行えば一連の作業を終えられる。1~2ヶ月で対応できる予定。

その後は何か新しい課題を考えることになると思う。

EBPocket for Android で dsl 辞書サポート

EBシリーズの dsl 対応が少し進み、EBPocket professional for Android 1.46.0 でアプリ単体でdslのインデクスが作れるようになった。
dsl形式の辞書を追加すると、自動的に拡張子 .dsl.ebd という名前のインデックスを作成する。
ただし、外部SDカードにdsl辞書を置いた場合には書き込みができないので、内部ストレージに辞書を置くか、EBWin4で作ったインデックスをdslと一緒に転送する必要がある。

iOSMac OS Xも、EBWin4で作ったインデックスをdslと一緒に転送すれば使用できるところまではできているが、単独でインデックスを作成できるようになってからリリースしたい。

 

EBWin4.7.4 で .dsl 暫定サポート

ABBYY Lingvoの辞書形式 .dsl .dsl.dzに対応したEBWin4.7.4を、暫定公開しました。

http://ebstudio.info/manual/EBWin4/EBWin4.html

.dslまたは.dsl.dz形式の辞書を追加すると、自動的にインデックスを作成して利用可能になります。

下記のURLで公開されている辞書で検証しています。

github.com

 

dadako.narod.ru

本格的な大規模辞書での検証が行えていないため、当面は暫定公開とします。

他のプラットフォームへの展開はこれからですが、Android 版の EBPocket Professional 1.45.3では、母艦のEBWin4で作成したインデックス( .dsl.ebd )を.dslと一緒に転送すれば、検索が可能です。