iOS Table View入門 #3

2012.03.12

前回に引き続き、セクションについて見ていきたいと思います。今回はセクションインデックスです。

セクションインデックス

セクションインデックスは、テーブルの右側に表示されるセクションタイトルのリストで、タップされたセクションタイトルに対応するセクションにスクロールして移動する機能を持っています。電話帳アプリなどで利用されており、リストの件数が多くなることが考えられる場合は、セクションインデックスを利用するとユーザーが目的のデータにアクセスしやすくなります。

では実際にセクションインデックスを使用してみたいと思います。前回までのサンプルに修正を加えて、支店名・支店のよみがな・支店のある都道府県のフィールドを持つデータを扱うセクションインデックス付きのテーブルを作成します。まずは支店名のよみがなでセクション分けをするテーブルを作成します。

エンティティの作成

まず、テーブルのデータを格納するエンティティクラスを作成します。支店名・よみがな・都道府県の3つのプロパティを定義し、指定イニシャライザを作成しています。

Store.h

#import <Foundation/Foundation.h>

@interface Store : NSObject

@property(nonatomic, strong) NSString *branchName;
@property(nonatomic, strong) NSString *phoneticGuides;
@property(nonatomic, strong) NSString *prefecture;

- (id)initWithBranchName:(NSString *)aBranchName 
          phoneticGuides:(NSString *)aPhoneticGuides 
              prefecture:(NSString *)aPrefecture;

@end

Store.m

#import "Store.h"

@implementation Store

@synthesize branchName;
@synthesize phoneticGuides;
@synthesize prefecture;

- (id)initWithBranchName:(NSString *)aBranchName 
          phoneticGuides:(NSString *)aPhoneticGuides 
              prefecture:(NSString *)aPrefecture
{
    self = [super init];
    if (self != nil) {
        self.branchName = aBranchName;
        self.phoneticGuides = aPhoneticGuides;
        self.prefecture = aPrefecture;
    }
    return self;
}

@end

データの作成とセクションインデックス対応

次に、TableViewController.mを修正していきます。まずはリストに表示するデータの準備です。データを作成するcreateDataメソッドを追加します。

- (NSArray *)createData
{
    Store *temp1 = [[Store alloc] initWithBranchName:@"新宿店"
                                      phoneticGuides:@"しんじゅく"
                                          prefecture:@"東京都"];
    Store *temp2 = [[Store alloc] initWithBranchName:@"飯田橋店"
                                      phoneticGuides:@"いいだばし"
                                          prefecture:@"東京都"];
    Store *temp3 = [[Store alloc] initWithBranchName:@"立川店"
                                      phoneticGuides:@"たちかわ"
                                          prefecture:@"東京都"];
    Store *temp4 = [[Store alloc] initWithBranchName:@"大手町店"
                                      phoneticGuides:@"おおてまち"
                                          prefecture:@"東京都"];
    Store *temp5 = [[Store alloc] initWithBranchName:@"成田店"
                                      phoneticGuides:@"なりた"
                                          prefecture:@"千葉県"];
    Store *temp6 = [[Store alloc] initWithBranchName:@"浦安店"
                                      phoneticGuides:@"うらやす"
                                          prefecture:@"千葉県"];
    Store *temp7 = [[Store alloc] initWithBranchName:@"みなとみらい店"
                                      phoneticGuides:@"みなとみらい"
                                          prefecture:@"神奈川県"];
    Store *temp8 = [[Store alloc] initWithBranchName:@"川崎店"
                                      phoneticGuides:@"かわさき"
                                          prefecture:@"神奈川県"];
    Store *temp9 = [[Store alloc] initWithBranchName:@"溝の口店"
                                      phoneticGuides:@"みぞのくち"
                                          prefecture:@"神奈川県"];
    Store *temp10 = [[Store alloc] initWithBranchName:@"朝霞店"
                                       phoneticGuides:@"あさか"
                                           prefecture:@"埼玉県"];
    NSArray *tempDatas = [[NSArray alloc] initWithObjects:temp1, temp2, temp3, temp4, temp5, temp6, temp7, temp8, temp9, temp10, nil];
    
    return tempDatas;
}

viewDidLoadを修正します。用意したデータのセクション分けとソートを行います。

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    NSArray *tempDatas = [self createData];
    UILocalizedIndexedCollation *collation = [UILocalizedIndexedCollation currentCollation];
    
    NSInteger sectionCount = [[collation sectionTitles] count];
    NSMutableArray *unsortedSections = [NSMutableArray arrayWithCapacity:sectionCount];
    
    for (int i = 0; i < sectionCount; i++) {
        [unsortedSections addObject:[NSMutableArray array]];
    }
    
    for (id data in tempDatas) {
        NSInteger index = [collation sectionForObject:data collationStringSelector:@selector(phoneticGuides)];
        [[unsortedSections objectAtIndex:index] addObject:data];
    }
    
    NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
    
    for (NSMutableArray *section in unsortedSections) {
        [sections addObject:[collation sortedArrayFromArray:section collationStringSelector:@selector(phoneticGuides)]];
    }
    
    datas = sections;
}
[/c]</p>
<p>ソースコードを順に追っていきます。</p>

