読者です 読者をやめる 読者になる 読者になる

プログラミングノート

一からものを作ることが好きなエンジニアの開発ブログです。

UIWebViewでWebとネイティブを相互連携させる方法について

iOS

特定のページのみUIWebViewを使ってWebページを表示することはよくあると思いますが、そのページでの処理終了したタイミングで、再びネイティブコードを実行したいというケースがあります。


PhoneGapではJSからネイティブコードを呼ぶ仕組みが実現されていますが、これと似たような仕組みを作るにはどうすればよいのか紹介します。

ネイティブからWeb呼び出し

JSを実行するためのメソッドがあるので任意のタイミングでそれを利用するだけでOKです。

[webView stringByEvaluatingJavaScriptFromString:@"alert('call from native');"];

簡単ですね。

Webからネイティブ呼び出し

UIWebViewDelegateにある、ページがロードされる前に呼ばれるメソッドを利用します。

- (BOOL)webView:(UIWebView *)webView
       shouldStartLoadWithRequest:(NSURLRequest *)request
       navigationType:(UIWebViewNavigationType)navigationType{
  return YES;
}

ここでYESを返すと通常通りページ遷移されるのですが、NOを返すとページ遷移をキャンセルできます。これを利用して、特定のパターンの場合のみページ遷移をキャンセルして他のメソッドを実行すればWebからネイティブコードを呼び出すことが可能になります。


まず、Webから呼び出す際のスキームを決めておきます。
今回は下記のようにし、paramsはJSONでまとめて値を渡すようにします。

app-api://method(params)


Web側からはロケーションを変更することでネイティブコードを呼び出します。

document.location = "app-api://alert({'value':'call from web'})"


上記パターンの判定ロジックを追加します。

- (BOOL)webView:(UIWebView *)webView
     shouldStartLoadWithRequest:(NSURLRequest *)request
     navigationType:(UIWebViewNavigationType)navigationType{
    
  NSString *requestString = [[request URL] absoluteString];
  if ([requestString rangeOfString:@"app-api://"].location == NSNotFound){
    return YES;
  }

  NSError *error = nil;
  NSString *pattern = [NSString stringWithFormat:@"app-api://(.+)\\((.+)\\)"];
  NSRegularExpression *reg = [NSRegularExpression regularExpressionWithPattern:pattern
                                     options:0
                                       error:&error];
  if(error != nil){
    NSLog(@"%@", [error description]);
    return NO;
  }
 
  NSTextCheckingResult *match = [reg firstMatchInString:requestString
                          options:0
                          range:NSMakeRange(0, requestString.length)];
  if(match.numberOfRanges == 0){
    NSLog(@"not match");
    return NO;
  }
   
  NSString *method = [requestString substringWithRange:[match rangeAtIndex:1]];
  NSString *params = [requestString substringWithRange:[match rangeAtIndex:2]];
  NSLog(@"%@, %@", method, params);

  // 呼ばれたメソッドを実行
  if([method isEqualToString:@"alert"]){
    [self alert:params];
  } 
  return NO;
}


受け取ったパラメータはSBJsonなどのパーサーを利用して、ディクショナリーで取得できます。

SBJsonParser *parser = [[SBJsonParser alloc] init];
NSDictionary *object = [parser objectWithString:params error:nil];
[parser release];


ここまでくれば後は対応したいAPIのロジックを追加して行くだけですね。
スキームも自由に変更可能なので、解析しやすい形で実装するのもよいかと思います。