[iOS] StoryboardでUITableViewを実装し理解する [4月からはじめるiPhoneアプリ #1]

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

この記事は4月からiOSアプリエンジニアとして働く方、転向する方を対象としています。

「iOSアプリケーションを1度でも作ったことがある」、もしくは「入門書を1冊でも読んだことがある」方には特に参考になるような記事になると思います。

UIKit入門

UIKitとは

The UIKit framework provides the classes needed to construct and manage an application’s user interface for iOS. It provides an application object, event handling, drawing model, windows, views, and controls specificallyC designed for a touch screen interface.

引用元:UIKit Framework Reference

要約すると、 iOSアプリの画面や操作に関わるインターフェースを提供しているよ ということです。

一般的にMVCで言われるView(画面)やController(ViewとModelをつなぐ・画面遷移)に該当するiOSのフレームワークということですね。

このフレームワークの中でもよく使われものを挙げると

  • UIStoryboard
  • ViewControllers
    • UIViewController
    • UINavigationController
    • UITabBarController
    • UITableViewController
  • UIView
    • UILabel
    • UIButton
    • UIImageView
    • UITextField
    • UIScrollView
    • UITableView

といったものがあります。 今回はこの中でも、iPhoneの見た目を実装する上で使わないアプリがほとんどないとも言える、 UITableView についてサンプルプロジェクトでの実装を交えて解説していきます。

Static CellsとDynamic Prototypes

UITableViewにはテーブルのセルが固定されている Static Cells とテーブルのセルがデータの数で可変する Dynamic Prototypes があります。

データが可変するアプリ(TODOアプリなどデータを追加削除していくアプリ・サーバーとの通信を介すアプリ全般)は Dynamic Prototypes を使うことになります。

Static Cellsは設定画面の設定項目一覧など、項目が変わらない場合に使います。Storyboard上で簡単にレイアウトを組めるのでDynamic Prototypesより手早く作れる利点があります。

StoryboardからUITableViewを使う

Xcodeで新規プロジェクト(私はSingle View Applicationを選択しましたが、何でも構いません。)を作ります。

uitableview_01_mini

こんな感じでTableViewをStoryboardで配置します。

その後分割ビュー(Xcode右上のスーツに蝶ネクタイみたいなアイコン)をクリックし、TableViewをViewController.mのプロパティとして紐付けします。

uitableview_02_mini

そしてUITableViewのプロトコルに準拠することを@interface部で宣言します。 UITableViewDelegateとUITableViewDataSourceの2つを追記します。

#import "ViewController.h"

@interface ViewController ()<UITableViewDelegate, UITableViewDataSource>

@property (weak, nonatomic) IBOutlet UITableView *tableView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

ここで警告が2つほど出ますが、これはUITableViewの必須メソッドが記述されていないからです。

この2つのメソッドは警告をクリックして見つけるのも良いですが、UITableViewDataSourceを Command + クリック で定義元を見て、@required修飾子(参照先での実装が必須)のメソッドをコピーしてくると早いです。

@interface xxViewController ()<○○Delegate> ←ここをCommand + クリック

この記述はよくあるので、覚えておくと開発がスムーズに行えます。

@protocol UITableViewDataSource<NSObject>

@required

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;

// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)

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

私はこの方法で上記メソッド2つをコピーして戻り、ViewController.mへペーストしています。

何はともあれテーブルビューは必須メソッドが2つある、ということを覚えて下さい。

  • テーブルにいくつのデータがあるか
  • テーブルの中のセルはどんなセルか

この2つのメソッドはUITableViewを使う上では必須となります。

いくつのデータがあるかは表示したいデータをNSArrayなどのコレクションで定義し、それのcountを返すようにすると楽です。

実装したのが以下です。

#import "ViewController.h"

@interface ViewController ()<UITableViewDelegate, UITableViewDataSource> // テーブルビューのプロトコルに準拠します

/**
 Storyboardに配置したテーブルが紐づいてます
 */
@property (weak, nonatomic) IBOutlet UITableView *tableView;

/**
 テーブルに表示する情報が入ります。
 */
@property (nonatomic, strong) NSArray *dataSourceiPhone;
@property (nonatomic, strong) NSArray *dataSourceAndroid;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // デリゲートメソッドをこのクラスで実装する
    self.tableView.delegate = self;
    self.tableView.dataSource = self;

    // テーブルに表示したいデータソースをセット
    self.dataSourceiPhone = @[@"iPhone 4", @"iPhone 4S", @"iPhone 5", @"iPhone 5c", @"iPhone 5s"];
    self.dataSourceAndroid = @[@"Nexus", @"Galaxy", @"Xperia"];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

#pragma mark - UITableView DataSource

/**
 テーブルに表示するデータ件数を返します。(必須)

 @return NSInteger : データ件数
 */
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSInteger dataCount;

    // テーブルに表示するデータ件数を返す
    switch (section) {
        case 0:
            dataCount = self.dataSourceiPhone.count;
            break;
        case 1:
            dataCount = self.dataSourceAndroid.count;
            break;
        default:
            break;
    }
    return dataCount;
}

/**
 テーブルに表示するセクション(区切り)の件数を返します。(オプション)

 @return NSInteger : セクションの数
 */
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 2;
}

/**
 テーブルに表示するセルを返します。(必須)

 @return UITableViewCell : テーブルセル
 */
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    // 再利用できるセルがあれば再利用する
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (!cell) {
        // 再利用できない場合は新規で作成
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                      reuseIdentifier:CellIdentifier];
    }

    switch (indexPath.section) {
        case 0:
            cell.textLabel.text = self.dataSourceiPhone[indexPath.row];
            break;
        case 1:
            cell.textLabel.text = self.dataSourceAndroid[indexPath.row];
            break;
        default:
            break;
    }

    return cell;
}

@end

uitableview_03_mini

2セクション作りました。Storyboard上のUITableViewのStyleを Grouped にしました。セクションが複数ある場合はStyleをGroupedにセットするとセクションの切れ目でグルーピングしてくれます。

以下実装順です

  1. Storyboardと実装クラスを紐付ける

  2. プロトコルに準拠する<UITableViewDelegate, UITableViewDataSource>

  3. デリゲートメソッドを自クラスに設定する

  4. データソースを定義、複数ある場合はセクション分のArrayがあると管理が楽

  5. デリゲートメソッドを実装する(必須)

  6. セクション数を返すメソッドを追加(任意)

まとめ

ポイントとしては

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

このメソッドの引数indexPathの扱いがあります。

NSIndexPathはsection(いくつ目の仕切りか)とrow(何行目に表示するか)の2つの組み合わせでできています。 つまりindexPath.sectionの値でデータソースを切り替え、dataSource[indexPath.row]と指定することで

X番目のデータをX行目に表示することが簡単に可能 です。(実際はArrayは0番目からですが、表示されたら1番目)

次回は更に一歩進んで、テーブルに表示したデータを検索する 記事を書きます。