[iOS] StoryboardでUITableView+UINavigationControllerの詳細画面を作る [4月からはじめるiPhoneアプリ #4]

ios1-100x100

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

はじめに

こんにちは!

前回のUITableViewのセルをカスタマイズするから引き続き、UITableViewについて書きます。

今回は非常に使う機会の多い、テーブルのセルをタッチした時に詳細画面に遷移するアプリを作成します。

これができるようになりますと、一覧画面の一項目をタップ → 詳細画面へ遷移といったようなアプリの処理が実装できます。

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

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

Storyboardは使いづらい?

今回も小ネタを挟んでいきます。

先日、コメントを残して下さった方がいて「Storyboardを使わなくてもコードでUITableViewを使った画面の作成が出来ますか?」と聞かれました。

もちろん全てコードで同じ事ができます。

同じように思われている方もいらっしゃると思いますので、まずはStoryboardのメリットを知りましょう。

  1. 画面をGUIで作れる。
  2. 簡単な画面遷移ならすぐに作れる。
  3. [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"background"]]など静的な画面に関するコードが大幅に省ける。
  4. 他人が作ったソースコードでもStoryboardがあれば大体どんなアプリかがわかる。
  5. UIに関するメンテナンスが楽(位置調整や、既存UIの間に画像を追加するなど)
  6. タブ遷移やナビゲーション遷移がすぐに作れる。(コードで書くと面倒)

私自身が使い始めにやりづらかった事は以下のような事があります。

  1. Xcode独特の操作を覚える事が必要。(Control + ドラッグでコードとUIの接続とか)
  2. 慣れないうちはハマる。例として、接続したIBOutletやActionの解除をしないまま、Storyboardからオブジェクトを消してアプリが落ちるなど。
  3. Storyboardに記述しているプロパティはコードで補完されない。UIが何個も配置された画面でタグをStoryboardで手打ちしていると、カオス。
  4. 画面間で値を受け渡したい時に、prepareForSegueやunwindSegueの記述が必要。
  5. Storyboardを操作しなくても開いた時に変更がかかることもあるので、チームで開発しているときは無駄なコンフリクトに注意する。

以上のようなことが思い浮かびました。

たまに1つのStoryboardに全ての画面をまとめないといけない、と書かれている記事を目にすることもありますが、普通に機能毎とかに分割できます。

使いづらい事は慣れる、もしくは開発スタート時から理解していれば避けることで解決できます。

これからiOSアプリを作成される方は確実にStoryboardは使った方が良いかと思います。使わないと細かいUIの修正などが非常に気だるくなります。

ただ、ゲームアプリなどの標準UIとは切り離して作成したいアプリは例外でしょう。

そのうちStoryboardの使い方についての記事も書きたいと思っています。

UINavigationControllerをStoryboardで作る

iOSアプリでとても多い、UINavigationControllerを今回は使っていきます。

ヘッダーについてる < 戻るとか書いてあるやつですね。

まずは前回のプロジェクトをgithubから取得してきましょう。

tableview_navi_01

画面右下のURLからgit clone、もしくはDownload ZIPを押して自分のXcodeの作業フォルダで解凍してください。

Xcodeプロジェクトファイルを開きます。

tableview_navi_02

プロジェクトナビゲーター(表示されていなければCommand + 1で出ます。)からViewグループを右クリック → New FileでUIViewControllerのサブクラスを作ります。名前を「DetailViewController」にしました。

StoryboardのTable VIewが配置されている右隣に新しいViewControllerを配置し、Identity Inspectorを開きます。Classに作成したDetailViewControllerを指定します。

その後、ViewControllerを選択します。ViewControllerの選択はTableViewの下側に表示されている、黒いエリアをクリックすれば選択できます。

一番左のViewControllerのアイコンからControl + ドラッグ操作で右側のDetailViewControllerへ接続します。遷移方法はpushを選びます。

ここで、つなぎ目のアイコン(Segue)をクリックし、Attributes Inspectorを開きます。

IdentifierをpushDetailViewにしました。

tableview_navi_05

その後、ViewControllerを選択したままmacの画面最上部からEditor → Embed In → Navigation Controllerを選択します。

tableview_navi_04

画面遷移時に値を受け渡しする

追加したViewControllerにImageViewとLabelを配置しましょう。DetailViewController.mファイルにIBOutlet接続しておきます。

tableview_navi_06

画像に表示したいファイル名と、ラベルに表示したい文字列をDetailViewController.hファイルで記述します。

#import <UIKit/UIKit.h>

@interface DetailViewController : UIViewController

@property (nonatomic, copy) NSString *imageName;
@property (nonatomic, copy) NSString *labelDeviceName;

@end

これが、画面遷移時に詳細画面へ渡したいデータのアクセサとなります。

