武之新
UIWebViewでリンク長押し時のActionSheet

UIWebViewはデフォルトで、リンクを長押ししてるとActionSheetが出てきます。選択肢がOpenとCopyしかないので、実質あまり意味がなかったりしますが。
なのでこのActionSheetのボタンをアプリでカスタマイズしたい諸兄も多いことでしょう。

そんなときのTips。

続きを読む…

UIWebViewでcanGoBackが効かない

UIWebViewではページの読込ませ方によってcanGoBack, canGoForwardの挙動が変わるようです。

  • canGoBackがYESになる
    loadRequest: 
  • canGoBackがNOのまま
    loadData:MIMEType:textEncodingName:baseURL:
    loadHTMLString:baseURL:

これはcanGoBackだけでなく、実際にgoBackしても動作しなくなります。historyがきちんと更新されるのはloadRequest:だけのようです。

Javascriptでhistoryがいじれるといいのですが、試しにやってみたところ期待通りにはいきませんでした。できそうなものなんですけどね、、

UIWebViewでフォントサイズをいじる

相変わらずUIWebViewと格闘中です。

AppStoreにあるブラウザアプリで、フォントサイズが変更できるものがあったので、真似してみることに。

やり方は簡単、次のようにやるだけ。

script = @"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust='120%';";

[webView stringByEvaluatingJavaScriptFromString:script];

jQueryなどでスタイルのfont-sizeをいじっても、HTML上のスタイル定義のされ方によって適用されたりされなかったり安定しません。webkitTextSizeAdjustを使えばそういうときでもイチコロです。

元ネタはこちら。
http://stackoverflow.com/questions/589177/how-to-increase-font-size-in-uiwebview 

続:NSURLCacheでGoogleのトップページをキャッシュすると落ちる件

結局解決できていません、本件。

あれからさらにいろいろ試して、見えてきたのは[NSURLRequest cachePolicy]。

そもそもは、デフォルトのNSURLCache実装がstoreCachedResponseでデータを渡されていてもcachedResponseForRequestで返すものと返さないものが存在している、というところでした。

なぜか?
データは違えども、NSCachedURLResponse、NSURLResponseの状態は概ね同じ。違うのは[NSURLRequest cachePolicy]。デフォルトのNSURLCacheはNSURLRequestReturnCacheDataElseLoad、NSURLRequestReturnCacheDataDontLoadの場合はキャッシュを返すけど、NSURLRequestUseProtocolCachePolicyの場合は返さないようです。

相変わらず理由はわかりませんが、真似してみることに。

ふむ、落ちなくなった

前回も落ちなくなった後、他の箇所をいじっていたら再発したので安心できませんが、とりあえず改善。どうやらものすごく微妙なバランスで動いているようです、NSURLCache。 

ただ、割といろんなリソースがNSURLRequestUseProtocolCachePolicyでリクエストされているので、NSURLRequestUseProtocolCachePolicyだとキャッシュを返しちゃだめとなるとローカル保存からWebページを再現するのは難しいのかもしれません。。

NSURLCacheでGoogleのトップページをキャッシュすると落ちる件

