Hatena::ブログ(Diary)

ウエブアプリケーション開発入門講座

  1. 開発環境の構築
  2. Webの基本
  3. Webサービス (WebAPI)
  4. CGIの基本 プログラム演習1
  5. ウエブAPIを用いた初めてのCGI (準備編) プログラム演習2
  6. ウエブAPIを用いた初めてのCGI (perl編) プログラム演習3
  7. ウエブAPIを用いた初めてのCGI (PHP編) プログラム演習4
  8. オブジェクト指向とMVC
  9. テンプレートエンジン (PHP Smarty) プログラム演習5
  10. 拡張性の高いプログラムを作る プログラム演習6
  11. ウエブセキュリティ
  12. JavaScript
  13. Ajaxを用いたウエブページ プログラム演習7
  14. 地図APIを使ったアプリケーション プログラム演習8
  15. DataBase(MySQL)
  16. MySQLを利用した掲示板 プログラム演習9
  17. 携帯(モバイル)への対応
  18. flash - ActionScript (前編)
  19. flash - ActionScript (後編)
  20. iPhoneプログラミング(前編)
  21. iPhoneプログラミング(中編)
  22. iPhoneプログラミング(後編)
ソースコードはこちらにもあります http://trac.assembla.com/cX0noKTPir3A7Oab7jnrAJ/browser

2008-12-29

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

今回は、本格的なiPhoneアプリを作成してみましょう。基本は(中編)で作成した検索を行うアプリケーションですが、後編は次のことを意識して開発します

作成するアプリケーションは今回はウエブ検索ではなくヤフー知恵袋サービスが提供する知恵袋API(質問検索)で、質問が検索できるものを作成します。検索結果をクリックすると、iPhoneらしい画面遷移でWebページ(Q&A画面)が表示されるようにします。中編ではSafariが起動してアプリが終了してしまいましたが、今回はアプリを終了することなく結果が表示されます。

f:id:hdtooo:20081229115637p:image

今回はある程度、規模の大きいアプリケーションとなり、作成手順を追って説明するのが難しいのでポイントとなる箇所を各々説明する形をとります。Xcodeで新規プロジェクトを作成しQaSearchBrowser (Navigation-Based Application) というプロジェクト名で開発をスタートしました。最終的なプロジェクトのリソース

f:id:hdtooo:20081229120006p:image

となりました、それぞれのクラスファイルの機能を説明します

QaSearchBrowserAppDelegateNavigation-Based AppのAppDelegate基本クラス(編集する必要なし)
QaSearchBrowserViewControllerアプリの基本となるクラス. Tableや検索窓を保持する. QaWebViewControllerのインスタンスもこれが保持する
QaWebViewControllerSufariの代わりにQ&Aを表示させる画面(UIWebViewを利用している)
QaTableViewCell今回、検索結果のTableの個々の要素をリッチするために拡張している.それを管理するクラス
QaSearchNextResultsCell検索結果の追加取得(次の20件)を今回テーブルの自動拡張で機能させている.そのTableの最後の要素を管理するクラス
QaInfo個々のQ&Aはこのクラスの要素となり、情報を出し入れする
QaListアプリが検索結果として保持しているQ&Aのリストを管理するクラス(Singleton)
RequestQueXMLYahooAPIへリクエストしてXMLを解析するクラス

です、InterfaceBuilderファイルは次の3つです(クリックで別画面で拡大参照できます)

  • MainWindow.xib

f:id:hdtooo:20081229164237p:image:small

  • QaSearchBrowserViewController.xib

f:id:hdtooo:20081229164252p:image:small

  • QaTableViewCell.xib

f:id:hdtooo:20081229164307p:image:small

プログラム(クラス,InterfarceBuilder)は

http://trac.assembla.com/cX0noKTPir3A7Oab7jnrAJ/browser/prog12

にありますので、都度参照しながら以下の解説をみてください。

Point.1 (Q&Aを管理するクラスを作る)

Q&Aは色々な情報を組み合わせて1件を構成しています。プログラム中でQ&A1件を表現するためにそれをクラスかします.QaInfo.hQaInfo.m がそれにあたります。

質問番号(qid),質問文(queString), 質問状態(condition), 回答数(ansCount), 質問日時(postedDate)を内部で保持し、アクセサで代入参照できるようにしています.

Point.2 (アプリ内でどこからでも参照できるQ&Aリストクラスを作成する)

APIから検索結果を取得し保持する、TableのCellに結果を代入するために参照する. などなど、さまざまなプログラム箇所で現時点でアプリで保持しているQ&Aのリスト(個々のQ&AはQaInfoクラス)を参照できるようにできるようにします。これを行うためのテクニックとしてSingletonでクラスを表現する方法がよく用いられます。具体的には以下のようにクラスを作成します. QaList.h

