iPhoneプログラミング(中編)

今回は,他の演習と同じようにヤフーの検索APIを利用したiPhoneアプリを作成してみます。検索窓と検索結果がある画面で、キーワードを入力すると検索結果が表示されます。検索結果をタップするとSafariが起動してサイトが閲覧できる簡単なアプリです。

今回のプログラムは説明と理解の難易度を下げるためにあえて、モジュール分割をせずコーディングしています。この状態で拡張していくとあるいみ破綻する可能性がありますので、開発理解を深めるためのコードと割り切って閲覧下さい。

それでは、前回の前編と同様にXcodeを起動し「新規プロジェクト」を作成します。今回は「WebSearch」というプロジェクト名にしました。自動生成されたプログラムファイルは以下のようになっていると思います。

今回この中で修正するファイルは

  • WebSearchViewController.h
  • WebSearchViewController.m
  • WebSearchViewController.xib (Interface Builderで編集)

この3ファイルのみです。まずInterface Builder(以下IB)での作業から行います。 WebSearchViewController.xibをダブルクリックしIBを起動します. Search Bar と Table View の2つを Libraryから Viewに以下のように設置します。

この状態でビルド実行してみると検索窓とテーブルが表示されます。SearchBarをクリックするとキーボードが表示され、キーワードがSearchBarに表示されますが、検索ボタンを押しても何も反応しません。

まずは Search Barの動きを確認してみましょう. XcodeワークスペースガイドのiPhoneOS2.2LibraryからUISearchBarを調べてみるとUISearchBarDelegateがあることが分かります。そのDelegateを見てみると

Sent to the delegate when the search button is tapped.
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar

というメソッドがあります。これを使えば良さそうです。さて、この関数を WebSearchViewController.m に追記します

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
	NSLog(searchBar.text);
	[searchBar resignFirstResponder];
}

これだけでは、だめです。UIのSearchBarのdelegateが関数を追加した WebSearchViewController であることを示す必要があります。IBに戻って SearchBarをControlを押しながらクリック、そのまま File'sOwner(WebSearchViewController)にドラッグしてOutletsのDelegateと繋げます。

これでデバッガコンソールでビルド実行してみてください。検索ボタンを押すとキーワードがコンソールに表示されます。ちなみに、

	[searchBar resignFirstResponder];

はキーボードを閉じる処理をしています。
さて、次にこのキーワードをもちいてウエブ検索APIからデータを取得しましょう。次のような関数を作ります。

static NSString *webSearchAPIUrl = @"http://search.yahooapis.jp/WebSearchService/V1/webSearch?appid=p3O1lFOxg66up7VWRDmVkEJnF6baGNJj38rT6gUY3DzyF.kX4_PepRsfnk1AYss1J.YG&results=20&query=";

- (void)searchByYahooAPI:(NSString *)query{
	NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@%@",webSearchAPIUrl,query]]];
	[parser setDelegate:self];
	[parser parse];
	[parser release];
}

NSXMLDocumentというXMLのDOM系クラスがAppleCoreLibraryにありますが、残念ながらiPhoneでは使えません(シミュレータではエラー無く使えてしまうので注意)。iPhoneXMLを処理するには現状SAX系のNSXMLParserを利用します。NSXMLParserのinitWithContentsOfURLを利用すると、外部リソース(URL)からXMLを簡単に取得してくれます。NSXMLParserはXML解析用の関数をdelegateで定義します。このクラス(WebSearchViewController)に書きますのでselfに指定しています。SAX関数を用意する前に、結果を保存する変数を用意しておきます
WebSearchViewController.hを以下のように変更します

@interface WebSearchViewController : UIViewController {
	IBOutlet UITableView *rTable;
@private 
	NSMutableString *tempXMLString;      //XMLの要素文字列を保存する変数
	NSMutableArray *webResult;           //検索結果全体を保存する配列(要素はDictionary)
	NSMutableDictionary *siteInfo;       //検索結果1件分の情報(各要素)を保持する
}

Table Viewの変数もついでにここで書いておきました。さらにもう少し準備しておきます。

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
	if(!webResult) {
		webResult = [[NSMutableArray alloc] init];
	} else {
		[webResult removeAllObjects];
	}
	[searchBar resignFirstResponder];
	[self searchByYahooAPI:searchBar.text];
}
- (void)dealloc {
	[webResult release];
	[super dealloc];
}

