Attention! Translated article might be found on my English blog.

2015年5月9日土曜日

Objective-CからJavaScriptに複雑な文字列を渡したかった

結論から言うと、UTF8でURLエンコードとBase64エンコードを重ねて対処しました。

objc側:

- (void)updateWithHTML:(NSString *)html {
    html = [html stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    
    NSData *data = [html dataUsingEncoding:NSUTF8StringEncoding
                      allowLossyConversion:NO];
    
    NSString *base64String = [data base64EncodedStringWithOptions:0];
    
    NSString *jsString = [NSString stringWithFormat:@"updateWithHTML('%@');", base64String];
    
    NSString *result = [self.webView stringByEvaluatingJavaScriptFromString:jsString];
    
    NSLog(@"result:%@", result);
}
JS側:
function updateWithHTML(base64html) {
    var html = decodeURIComponent(window.atob(base64html));
    
    // do some stuff
    
    return 'OK';
}


Base64エンコードを使う理由:
文字列に含まれる括弧やシングルクォートのせいか、JSを実行できていないようでした。
(objcコード中の引数htmlは特定ページを表現するための完全なHTML文字列で、JSコードも含んでいます)
JSON化も試してみたのですが、いまいち期待通り動かなかったためBase64化して渡すことにしました。
うまく使えばJSONでも解決できそうなのですが…必要があればまた検討します。

URLエンコードを使う理由: 
URLエンコード無しの場合、JS側でBase64デコード後の日本語が文字化けしてしまいました。
window.btoa - Web API インターフェイス | MDNによると、Unicodeに対してatob()を使うと例外が出るようでした。
私の書いたJSコードをSafariで動かしてみても特に例外は出ていないようでしたが、
上記サイトの通りURLエンコードも行うようにしたところ、期待通り日本語文字列を取得できるようになりました。

なお、ios - How do I URL encode a string - Stack Overflowによると、[NSString stringByAddingPercentEscapesUsingEncoding:]は不完全らしいので、さらに改善が必要かもしれません。