<h3>UILocalizedIndexedCollation</h3>
<p>6行目でUILocalizedIndexedCollationのインスタンスを取得しています。このクラスは、セクションとセクションインデックスのリストに関する様々な処理を肩代わりしてくれるヘルパークラスです。実装されているプロパティとメソッドの役割は以下の通りです。</p>
<h4>sectionTitles</h4>
<p>UILocalizedIndexedCollationは、実行環境のロケールに応じたセクションタイトルのリストを内部で生成して管理しています。sectionTitlesプロパティは内部で保持しているセクションタイトルのリストを返します。このリストを利用して各データのセクションの分類とセクションヘッダのタイトルの設定を行います。</p>
<h4>sectionIndexTitles</h4>
<p>セクションタイトルのリストと同様に、実行環境のロケールに応じたセクションインデックスタイトルのリストも内部で生成して管理されています。sectionIndexTitlesプロパティはこのセクションインデックスタイトルのリストを返します。このリストを利用して、テーブルのセクションインデックスを設定します。なお、言語が英語の場合はaからzとその他を表す#、日本語の場合はaからzの一部を間引いたものと五十音の「あ」の段のインデックスリストが作成されます。</p>
<h4>sectionForObject:collationStringSelector:</h4>
<p>このメソッドは、データが分類されるべきセクションの照会を行うもので、データを渡すと分類されるべきセクションのインデックスを返してくれます。collationStringSelector:パラメータで渡したセレクタでデータオブジェクトに対してメッセージを送信し、その返す値によってセクションの分類が行われます。したがって、このセレクタは渡すデータオブジェクトで実装されるプロパティもしくはメソッドである必要があります。返されるインデックスは、sectionTitlesのインデックスに対応しています。</p>
<h4>sectionForSectionIndexTitleAtIndex:</h4>
<p>このメソッドは、セクションインデックスに対応するセクションの照会を行います。セクションインデックスタイトルリストのインデックスをパラメータで渡すと、対応するセクションタイトルリストのインデックスを返します。</p>
<h4>sortedArrayFromArray:collationStringSelector:</h4>
<p>このメソッドは実行環境のロケールに従ってデータのソート処理を行うもので、パラメータで渡した配列内のデータのソートを行ってくれます。collationStringSelector:パラメータの使用方法はsectionForObject:collationStringSelector:メソッドと同じで、このセレクタで指定されたプロパティまたはメソッドが返す値でソートを行います。</p>
<p>詳しくは<a href="https://developer.apple.com/library/ios/#DOCUMENTATION/iPhone/Reference/UILocalizedIndexedCollation_Class/UILocalizedIndexedCollation.html">UILocalizedIndexedCollation Class Reference</a>を参照して下さい。</p>

<h3>データのセクション分けとソート</h3>
<p>先ほどのviewDidLoadメソッドに戻りましょう。</p>
<p>8〜13行目では各セクションのデータを保持する一時領域を用意しています。UILocalizedIndexedCollationのsectionTitlesプロパティからセクションの数を取得しています。なお、今回は言語が日本語ですので、sectionTitlesプロパティで返される配列は"a"から"z"と"あ"から"わ"になります。</p><p>16行目では、作成したデータをUILocalizedIndexedCollationのsectionForObject:collationStringSelector:メソッドに渡して分類されるべきセクションのインデックスを取得しています。今回はphoneticGuidesをセレクタで指定してよみがなで分類されるようにしています。17行目では、これによって得たインデックスを利用して然るべきセクションにデータを格納しています。</p>
<p>22〜24行目では、UILocalizedIndexedCollationのsortedArrayFromArray:collationStringSelector:メソッドを利用して各セクション内のデータをソートしています。こちらもphoneticGuidesをセレクタで指定してよみがなでソートを行っています。</p>

<h2 id="toc-3">データソースメソッドの修正</h2>
<p>UITableViewDataSourceプロトコルで定義されるメソッドは以下の様に修正します。</p>

<p>numberOfSectionsInTableView:メソッドです。セクションタイトルの数をUILocalizedIndexedCollationから取得して返します。</p>
<p></p>
<p>prefecturesにセクションインデックスとセクションのタイトルとして使用する都道府県名の配列をセットします。あとは、9行目のセクション数、17行目の個々のデータの分類先セクションのインデックスの取得部分でUILocalizedIndexedCollationの代わりにprefecturesを利用します。セクション内のソートは五十音順にしたいので、UILocalizedIndexedCollationをそのまま利用します。</p>

<p>下記のデータソースメソッドも全てprefecturesを利用して値を返すよう修正します。</p>
<p>
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return prefectures.count;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    return [prefectures objectAtIndex:section];
}

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
    return prefectures;
}

今回はセクションインデックスタイトルのリストとセクションタイトルのリストが一致しています。このような場合には、tableView:sectonForSectionIndexTitle:atIndex:メソッドではセクションインデックスのタイトルのインデックスをそのまま返すだけで問題ありません。

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
    return index;
}

動作確認

これで修正できました。実行結果です。

セクションインデックスがprefecturesの内容になり、また、データもきちんと都道府県別のセクションに分類されていることが確認できると思います。任意のセクションインデックスを利用する際は、UILocalizedIndexCollationが受け持っていた処理を適切に実装することで対応できました。

参考サイト

iOS Developer Library – 日本語版
How to use UILocalizedIndexedCollation to add a localized index to a UITableView (aka adding an A-Z index to a UITableView)