プロジェクトナビゲータからViewController.mを選択します。こちらに画面遷移の処理を追加していきましょう。

まずは先ほど追加したクラスをインポートします。

#import "DetailViewController.h"

セルがタップされた時に詳細画面に遷移したいので、セルがタップされた時のデリゲートメソッドを記述しましょう。

174行目からがUITableViewDelegateのメソッド記述しているところなので、この下に追記していきます。

/**
 *  テーブルのセルがタップされた時に呼ばれます。
 *
 *  @param tableView テーブルビュー
 *  @param indexPath セクション番号・行番号の組み合わせ
 */
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

}

セルが選択された時に呼び出されるデリゲードメソッドです。

選択したセルを保持したいので、40行目以降にプロパティを追加します。

@property (nonatomic, strong) NSIndexPath *selectedIndexPath;

選択した行数のデバイス名称と、選択されたセルの画像がiOSかAndroidかを詳細画面に渡してあげます。次のようにします。

※追記:コメントを頂いて修正したところをハイライトしました。

#pragma mark - UITableViewDelegate methods

/**
 *  テーブルのセルがタップされた時に呼ばれます。
 *
 *  @param tableView テーブルビュー
 *  @param indexPath セクション番号・行番号の組み合わせ
 */
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    _selectedIndexPath = indexPath;
    // セグエを呼び出して画面遷移します。
    [self performSegueWithIdentifier:@"pushDetailView" sender:self];
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return [CustomTableViewCell rowHeight];
}

#pragma mark - Segue method

/**
 *  セグエでの画面遷移前に呼び出されます。
 *
 *  @param segue  セグエ(pushを選択したやつ)
 *  @param sender 呼び出し元のViewController(今回の場合はViewController)
 */
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // セグエが複数ある場合はこちらで確認して、分岐します。
    if ([segue.identifier  isEqualToString:@"pushDetailView"]) {
        // 遷移先を取得します
        DetailViewController *detailViewController = segue.destinationViewController;
    
        // 詳細画面に画像名をセット
        detailViewController.imageName = (_selectedIndexPath.section == 0) ? @"ios1-100x100" : @"android-200x200";
        
        if (self.searchDisplayController.isActive) {
            // 詳細画面に検索したデータソースからデバイス名をセット
            switch (_selectedIndexPath.section) {
                case 0: // iOS
                    detailViewController.labelDeviceName = _dataSourceSearchResultsiPhone[_selectedIndexPath.row];
                    break;
                case 1: // Android
                    detailViewController.labelDeviceName = _dataSourceSearchResultsAndroid[_selectedIndexPath.row];
                    break;
                default:
                    break;
            }
        } else {
            // 詳細画面にデータソースからデバイス名をセット
            switch (_selectedIndexPath.section) {
                case 0: // iOS
                    detailViewController.labelDeviceName = _dataSourceiPhone[_selectedIndexPath.row];
                    break;
                case 1: // Android
                    detailViewController.labelDeviceName = _dataSourceAndroid[_selectedIndexPath.row];
                    break;
                default:
                    break;
            }
        }
    }
}

あとは遷移先のViewControllerで受け取った情報をセットすれば完了です。

DetailViewController.mを編集します。

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.detailImage.image = [UIImage imageNamed:_imageName];
    self.detailName.text = _labelDeviceName;
}

実行してみます。

遷移元

tableview_navi_07

遷移先

tableview_navi_08

ナビゲーションバーが上部に表示されて、遷移元のレイアウトが崩れましたが、もろもろ修正したものをgithubへあげておきましたので、気になる方はご覧ください。

セルがタップされた時にセルのグレー表示が消えるようにする処理も追加しました。

まとめ

ここまで4回にわたってUITableViewの使い方を学んできました。

iOSのアプリを作るときにUITableViewを使わないことはないといっても過言ではありません。

よく使う知識をしっかりと抑えることで、4月からiOS開発を始めた方は一歩先へ進めるでしょう。

私自身もそうなりたいです。

この記事から見始めた方は是非1回目からご覧頂けると幸いです。

  • akuraru

    1. 画面間のデータの受け渡しのさいですが、`performSegueWithIdentifier:sender:`を呼び指す際、`sender`にオブジェクトを渡すと`prepareForSegue:sender:`の`sender`としてオブジェクトを受け取ることができます。

    2. `prepareForSegue:sender:`を実装する際、必ず`segue.identifier`を調べたほうが良いと思います。大抵の場合遷移先は複数あるので切り分ける必要があります。

    • Yasuhisa Arakawa

      akuraruさん
      コメントありがとうございます!
      今回は1画面しかなかったのでdestinationViewControllerを確認もせずに代入しましたが、確かに仰る通りですね。
      早速記事の方に追記させて頂きます。