原因が特定できなくて困っていた、Googleのトップページでだけ発生する落ちバグがあったわけですが、なんとなく原因が特定できたくさい。
ちなみにこれは次のような現象。

  • Googleのトップページ(http://www.google.co.jp/)の内容を保存しておき、通信しないでUIWebViewで表示しようとすると落ちる。
  • キャッシュの仕方はNSURLCacheでやろうと、別の方法を使っても発生する現象は同じ。
  • 落ちるのはHTTPProtocol::createStreamのあたり。起きているのはEXC_BAD_ACCESS

あちこちのサイトをキャッシュしてみても、上記現象が起きるのはなぜかGoogleだけ。Googleとは言っても、ニュース(http://news.google.co.jp/)では大丈夫だったりします。

さらに、EXC_BAD_ACCESSは通常過剰releaseによって解放済みのメモリにアクセスすると起きるものですが、いくらretainかましても改善しませんでした。過剰releaseならGoogle以外でも発生するだろうし、ランダム要素があるなら http://www.google.co.jp/ で100%発生するというのはおかしいので、やはりGoogleさんのサイト構造に起因するものとして調べてみました。

とはいってもソースもないライブラリの中で起きてる例外なので調べようもなく、できることといえばファイルの構成をよくよくみてみるくらい。じーっ、とよく見てみると、試してみたサイトの中で、唯一 http://www.google.co.jp/ だけHTML5のmanifestファイルが含まれていました!

試しにmanifest指定を削ってみます。 

落ちない! これか!

理屈はわかりませんが、とりあえずmanifest指定を削ってしまうとよいようです。

(2/21追記)

いろいろいじってるうちにまた落ちるようになりました。違うらしい。

[iPhone]サイトをローカル保存するための格闘

iPhoneを使っていて、最もイライラするのは通信状況。電波の受信感度はバッチリでも通信できないということが珍しくありません。
iPhoneのせい、というよりは、Softbankの電波のせいなわけですが。

iPhoneのSafariは(というか普通のブラウザはみな)一回みたページに戻る場合でも 最新かどうかを判断するため通信しようとするので、通信状況が不安定だとブラウジングがものすごくイライラします。

そこで一度みたページをすべてキャッシュし、キャッシュがあれば最新チェックもなにもかもすっ飛ばして表示してくれるアプリを作ろうとしています。が、これがなかなかに難儀しています。

いままでチャレンジしてみたこととしては、

(1) webView:shouldStartLoadWithRequest:navigationType: で保存すべきURLがわかるか?

  1. ドキュメントのURLはこれでわかるけど、画像やCSSなどのリソースについては呼び出されないのでわからない。さらにJavascriptでXHRとかしてるとそれは呼び出されてしまい、なにがページでなにがXHRかわからない。

(2) いっそのこと webView:shouldStartLoadWithRequest:navigationType: に飛んで来たURLからHTMLを取得し、中身をparseして画像やCSSなどのリンクをたどればできるんじゃないか?

  1. HTML、CSSのparseはできた。
  2. GoogleやYahooなどではCSSをJavascriptで後から追加するので、HTMLだけparseしても正しく表示できない。
  3. Javascriptのparseはさすがにちょっと無理。

(3) uiWebView:resource:willSendRequest:redirectResponse:fromDataSource: でなら画像やCSSも含めて呼び出されているっぽいからいけるんじゃない?

  1. 概ねこれで保存対象がわかるようではあるが、一部のリソースは呼ばれない模様。NSURLCacheのcachedResponse:forRequest: をチェックしているとwillSendRequestに飛んで来てないURLがcachedResponse:forRequest: されていたりしている。
  2. キャッシュしているリソースであればrequestのURLをfile://に書き換えたいが、それをすると落ちる。特にGoogleのトップページとか。
  3. 2が回避不能なのでこれだけでは解決できず。

(4) NSURLCacheでどうよ?

  1. キャッシュを返しても落ちない!キャッシュを返すのはこれでイケる。
  2. UIWebViewWebViewDelegateよりも呼ばれる数が多いので、こちらが呼ばれたものが本当に通信しにいっているリソースだと思われる。なので、これで保存対象がわかる。 
  3. storeCachedResponse:forRequest: はredirectされていても呼ばれてしまう模様。NSCachedURLResponseだけではredirectされたものかどうかの判断ができそうにないので、保存は無理。

という流れ。
結論としては、(4)でなんとかなりそう。
ただしstoreCachedResponse:forRequest: はredirectの絡みで使えないため、

  • cachedResponse:forRequest: で保存対象を特定
  • 自力で保存
  • 自力保存したリソースが要求されたらそれをcachedResponse:forRequest: で返す

という実装になりそうである。なかなか大変。 

UIWebViewでUser-Agentを変更する

UIWebViewでサイトをみると、User-AgentがSafariと違うため、一部のサイトでは iPhoneであると認識できずにPCサイトに連れていかれてしまいます。(Yahooとか)

ちょっと前までは

- (BOOL)webView:(UIWebView *)webView 
shouldStartLoadWithRequest:(NSURLRequest *)request 
 navigationType:(UIWebViewNavigationType)navigationType
{
	NSMutableURLRequest *req = (NSMutableURLRequest*)request;
	[req setValue:USER_AGENT_STRING forHTTPHeaderField:@"User_Agent"];
	return YES;
}	

でよかったようですが、iOS4.2でやってみると期待通りには動いてくれませんでした。 色々やってみたところ、

-(NSURLRequest*) uiWebView:(id)webView 
				  resource:(id)identifier 
		   willSendRequest:(NSURLRequest *)request 
		  redirectResponse:(NSURLResponse *)redirectResponse 
			fromDataSource:(id)dataSource
{
	NSMutableURLRequest *req = (NSMutableURLRequest*)request;
	[req setValue:USER_AGENT_STRING forHTTPHeaderField:@"User-Agent"];

	return req;
}

とやると期待通りになってくれるようです。

これはUIWebViewWebViewDelegateというprotocolで、WebViewDelegateと同じ内容を 備えている非公開protocolのようです。非公開なのでこれを使って審査が通るかどうかは 未知数ですが、UIWebViewWebViewDelegateを使うと色々便利なことが出来るので、 いま作ってるアプリではバリバリ使ってみてます。 審査が通るといいなぁ。

UIWebViewWebViewDelegateは非公開なので

#import <UIKit/UIKit.h>

とやっても宣言に含まれてきません。 なので@interfaceは普通に

@interface HogeViewController : UIViewController<UIWebViewDelegate>

とだけやっておいて、.m で知らん顔して実装してしまえばUIWebViewが呼び出してくれます。=D もちろんUIWebViewのdelegateにHogeViewControllerを設定するのは必要です。