テーブルに表示するデータの準備
テーブルに表示するために、複数のセクションに分かれていて、さらにそのセクションごとに複数の行を含むようなデータを準備するのは、たとえダミーでもなかなか面倒です。構造はともかくとして、ある程度の数がなければセクションに分けて表示するメリットが感じられないからです。
そこで今回は、iOSに埋め込まれている既存のデータを抜き出して使うことにします。それに手を加えて目的の形式に変換することにより、必要なデータを用意します。その「既存の」データとは、世界のタイムゾーンの時差を記録している地域の名前を集めた配列です。その名もTimeZoneクラスのknownTimeZoneIdentifierというプロパティの値として、例えば以下のようにして簡単に入手することができます。
var timeZones = TimeZone.knownTimeZoneIdentifiers
まずは、それがどんな内容を含んでいるのか知るために、生データのままテーブルビューに表示してみましょう。そのためには、UITableViewControllerを引き継いで作るPGTVControllerの定義の中にまず上の1行を書きます。あとは前回のfruits配列の代わりにtimeZones配列を指定すれば、この配列のすべての内容を含むテーブルビューが表示されます。
画面には、もちろん全部のデータを一度に表示することはできませんが、この配列は「Africa/Abidjan」から始まって、「Pacific/Wallis」まで、全部で434もの地名を文字列として含んでいます。
セクションと行に分けてタイムゾーン配列の中身を表示する
表示されたタイムゾーンの文字列を見ると、先頭に「Africa」や「America」といった大陸規模の(ただし必ずしも大陸の名前ではありません)地域名が来て、その後ろに「/」(スラッシュ)で区切って、都市の名前が続くのが基本的なスタイルだとわかります。基本的にはこの大きな地域をセクションとして、都市をその中の行とすればよさそうです。
表示されたテーブルビューをスクロールして見ていくとわかりますが、これには例外があります。1つは、「America」地域がさらに米国の州などに分かれていて、さらにその下に都市の名前が続く場合があることです。例えば「Indiana」州は、以下のようになっています。
America/Indiana/Knox
America/Indiana/Marengo
...
America/Indiana/Winamac
テーブルビューのセクションは多段階にすることができません。そこで、このような場合は「America」をセクションの名前として、残りの「Indiana/Knox」のような「州/都市」を1つの行として表示することにします。ただし、あとで述べるように、区切り記号は変更しましょう。
もう1つの例外は、「地域/都市」ではなく「GMT」が1つの要素となっていることです。これは言うまでもなく「グリニッジ標準時」のことなので、それが単独でタイムゾーンの中に含まれているのは理解できます。苦肉の策ですが、この場合はセクション名を「GMT」として、その中の行にも「GMT」を入れることにします。
これで方針が定まりました。テーブルビューをセクションに分けて表示するには、基本的に2つの配列が必要となります。1つは、セクションの名前の配列です。この場合最初の要素が「Africa」、最後の要素が「Pacific」となる配列です。もう1つは、このセクションごとの中身の配列をセクションの数だけ並べた配列です。これは二重の配列(文字列の配列の配列)ということになります。最初の要素は「Africa」地域に含まれる都市名の配列、次の要素は「America」地域の都市名の配列、最後の要素が「Pacific」地域の都市名の配列ということになります。途中に「GMT」に対応して、要素が「GMT」の1つだけの配列も含みます。
このような2つの配列は、timeZones配列から機械的に作成することができます。そのためのプログラムは、PGControllerの中のviewDidLoadメソッドの定義として記述することにします。このメソッドは、ビューコントローラーにビューがロードされたことを知らせるためのものです。その中にはそのタイミングで実行すべきビューの初期化などの処理を書くことができます。
1つ目の配列はregionNamesとして、文字列の配列として初期化しておきます。2つ目の配列areaNamesは、文字列の配列の配列として初期化します。viewDidLoadの中では、timeZones配列の要素を最初から順に全部調べます。その際にStringタイプのcomponentsメソッドを使って、「/」で区切られた文字列をその位置で分解した配列に変換しています。その配列の最初の要素は地域名となります。そこで新しい地域名が出てくるたびに、その名前をregionNames配列に追加します。さらに、areaNames配列の要素として追加する配列の位置を示す変数regionIndexの値を1つ増やし、areaNames配列に空の配列を追加します。ここまでの処理がswitch文の前までです。
続くswitch文の中では、上で述べたように、地域名に続く部分の例外に対応するための処理を実行します。
timeZones配列の要素を「/」で分解した配列の要素の数が1の場合(「GMT」の場合)、areaNames配列には、その要素1つからなる配列を代入します。要素の数が2の場合は、areaNames配列に都市名を要素として追加します。ほとんどの場合は、この処理になります。要素の数が3の場合は、都市名の部分が州と都市に分かれている場合です。その場合は州と都市の名前を「 - 」で区切ったものを、1つの文字列としてareaNames配列に追加します。言葉で書くとわかりにくいですが、実際のデータを想像しながらプログラムを追ってみれば難しくないでしょう。
以上のようにして2つの配列が作成できれば、あとはテーブルビューコントローラーの規定のメソッドによって必要なデータを返すようにすれば、自動的にテーブルビューの中身が表示されるはずです。前回は、行の数と行の内容を返す2つのメソッドだけを実装しました。今回はそれに加えてセクションの数を返すメソッドと、セクションのタイトルを返すメソッドを実装しています。
前者では、regionNames配列の要素の数を、後者では指定されたインデックスに対するregionNames配列の中身を返しています。また、行の中身を返すメソッドでは、2次元の配列であるareaNamesの中身を返しますが、その際のインデックスは、与えられたindexPathオブジェクトのsectionプロパティと、rowプロパティを取り出して別々に指定しています。このindexPathによってセクションと行の番号を一度に指定できるのです。
このプログラムを動かすと、「Africa」や「America」といった地域名がセクションのタイトルとなって薄いグレーをバックに少し太い文字で表示され、その間に都市の名前が行として表示されます。
テーブルビューにインデックスを付ける
テーブルビューをセクションに分割しても行の数が減るわけではありません。このように行の数が400以上もあると、そのままでは目的のものを探し出すのも大変です。しかし、テーブルビューをセクションに分割したことによって、インデックス機能を使って頭出しが可能になるという大きなメリットが生まれます。
テーブルビューにインデックスを付けるのは、通常はセクションがアルファベット、あるいは50音の文字に分割されている場合です。テーブルビューの右側にアルファベットや50音の文字がオーバーラップして、インデックスとして表示されるのを見ることも少なくないでしょう。
しかし、テーブルビューのインデックス機能は必ずしもセクションがアルファベットや50音に分かれている場合だけに有効とは限りません。今回のような地域名そのものをインデックスとすることもできるのです。その場合には、インデックス(索引)というよりも目次的なものになります。実際に今回のようにセクションを地域名で区切った場合、全部で11ある地域名のうちなんと最初の6つは、すべて「A」で始まっています。これではアルファベットのインデックスで頭出ししようとしても、効果的でないことは明らかです。
テーブルビューにインデックスを付けるのは非常に簡単です。テーブルビューコントローラーの中に、もう1つのメソッド、sectionIndexTitlesを実装すればいいのです。そこではインデックスとして使う文字列の配列を返すだけです。その配列の要素の数と位置はセクション名の配列と対応したものとします。この例ではセクション名の配列、regionNamesそのものを返しています。
これによってインデックスは1文字ではなく、地域名そのものとなり、ほとんど目次として機能します。テーブルビューの右側に赤い文字でオーバーラップして表示された地域名の1つをタップすると、その地域の最初の行がテーブビューの先頭から表示されるようになります。
次回の予定
今回は、テーブルビューを複数のセクションに分けて表示し、さらに目次として使えるインデックスを表示する方法も示しました。テーブビューの表示方法にはほかにもいろいろありますが、それはひとまずこれくらいにしておきましょう。次回からは単に表示するだけでなく、操作できるようにする方法を取り上げていく予定です。