HTTPステータスを判定して UIWebView を生成する

UIWebView はかんたんに HTTP 通信して Web ページを表示することができて便利なのだけれども、HTTP ステータスコードを見ていないため、iPhone 上に "500 Internal Server Error" とか出てくるわけです。これはカッコ悪い。

例えば、次のように UIWebView の loadRequest: を使った場合、通信エラー処理は webView:didFailLoadWithError: に委譲されるものの、HTTPサーバのエラー処理は委譲されず、処理するタイミングがありません。

- (void)viewDidLoad {
    UIWebView *wv = [[UIWebView alloc] initWithFrame: view.frame];
    wv.delegate = self;
    wv.scalesPageToFit = NO;
    [self.view addSubview:wv];
    
    [wv loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.deftrash.com/"]]];
    [wv release];
    
    [super viewDidLoad];
}

#pragma mark UIWebViewDelegate - (void)webView:(UIWebView *)wv didFailLoadWithError:(NSError *)error { NSLog(@"UIWebView::didFailLoadWithError"); }

仕方ないので、 NSURLConnection の delegate で制御します。 _conn と _data はそれぞれ、NSURLConnection と NSMutableData のインスタンス変数です。

- (void)viewDidLoad {
    NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.deftrash.com/"]];
    _conn = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:YES];
    if (_conn) {
        _data = [[NSMutableData data] retain];
    }
}

-(void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { NSInteger status = [(NSHTTPURLResponse *)response statusCode]; if (status != 200) { [_conn cancel]; // 本当はちゃんと NSError オブジェクトを作って渡すべき [self connection:connection didFailWithError:nil]; } else { [_data setLength:0]; } } -(void) connection:(NSURLConnection *) connection didReceiveData:(NSData *) data { [_data appendData:data]; } -(void) connection:(NSURLConnection *) connection didFailWithError:(NSError *) error { [_conn release]; _conn = nil; [_data release]; } -(void) connectionDidFinishLoading:(NSURLConnection *) connection { UIWebView *wv = [[UIWebView alloc] initWithFrame: self.view]; wv.scalesPageToFit = NO; [wv loadData:_data MIMEType:@"text/html" textEncodingName:nil baseURL:nil]; [_conn release]; _conn = nil; [_data release]; }

NSURLConnection の initWithRequest:delegate: を使うと TCP 通信のプロセスを委譲できるので、これを利用しています。なぜか delegate のプロトコルはありません。connection:didReceiveResponse: で最初のレスポンスを受けた段階で、ステータス判定して、正常(200) でなければ通信をキャンセルしています。 本来は switch 構文で、もっと細かくステータスごとの処理をするべきでしょう。せめて、400 や 500 番台だったらエラーというぐらいにはすべきでしょうね。

このあたりのコントロールは、実際にやってみると非常に面倒です。こうした非同期通信を複数スレッド走らせる場合は、きちんとキューイングしたり、インスタンスの使い回しをしないようにしたり、気にかけるポイントが多かったです。いやあ、途中でメモリの開放漏れなどで、10回くらいクラッシュしました。

こうして考えると ASIHTTPRequest は超絶便利です。ちょっとした HTTP 通信をするにはライブラリとして大きくなりすぎているのが気になっているものの、BSD ライセンスですし、選択肢として取れるのであれば積極的に使いたいものですね。

おつかれさまでした。

ローカルの html を UIWebView で表示する

iPhone アプリ上で使用許諾文書のような長文を表示させたいとき、View にベタ書きという選択はありませんが、かと言ってわざわざ Web と通信するまでもありません。ということで、プロジェクトに配置しておいた静的 html を読んで表示してあげるテスト。

Xcode を開いて Resources フォルダに、 html ファイルを置いておきます。今回は policy.html というのを置いてみました。あとは、それを表示する ViewController で以下のようにするだけでできました。

- (void)viewDidLoad {
    CGRect frame = [[UIScreen mainScreen] applicationFrame];
    CGRect rect = CGRectMake(0,0,frame.size.width, frame.size.height);
    UIWebView *wv = [[UIWebView alloc] initWithFrame:rect];
    wv.scalesPageToFit = YES;
    [self.view addSubview:wv];
	
    NSString *path = [[NSBundle mainBundle] pathForResource:@"policy" ofType:@"html"];
    [wv loadRequest:[NSURLRequest requestWithURL:[NSURL fileURLWithPath:path]]];
    [wv release];
    
    [super viewDidLoad];
}

よくよく考えてみれば、これで html5 + javascript を読み込んでしまえば、すぐに簡易 iPhone アプリが作れるんですね。その割には、Xcode の新規プロジェクトのなかには "HTML-based Application" という選択肢がないのが、なんとも考えさせられます。


最新エントリー
HTTPステータスを判定して UIWebView を生成する
ローカルの html を UIWebView で表示する
あわせて読みたいブログパーツ