bash-3.2$ cat prog12/Classes/QaList.h
#import <Foundation/Foundation.h>
#import "QaInfo.h"

@interface QaList : NSObject {
	NSMutableArray *list;
	NSInteger totalResults;
}

+ (QaList *)sharedQaList;
- (void)addToQaList:(QaInfo *)newQaInfo;
- (QaInfo *)getQaInfo:(NSInteger)index;
- (void)clearQaList;
- (NSInteger)getQaCount;
- (void)setTotalResults:(NSInteger)hits;
- (NSInteger)getTotalResults;

@end

内部には NSMutableArray でQ&A(QaInfo)を配列として保持します. totalResultsは検索結果件数の情報です

sharedQaListのメソッドがクラスメソッドになっているのがポイントです. implementationの部分は以下になります(ポイントのみ抜粋) QaList.m

bash-3.2$ cat prog12/Classes/QaList.m
...
...
@implementation QaList
static QaList *sharedQaListInstance = nil;

+(QaList *)sharedQaList {
	@synchronized(self) {
		if(sharedQaListInstance == nil) {
			[[self alloc] init];
		}
	}
	return sharedQaListInstance;
}

+ (id)allocWithZone:(NSZone *)zone {
    @synchronized(self) {
        if (sharedQaListInstance == nil) {
            sharedQaListInstance = [super allocWithZone:zone];
            return sharedQaListInstance;
        }
    }
    return nil;
}
...
...
- init {
	if (self = [super init]) {
		list = [[NSMutableArray alloc] init];
	}
	return self;
}
@end

staticで自分自身のインスタンスを保持している部分がポイントです。これを取得するクラスメソッドsharedQaListを用いて例えば、現時点でアプリが保持しているQ&Aの数を調べるには

[[QaList sharedQaList] getQaCount]

Q&Aをリストに追加するには

[[QaList sharedQaList] addToQaList: aQaInfoObject];

プログラム中どこからでも呼び出せることになります。

Point.3 TableViewCellの表示内容をカスタマイズする

中編ではTableの要素を返すUITableViewのdataSource関数

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 

はUITableViewCellをそのまま用いcell.textに文字列を代入したのみでした、Cellを拡張してカスタマイズすることができます。今回以下のようにカスタマイズされたTableViewCellを作成しました

f:id:hdtooo:20081229130926p:image

まずは,TableViewCellの拡張クラス QaTableViewCellを作成します。Xcodeで新規ファイルで UITableViewCell subclassを選択します. QaTableViewCell.hは以下にします

bash-3.2$ cat prog12/Classes/QaTableViewCell.h
#import <UIKit/UIKit.h>
#import "QaInfo.h"

@interface QaTableViewCell : UITableViewCell {
	QaInfo *qaInfo;
	IBOutlet UILabel *lbl_queString;
	IBOutlet UILabel *lbl_postedDate;
	IBOutlet UILabel *lbl_ansCount;
}

@property (nonatomic,retain) QaInfo *qaInfo;

@end

表示に利用する質問文(queString),質問日時(postedDate), 回答数(ansCount)はUILabelでIBOutletとします。QaTableViewCell.mは以下にします.(一部省略)

bash-3.2$ cat prog12/Classes/QaTableViewCell.m
#import "QaTableViewCell.h"

@implementation QaTableViewCell

@synthesize qaInfo;
...
...
...
- (void)setQaInfo:(QaInfo *)_qaInfo {
	if(qaInfo != _qaInfo) {
		[_qaInfo release];
		qaInfo = [_qaInfo retain];
	}
	lbl_queString.text = qaInfo.queString;
	lbl_postedDate.text = qaInfo.postedDate;
	lbl_ansCount.text = [NSString stringWithFormat: @"回答数:%@",qaInfo.ansCount];
}

@end

アクセサを自前で定義してQaInfoが代入された時点でUILabelの各要素のtextを設定する部分setQaInfoも注目してください.

次にInterfaceBuilderで新規xibファイルを作成します(QaTableViewCell.xib). FileからNewを選択したときにTableViewCellはないので、Viewを選択します。その後Viewを削除しLibraryからTableViewCellを選択し交換します.

f:id:hdtooo:20081229132236p:image

TableViewCellのClassIdentityを先ほど作成したQaTableViewCellとしたいですが、プロジェクトに属さないと選択できないので、ここでInterfaceBuilderのsaveでQaTableViewCell.xibとして保存しプロジェクトに入れます。そうするとQaTableViewCellがClassとして設定できます。次にCellの上にUILabelを設置しOutletで結びます(QaTableViewCellから設置した各UILabelに結ぶ) UIViewControllerのviewのOutletとUITableViewCellを結ぶのも忘れずに..

f:id:hdtooo:20081229132633p:image.

