iOS (iPhone/iPad) アプリ開発で UITableView と UINavigationView を組合せて選択式メニューのような機能を作る
ほぼ個人的なメモ。
- UITableViewController 側から、自分を管理している UINavigationViewController のインスタンスを参照するときは、self.navigationController を使う。
- 下の階層 (newController) に進ませるときは、[self.navigationController pushViewController: newController animated: YES]; とする。
- 強制的に戻るときは、[self.navigationController popViewControllerAnimated: YES ]; とする。
- TableView の項目は、強制的に描画させないかぎり、最初に表示されたときの状態から変わらない。
- 選択したメニューにあわせて表示項目を変えたい場合は、[self.tableView reloadData]: として強制的に再描画させる必要がある。
- 選択式メニュー(メニューの項目の一覧を与えて、その項目のどれが選択されたかという値を返すような、汎用のクラスは提供されていない(たぶん)。
以下、ARC なしで iOS の Empty アプリケーションを作って、UITableViewController のサブクラスとして FirstViewController クラスと SelectTableController という二つのクラスを作る例を挙げてみる。SelectTableController を、汎用的に使える選択式メニューにする。IB は使っていない。
この例では UITableViewController のインスタンスをArrayにすべて保持する方法をとっている。選択肢が多くなると、この方法ではメモリを圧迫する可能性がある。これを避けるには、他のところで書いてるように Core Data を使ってデータを管理するか、viewWillAppear, viewWillDisappear をうまくつかって処理する方法がある。
ソース例
- AppDelegate.h
UINavigationViewController と、それにセットする UITableViewController のサブクラスを作成する。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease]; // トップレベルの TableViewController FirstViewController *first = [[FirstViewController alloc] initWithStyle: UITableViewStylePlain ]; UINavigationController *navi1 = [[UINavigationController alloc] initWithRootViewController:first ]; // Windows の rootViewController として navi1 をセット [self.window setRootViewController: navi1]; self.window.backgroundColor = [UIColor whiteColor]; [self.window makeKeyAndVisible]; return YES; }
- FirstViewController.h
@interface FirstViewController : UITableViewController { // メニューの項目(SelectViewController)のリストを保持する NSMutableArray *childTables; } @end
- FirstViewController.m
#import "FirstViewController.h" #import "SelectTableController.h" @implementation FirstViewController // 子メニューに使う選択肢の文字列リスト static NSString* letters[4] = {@"A",@"B",@"C",@"D"}; - (id)initWithStyle:(UITableViewStyle)style { self = [super initWithStyle:style]; if (self) { childTables = [[NSMutableArray alloc] init]; for ( int i = 0; i < 10; i++ ) { // 子メニューに与える選択肢の一覧を Array にセットする。 // この配列の内容を変更することで、子メニューでの選択肢の内容を変化させられる。 NSArray *strs = [NSArray arrayWithObjects: [NSString stringWithFormat: @"%d: %@", i, letters[0] ], [NSString stringWithFormat: @"%d: %@", i, letters[1] ], [NSString stringWithFormat: @"%d: %@", i, letters[2] ], [NSString stringWithFormat: @"%d: %@", i, letters[3] ], nil ]; // 子メニューを作って childTables に保持しておく。 SelectTableController *child = [[SelectTableController alloc] initWithStyle:UITableViewStylePlain stringArray: strs]; [childTables addObject: child]; } } return self; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } #pragma mark - View lifecycle - (void)viewDidLoad { [super viewDidLoad]; [self.navigationItem setTitle: @"First"]; } - (void)viewDidUnload { [super viewDidUnload]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; } // 画面遷移などで self.tableView が表示される時に呼ばれる - (void)viewDidAppear:(BOOL)animated { // 強制的にメニューの項目をすべて再表示させる。 // これをしないと、子メニューで選択された項目の内容が表示に反映されない。 [self.tableView reloadData]; [super viewDidAppear:animated]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return (interfaceOrientation == UIInterfaceOrientationPortrait); } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; // この例ではセクション数は 1 つ } // 保持するメニューの項目数を返す。 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [childTables count]; } // メニュー項目を表示する - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; } // 右側に矢印のアイコンをつける cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; SelectTableController *sel = [childTables objectAtIndex:[indexPath row]]; // 子メニューでいずれかの項目を選択済なら、その項目の内容に対応する文字を表示させる。 // 何も選択されていなければ、未選択と表示する。 if ( sel.selectedNumber >= 0 ){ [cell.textLabel setText: [NSString stringWithFormat: @"%d: 選択: %@", [indexPath row], letters[sel.selectedNumber] ]]; }else{ [cell.textLabel setText: [NSString stringWithFormat: @"%d: 未選択", [indexPath row] ]]; } return cell; } #pragma mark - Table view delegate // 項目が選択されたときに呼びだされる - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // 選択された項目に対応する SelectTableController を遷移する画面として // self. navigationController に push する。 [self.navigationController pushViewController:[childTables objectAtIndex:[indexPath row]] animated: YES ]; } @end
- SelectTableController.h
@interface SelectTableController : UITableViewController { // 選択肢のメニュー(文字列)nの一覧を保持する NSArray* array; // 選択されたメニューの番号を保持する。 NSInteger selectedNumber; } // 外部のインスタンスから選択されたメニューの番号を参照できるようにする。 @property (nonatomic, assign) NSInteger selectedNumber; // 選択肢のメニュー(文字列/NSString)を stringArray として与えて初期化できるメソッドを用意 - (id)initWithStyle:(UITableViewStyle)style stringArray:(NSArray*)stringArray; @end
- SelectTableController.m
#import "SelectTableController.h" @implementation SelectTableController // selectedNumber のアクセサ(getter/setter)を生成 @synthesize selectedNumber; - (id)initWithStyle:(UITableViewStyle)style { self = [super initWithStyle:style]; if (self) { // Custom initialization } return self; } // メニューの項目一覧を stringArray として受け取って初期化 - (id)initWithStyle:(UITableViewStyle)style stringArray:(NSArray*)stringArray; { self = [super initWithStyle:style]; if ( self ){ // retain して array を保持する。 array = [stringArray retain]; selectedNumber = -1; // -1 は未選択状態を意味することにする。 } return self; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } #pragma mark - View lifecycle - (void)viewDidLoad { [super viewDidLoad]; } - (void)viewDidUnload { [super viewDidUnload]; [array release]; // ratin に対応して release する。 } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return (interfaceOrientation == UIInterfaceOrientationPortrait); } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [array count]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; } // メニューの項目(文字列)の設定 [cell.textLabel setText: [array objectAtIndex:[indexPath row]]]; return cell; } #pragma mark - Table view delegate - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { // メニューが選択さたときに、その番号を保持しておく。 selectedNumber = [indexPath row]; // 強制的にひとつ上の階層に戻す。これをコメントアウトすると、選択しただけでは戻らなくなる。 [self.navigationController popViewControllerAnimated: YES ]; } @end