初回検索時にNSMutableArrayをメモリ上に確保します。次の検索の際は結果をクリアします。定義した検索を行う関数(searchByYahooAPIにキーワードを渡しています).
さて、SAX関数を定義します

- (void)parserDidStartDocument:(NSXMLParser *)parser {
	NSLog(@"start");
}

- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
	tempXMLString = [NSMutableString string];
	if ([elementName isEqualToString:@"Result"]) {
		siteInfo = [[NSMutableDictionary alloc] init];
	}
}

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
	if ([elementName isEqualToString:@"Title"]) {	
		[siteInfo setObject:tempXMLString forKey:elementName];
	}
	if ([elementName isEqualToString:@"ClickUrl"]) {
		[siteInfo setObject:tempXMLString forKey:elementName];		
	}
	if ([elementName isEqualToString:@"Result"]) {
		[webResult addObject:siteInfo];
		[siteInfo release];
		siteInfo = [[NSMutableDictionary alloc] init];
	}
}

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string {
	[tempXMLString appendString:string];
}

- (void)parserDidEndDocument:(NSXMLParser *)parser {
	[siteInfo release];
	NSLog(@"end");
	NSLog(@"%@", webResult);
}

NSXMLParserの5つのdelegateの関数を定義しています

  • parserDidStartDocument
  • didStartElement
  • didEndElement
  • foundCharacters
  • parserDidEndDocument

簡単に説明すると、XMLのResultタグ毎にsiteInfo変数に属性(今回はTitle,ClickUrlのみ)を保存しています。Resultの閉じタグで、1件分の検索結果が出来上がりますので、webResultの変数に保存しています。この部分の各自で色々な箇所でNSLogを用いて変数を確認すると理解が深まりますので試してみてください。
デバックコンソールで起動してみましょう。検索キーワードをいれて検索ボタンをおすとコンソールに XML解析が終わった時点でつけたデバック関数

	NSLog(@"%@", webResult);

の出力が確認できると思います。日本語の部分が内部エンコードで出力されるので読めませんが問題ありません

つぎは最後にこの出力結果をTable Viewに表示します。 Table Viewはすこし表示のさせ方がユニークで最低2つの関数を定義します

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 

上が、このテーブルには何件データが含まれているかを返却する関数です.下が、各要素(何番目の要素かは引数で与えられる)では何を表示するか (UITableViewCellを返却する).
これらを定義したクラスをTableViewのdataSorceで指定します(delegateみたいな感じに).
また、良く利用されるTable Viewのdelegate

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

です。これは何番目がクリック(タップ)されたときにどういう動作をするかを定義する関数です.
それぞれを以下のように定義します。

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
	return [webResult count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	UITableViewCell *cell;
	cell = [[UITableViewCell alloc] init];
	cell.text = [[webResult objectAtIndex: indexPath.row] objectForKey: @"Title"];
	return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
	[[UIApplication sharedApplication] openURL:[NSURL URLWithString:[[webResult objectAtIndex: indexPath.row] objectForKey: @"ClickUrl"]]];
}

今回はクリック時にはSafariが起動してURLを開くようにしました。テーブルの再描画は reloadDataをtableに対しておこないますのでXMLの解析終了時にそれを行うようにします。

- (void)parserDidEndDocument:(NSXMLParser *)parser {
	[siteInfo release];
	NSLog(@"end");
	NSLog(@"%@", webResult);
	[rTable reloadData];
}

あともう一つ作業がひつようです、SearchBarの時と同じようにIBに戻って以下の定義を行います

  • IB の TableViewとは WebSearchViewController の rTableですよ
  • IB の TableViewの dataSourceと delegateは WebSearchViewController

です。上はFile'sOwnerからTableViewへ, 下はTableViewからFile'sOwnerに向かってドラックします。

以上で完成です。ビルドして実行してみましょう

検索アプリケーションが完成しました。結果をクリックする度にSafariが起動してプログラムが終了してしまうなど正直言ってこのままでは使い物になりませんね。勉強用プログラムと割り切ってもっと使いやすいもの後編でしていきましょう。また、今回は何でもWebSearchViewControllerに追記してしまいあまり良いプログラム設計とはいえません、この辺も後編で改善していきましょう。

iPhoneプログラミング(後編)へ続きます。