あともう一つTableViewCellのidentifierを次のようにしておきます

f:id:hdtooo:20081229133146p:image

これでTableViewCellの準備は完了です

最後に、TableViewのtableView:cellForRowAtIndexPath:indexPathを次のようにします

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	static NSString *identifier = @"QaTableViewCell";	
	QaTableViewCell *cell = (QaTableViewCell *)[tableView dequeueReusableCellWithIdentifier:identifier];
	if(cell == nil) {
		UIViewController *controller = [[UIViewController alloc] initWithNibName:identifier bundle:nil];
		cell = (QaTableViewCell *)controller.view;
		[controller release];
	}
	cell.qaInfo = [[QaList sharedQaList] getQaInfo:indexPath.row];
	return cell;
}

Point.4 navigationControllerによるページ切り替え. & UIWebViewの利用方法

今回のアプリでは検索結果をクリックするとページが遷移してウエブページ画面に切り替わるようにします。この部分はnavigationControllerによって実現されます。イメージ的にはnavigationControllerはスタック風にページを管理して次々にページを上に乗せる、または乗せたページを取り除く風の動作をします。乗せる各々ページはUIViewControllerで作ります(UIViewではない)

QaSearchBrowserViewController.xibに(別にxibを作成しても良いが今回はここに作りました) UIViewController(QaWebViewController)を設置しその下にUIViewさらにUIWebViewを置きます。

QaWebViewControllerのクラス定義は QaWebViewController.h, QaWebViewController.mとします。

UIWebViewで指定したURLを開く方法部分は

- (void)loadRequest:(NSString *)qid viewNomalType:(bool)viewNomalType{
	if(viewNomalType) {
		[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q%@",qid]]]];
	} else {
		[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://chiebukuro.yahoo.co.jp/iphone/detail/q%@",qid]]]];		
	}
}

qidを指定すると指定のQ&Aページが開くようになります。(Yahoo!知恵袋ではiPhone最適化されたページとPC用のページがあり引数で出し分けできるようにしました).

検索結果をクリックしたら、このQaWebViewControllerのUIViewControllerをnavigationControllerに設置し画面遷移をさせます。その部分は QaSearchBrowserViewController.m 内の

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
	[webViewController loadRequest:[[[QaList sharedQaList] getQaInfo:indexPath.row] qid] viewNomalType:NO];
	[self.navigationController pushViewController:webViewController animated:YES];
}

で、UIWebViewの画面を切り替えた後, pushViewController で画面を遷移させています。

Point.5 TableViewの動的拡張

iPhoneアプリでよく利用される、Tableの自動拡張によるページ送り(次の○件をクリックするとTableが動的に伸びる形式)を作ってみます。

f:id:hdtooo:20081229161719p:image

UITableViewのCellの最後のCellをページ送りをするために準備します. QaSearchBrowserViewController.mのUITableView関連の関数を参照してください. 要素数を応答する際に+1の数を、要素(Cell)を返却する部分では通常のCell(QaTableViewCell)ではなく、遷移用の専用のCell(QaSearchBrowserViewController.xibで定義) QaSearchNextResultsCel を応答します. クリック時の動作時はAPIに再読み込みをさせます. この場合はQaListをクリアせずさらに追記する形でQaListに要素を加えます

最後に

今回はポイントのみを解説したため、順を追った全体的なプログラムのイメージが掴めなかったかもしれませんがプログラムのソースを確認し理解を深めてください。

http://trac.assembla.com/cX0noKTPir3A7Oab7jnrAJ/browser/prog12

  1. 開発環境の構築
  2. Webの基本
  3. Webサービス (WebAPI)
  4. CGIの基本 プログラム演習1
  5. ウエブAPIを用いた初めてのCGI (準備編) プログラム演習2
  6. ウエブAPIを用いた初めてのCGI (perl編) プログラム演習3
  7. ウエブAPIを用いた初めてのCGI (PHP編) プログラム演習4
  8. オブジェクト指向とMVC
  9. テンプレートエンジン (PHP Smarty) プログラム演習5
  10. 拡張性の高いプログラムを作る プログラム演習6
  11. ウエブセキュリティ
  12. JavaScript
  13. Ajaxを用いたウエブページ プログラム演習7
  14. 地図APIを使ったアプリケーション プログラム演習8
  15. DataBase(MySQL)
  16. MySQLを利用した掲示板 プログラム演習9
  17. 携帯(モバイル)への対応
  18. flash - ActionScript (前編)
  19. flash - ActionScript (後編)
  20. iPhoneプログラミング(前編)
  21. iPhoneプログラミング(中編)
  22. iPhoneプログラミング(後編)
ソースコードはこちらにもあります http://trac.assembla.com/cX0noKTPir3A7Oab7jnrAJ/browser