Start Developing iOS Apps Today(11)~チュートリアル:データの追加

2014. 03. 31
●チュートリアル:データの追加

このチュートリアルは、2つ目のチュートリアル(チュートリアル:ストーリーボード)で作成したプロジェクトに基づいています。
ToDoListアプリに動的データのサポートを追加するために、これまで学んだデザインパターンの使用や、Foundationとの連携、カスタムクラスの記述を使用します。

このチュートリアルでは以下の方法を教えます。

  • 一般的なFoundationクラスとの連携
  • カスタムデータクラスの作成
  • デリゲートとデータソースプロトコルの実装
  • ビューコントローラ間でデータを渡す

このチュートリアルの全ての手順を完了すると、次のような見た目のアプリになります。

ios_simulator_add_new_item_2x.png  ios_simulator_full_list_checked_2x.png



●データクラスの作成

開始するにはXcodeで既存のプロジェクトを開きます。

この時点で、ストーリーボードを使用したToDoListアプリのためのインターフェイスとナビゲーションスキームを持っています。
ここではデータストレージとモデルオブジェクトとの動作を追加します。

アプリの目標はToDo項目のリストを作成することなので、最初に個々のToDo項目を表すためのカスタムクラスXYZToDoItemを作成します。
ご存知のように、XYZToDoItemクラスは「カスタムクラスの記述」で説明しました。


XYZToDoItemクラスの作成

  1. File > New > Fileを選択します(またはCommand + Nを押します)。

    新しいファイルのテンプレートの選択を促すダイアログが表示されます。

  2. 左側でiOSの下のCocoa Touchを選択します。

  3. Objective-C classを選択し、Nextをクリックします。

  4. Class欄で、XYZ接頭辞の後にToDoItemと入力します。

  5. "Subclass of"のポップアップメニューからNSObjectを選択します。

    正確にチュートリアルを追ってきた場合、クラスのタイトルはおそらくこの前工程のXYZToDoItemViewControllerとなっています。
    "Subclass of"でNSObjectを選択すると、Xcodeは通常のカスタムクラスを作成すると知り、以前に追加していたViewControllerテキストを削除します。

  6. Nextをクリックします。

  7. 保存場所はデフォルトでプロジェクトのディレクトリになっています。
    そのままにしておきます。

  8. Groupオプションはデフォルトでアプリ名のToDoListになっています。
    そのままにしておきます。

  9. Targetsセクションはデフォルトでアプリが選択、アプリのテストは非選択になっています。
    これは完璧なので、そのままにしておきます。

  10. Createをクリックします。

XYZToDoItemクラスは簡単に実装できます。
名前、作成日時、項目が完了したかどうかのプロパティを持ちます。
この後、XYZToDoItemクラスのインターフェイスでこれらのプロパティを追加します。


XYZToDoItemクラスの構成

  1. プロジェクトナビゲータでXYZToDoItem.hを選択します。

  2. 以下のようにインターフェイスにプロパティを追加します。

    @interface XYZToDoItem : NSObject

    @property NSString *itemName;
    @property BOOL completed;
    @property (readonly) NSDate *creationDate;

    @end

チェックポイント:Product > Buildを選択して(またはCommand + Bを押して)プロジェクトをビルドします。
貴方はまだ新しいクラスで何も使用していませんが、ビルドするとコンパイラが何らかの入力ミスを行っていないかを確認する機会を与えてくれます。
コンパイラが提供する警告やエラーを通して読むことによってそれらを修正し、全てがここで説明している方法に見えるか確認するために、このチュートリアルの手順を振り返ってください。



●データの読み込み

これで個々のリスト項目のデータを作成して格納することができるクラスができました。
また、それらの項目のリストを保持する必要があります。
これを追跡するための自然な場所は、(モデルとビュー間の調整を担当して、モデルを参照する必要がある)XYZToDoListViewControllerクラスです。

Foundationフレームワークには、項目のリストの追跡に適したクラスNSMutableArrayが含まれています。
ユーザが配列に項目を追加するために、可変配列を使用することが重要です。
不変バージョンのNSArrayでは、初期化した後に項目を追加することはできません。

配列を使用するには、それを宣言して作成することの両方が必要です。
配列の割り当てと初期化をすることによって、これを行います。


配列の割り当てと初期化

  1. プロジェクトナビゲータでXYZToDoListViewController.mを選択します。

    項目の配列はテーブルビューコントローラの実装の詳細なので、.hファイルの代わりに.mファイルに宣言します。
    これはカスタムクラスでのプライベートになります。

  2. Xcodeがカスタムテーブルビューコントローラクラス内に作成したインターフェイスの範疇に、以下のプロパティを追加します。
    宣言は次のようになります。

    @interface XYZToDoListViewController ()

    @property NSMutableArray *toDoItems;

    @end

  3. viewDidLoadメソッドでtoDoItems配列の割り当てと初期化を行います。

    - (void)viewDidLoad
    {
        [super viewDidLoad];
        self.toDoItems = [[NSMutableArray alloc] init];
    }

viewDidLoadの実際のコードでは、(XYZListViewControllerを作成した時にXcodeによって挿入された)コメントアウトされているいくつかの追加の行が挿入されています。
気にせずにそのままにしておきます。

この時点で、項目を追加することができる配列を持っています。
これを行うのは別個のメソッドloadInitialDataで、viewDidLoadから呼び出します。
このコードはモジューラタスクなため独自のメソッドで行い、別個のメソッドを作成することによってコードの可読性を向上させることができます。
実際のアプリでは、このメソッドはファイルなどある種の永続ストアからデータを読み込みます。
今のところ、目標はテーブルビューがカスタムデータ項目でどのように動作するかを確認することなので、実験のためにいくつかのテストデータを作成します。

配列を作成した方法(割り当てと初期化)で項目を作成します。
その後、項目に名前を付けます。
これはテーブルビューに表示される名前です。
いくつかの項目に対してこれを行います。


初期データの読み込み

  1. @implementation行の下に、新しいメソッドloadInitialDataを追加します。

    - (void)loadInitialData {
    }

  2. このメソッド内でいくつかのリスト項目を作成し、配列に追加します。

    - (void)loadInitialData {
        XYZToDoItem *item1 = [[XYZToDoItem alloc] init];
        item1.itemName = @"Buy milk";
        [self.toDoItems addObject:item1];
        XYZToDoItem *item2 = [[XYZToDoItem alloc] init];
        item2.itemName = @"Buy eggs";
        [self.toDoItems addObject:item2];
        XYZToDoItem *item3 = [[XYZToDoItem alloc] init];
        item3.itemName = @"Read a book";
        [self.toDoItems addObject:item3];
    }

  3. viewDidLoadメソッド内でloadInitialDataを呼び出します。

    - (void)viewDidLoad
    {
        [super viewDidLoad];
        self.toDoItems = [[NSMutableArray alloc] init];
        [self loadInitialData];
    }

チェックポイント:Product > Buildを選択してプロジェクトをビルドします。
loadInitialDataメソッドの行で多数のエラーが表示されるはずです。
うまく行かない鍵は最初の行で、“Use of undeclared identifier XYZToDoItem”を言う必要があるからです。
これはXYZToDoListViewControllerをコンパイルする時に、コンパイラがXYZToDoItemについて知らないことを意味します。
コンパイラは非常に特殊であり、何に注意を払うかを明示的に指示する必要があります。


カスタムリスト項目クラスに注意を払うようにコンパイラに指示

  1. XYZToDoListViewController.mの先頭近くの行にある、#import "XYZToDoListViewController.h"を見つけます。

  2. すぐ下に次の行を追加します。

    #import "XYZToDoItem.h"

チェックポイント:Product > Buildを選択してプロジェクトをビルドします。
ここではエラー無しでビルドする必要があります。



●データの表示

この時点で、テーブルビューは
いくつかのサンプルToDo項目が事前に入っている可変配列を持っています。
ここでテーブルビューにデータを表示する必要があります。

XYZToDoListViewControllerでテーブルビューのデータソースを作成することによって、これを行います。
何らかのテーブルビューのデータソースを作成するには、UITableViewDataSourceプロトコルを実装する必要があります。
実装する必要があるメソッドは、ちょうど2つ目のチュートリアルでコメントアウトしたので、これを反転します。
テーブルビューが機能するようにするには、3つのメソッドが必要です。
1つ目はnumberOfSectionsInTableView:で、表示するセクション数をテーブルビューに伝えます。
このアプリでは、テーブルビューで単一のセクションを表示したいので、実装は簡単です。


テーブルでのセクションの表示

  1. プロジェクトナビゲータでXYZToDoListViewController.mを選択します。

  2. 2つ目のチュートリアルでテーブルビューのデータソースメソッドをコメントアウトした場合は、ここでコメントマーカを削除します。

  3. 以下のようなテンプレートの実装部分を探します。

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
    #warning Potentially incomplete method implementation.
        // Return the number of sections.
        return 0;
    }

    単一のセクションにしたい場合、警告行を削除して、戻り値を0から1に変更します。

  4. 単一セクションを返すため、以下のようにnumberOfSectionsInTableView:データソースメソッドを変更します。

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
    // Return the number of sections.
    return 1;
    }

次のメソッドはtableView:numberOfRowsInSection:で、指定したセクションで表示する行数をテーブルビューに伝えます。
テーブルには単一のセクションが有り、各ToDo項目はテーブルビューに独自の行を持つ必要があります。
つまり行数はtoDoItem配列内のXYZToDoItemオブジェクトの数にする必要があることを意味します。


テーブル内の行数を返す

  1. プロジェクトナビゲータでXYZToDoListViewController.mを選択します。

  2. 以下のようなテンプレートの実装部分を探します。

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        #warning Incomplete method implementation.
        // Return the number of rows in the section.
        return 0;
    }

    貴方はリストの項目数を返すようにしたいでしょう。
    幸いなことに、NSArrayは配列内の項目数を返すcountと呼ばれる便利なメソッドを持っており、行数は[self.toDoItems count]で得られます。

  3. 適切な行数を返すように、tableView:numberOfRowsInSection:データソースメソッドを変更します。

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        // Return the number of rows in the section.
        return [self.toDoItems count];
    }

最後のメソッドはtableView:cellForRowAtIndexPath:で、指定した行に表示するセルを尋ねます。
これまではコードだけで作業してきましたが、行に表示するセルはインターフェイスの大部分を占めます。
幸いなことに、XcodeはInterface Builderでカスタムセルを容易に設計することができます。
最初のタスク、テーブルビューに伝えるために静的コンテンツを使用する代わりに、動的コンテンツでプロトタイプセルを使用するセルを設計します。


テーブルビューの設定

  1. ストーリーボードを開きます。

  2. アウトラインでテーブルビューを選択します。

  3. テーブルビューが選択された状態で、ユーティリティ領域で属性インスペクタinspector_attributes_2x_20140209113430cca.png を開きます。

  4. 属性インスペクタで、テーブルビューのContent属性をStatic CellsからDynamic Prototypesに変更します。

Interface Builderは設定された静的セルを取得し、それらを全てプロトタイプに変換します。
プロトタイプセルはその名が示す通り、設定されたテキスト形式、色、画像、または他の属性を、貴方が望む際に実行時にデータソースからデータを取得して表示するセルです。
データソースは各行のプロトタイプセルを読み込んでから、行のデータを表示するために設定されます。

適切なセルを読み込むにはデータソースが呼び出される物を知っている必要があり、その名前もストーリーボードで設定する必要があります。

貴方はプロトタイプセル名を設定していますが、別のプロパティ(ユーザがタップした時のセルの外観を決定する、セルの選択スタイル)も設定することになります。
ユーザがタップした時にセルがハイライト表示されないようにするためには、セルの選択スタイルをNoneに設定します。
これは(このチュートリアルの後半で実装する機能の)ユーザがToDoリストの項目をタップした時に、完了または未了として印したいセルの動作です。


プロトタイプセルの設定

  1. テーブルで最初のテーブルビューセルを選択します。

  2. 属性インスペクタでIdentifier欄を探し、ListPrototypeCellと入力します。

  3. 属性インスペクタでSelection欄を探し、Noneを選択します。

またプロトタイプセルのフォントや他の属性を変更することもできます。
基本的な設定は容易な作業なので、しておきましょう。

次の手順はtableView:cellForRowAtIndexPath:を実装することによって、指定した行のセルをどのように設定するかをデータソースに教える事です。
このデータソースメソッドは、指定した行を表示したい時にテーブルビューによって呼び出されます。
行が少数のテーブルビューでは、おそらく全ての行が一度に画面に表示されますが、このメソッドはテーブルの各行毎に呼び出されます。
しかし多数の行を含むテーブルビューでは、特定の時点で総項目の極一部のみ表示されます。
テーブルビューにとって表示されている行のセルのみを尋ねるのが最も効率的で、tableView:cellForRowAtIndexPath:はテーブルビューにそれを行うことができます。

テーブル内の任意の行について、toDoItems配列内の対応するエントリを取り出し、項目名にセルのテキストラベルを設定します。


テーブル内のセルの表示

  1. プロジェクトナビゲータでXYZToDoListViewController.mを選択します。

  2. tableView:cellForRowAtIndexPath:データソースメソッドを探します。
    テンプレートの実装は次のようになります。

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        static NSString *CellIdentifier = @"Cell";
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

        // Configure the cell...

        return cell;
    }

    テンプレートではいくつかのタスクを実行します。
    セルの識別子を保持する変数を作成し、その識別子を持つセルのためのテーブルビューを尋ね、セルを構成するためのコードが行く場所についてのコメントが追加され、そしてセルを返しています。

    貴方のアプリ用にこのコードを機能させるには、ストーリーボードで設定するために識別子を変更し、セルを構成するコードを追加する必要があります。

  3. ストーリーボードで設定するために、セル識別子を変更します。
    タイプミスを避けるために、ストーリーボードから実装ファイルにコピー&ペーストします。
    セル識別子の行は次のようになります。

    static NSString *CellIdentifier = @"ListPrototypeCell";

  4. return文の直前に以下のコード行を追加します。

    XYZToDoItem *toDoItem = [self.toDoItems objectAtIndex:indexPath.row];
    cell.textLabel.text = toDoItem.itemName;

貴方のtableView:cellForRowAtIndexPath:メソッドは以下のようになります。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ListPrototypeCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    XYZToDoItem *toDoItem = [self.toDoItems objectAtIndex:indexPath.row];
    cell.textLabel.text = toDoItem.itemName;
    return cell;
}

チェックポイント:アプリを実行します。
loadInitialDataで追加した項目のリストがテーブルビューのセルとして表示される必要があります。



●完了として項目を印す

項目に完了したと印すことができない場合、ToDoリストとしてあまり良くありません。
ここで、そのためのサポートを追加します。
シンプルなインターフェイスは、ユーザがセルをタップした時に完了状態を切り替えられるようにし、その横にチェックマークで完了した項目を表示するようにします。
幸いなことに、テーブルビューには(特にユーザがセルをタップした時に、テーブルビューがデリゲートに通知するといった)このシンプルなインターフェイスを実装するために利用することができる、いくつかの組み込み動作が含まれています。
したがってタスクは、ユーザがテーブル内のToDo項目をタップすることに対応するコードを記述することです。

Xcodeは既にストーリーボードで設定した時に、テーブルビューのデリゲートXYZToDoListViewControllerを作成しています。
貴方はユーザのタップに応答してToDoリストの項目を適切に更新するために、tableView:didSelectRowAtIndexPath:デリゲートメソッドを実装する必要があります。

セルが選択された時に、テーブルビューはどのような選択処理をする必要があるのかを確認するために、tableView:didSelectRowAtIndexPath:デリゲートメソッドを呼び出します。
このメソッドには、ToDo項目の完了状態を更新するためのコードを記述します。


完了または未了として項目を印す

  1. プロジェクトナビゲータでXYZToDoListViewController.mを選択します。

  2. ファイルの最後の@end行の上に、以下の行を追加します。

    #pragma mark - Table view delegate

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {

    }

    2行目はコピー&ペーストをする代わりに、入力してみてください。
    Xcodeの素晴らしい時間節約機能の一つであるコード補完が分かります。
    Xcodeが潜在的な補完のリストを表示する際に、貴方が望む物を見つけてReturnを押すまでスクロールします。
    Xcodeは行全体を挿入します。

  3. タップには応答するが、実際にセルが選択されたままにしたくはありません。
    選択した直後にセルの選択を解除するには以下のコードを追加します。

    [tableView deselectRowAtIndexPath:indexPath animated:NO];

  4. toDoItems配列内で対応するXYZToDoItemを検索します。

    XYZToDoItem *tappedItem = [self.toDoItems objectAtIndex:indexPath.row];

  5. タップされた項目の完了状態を切り替えます。

    tappedItem.completed = !tappedItem.completed;

  6. データが更新された行を再読み込みするためにテーブルビューに伝えます。

    [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];

tableView:didSelectRowAtIndexPath:メソッドは次のようになります。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:NO];
    XYZToDoItem *tappedItem = [self.toDoItems objectAtIndex:indexPath.row];
    tappedItem.completed = !tappedItem.completed;
    [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}

チェックポイント:アプリを実行します。
loadInitialDataに追加された項目のリストがテーブルビューのセルとして表示されます。
しかし項目をタップしても何も起きていないように見えます。
何故でしょう?

その理由は、項目の完了状態を表示するためのテーブルビューセルを構成していないからです。
方法はtableView:cellForRowAtIndexPath:メソッドに戻り、項目が完了した時にインジケータを表示するようにセルを構成する必要があります。

項目が完了したことを示す一つの方法は、横にチェックマークを置くことです。
幸いなことに、テーブルビューセルは右側にセルアクセサリを持つことができます。
デフォルトではアクセサリは何もありませんが、様々なアクセサリを表示するためにセルを変更することができ、その一つにはチェックマークもあります。
貴方はToDo項目の完了状態に基づいて、セルアクセサリを設定する必要があります。


項目の完了状態を表示

  1. tableView:cellForRowAtIndexPath:メソッドを表示します。

  2. セルのテキストラベルを設定する行の直後に、以下のコードを追加します。

    if (toDoItem.completed) {
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
    } else {
        cell.accessoryType = UITableViewCellAccessoryNone;
    }

tableView:cellForRowAtIndexPath:メソッドは次のようになります。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"ListPrototypeCell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    XYZToDoItem *toDoItem = [self.toDoItems objectAtIndex:indexPath.row];
    cell.textLabel.text = toDoItem.itemName;
    if (toDoItem.completed) {
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
    } else {
        cell.accessoryType = UITableViewCellAccessoryNone;
    }
    return cell;
}

チェックポイント:アプリを実行します。
loadInitialDataで追加した項目のリストがテーブルビューのセルとして表示されます。
項目をタップするとチェックマークがその横に表示されます。
同じ項目を再度タップした場合、チェックマークが消えます。



●新しい項目の追加

ToDoリストアプリの機能を作成するための最後の手順は、項目を追加する機能を実装することです。
ユーザがXYZAddToDoItemViewControllerのシーン上のテキストフィールドで項目名を入力してDoneボタンをタップした時に、ビューコントローラは新しいリスト項目を作成して渡し、ToDoリストで表示するためにXYZToDoListViewControllerに戻るようにしたいでしょう。

まず、設定するためのリスト項目を持つ必要があります。
テーブルビューと同様に、ビューコントローラはモデルへのインターフェイスを接続するための論理的な場所です。
XYZAddToDoItemViewControllerに新しいToDo項目を保持するためのプロパティを与えます。


XYZAddToDoItemViewControllerクラスにXYZToDoItemを追加する

  1. プロジェクトナビゲータでXYZAddToDoItemViewController.hを選択します。

    後でテーブルビューコントローラからリスト項目にアクセスする必要があるため、パブリックプロパティとして作成することが重要です。
    これが実装ファイルXYZAddToDoItemViewController.mではなく、インターフェイスファイルXYZAddToDoItemViewController.hで宣言する理由です。

  2. XYZAddToDoItemViewController.hで、@interface行の上にXYZToDoItem.hのインポート宣言を追加します。

    #import "XYZToDoItem.h"

  3. インターフェイスにtoDoItemプロパティを追加します。

    @interface XYZAddToDoItemViewController : UIViewController

    @property XYZToDoItem *toDoItem;

    @end

新しい項目の名前を取得するには、ビューコントローラがユーザが名前を入力するテキストフィールドのコンテンツへのアクセスが必要になります。
これを行うには、ストーリーボードでXYZAddToDoItemViewControllerクラスからテキストフィールドへの接続を作成します。


ビューコントローラへのテキストフィールドの接続

  1. アウトラインビューでXYZAddToDoItemViewControllerオブジェクトを選択します。

  2. アシスタントエディタを開くために、ウィンドウのツールバーの右上にあるAssistantボタンをクリックします。

    assistant_editor_2x.png

    エディタの右側にXYZAddToDoItemViewController.mが表示されます。
    表示されなかった場合、右側のエディタでファイル名をクリックしてXYZaddToDoItemViewController.mを選択してください。

    アシスタントエディタは一度に2つのファイルを開くことができ、インターフェイスのオブジェクトとソースファイルのプロパティを接続するなど、それらの間の操作を実行することができます。

  3. ストーリーボードでテキストフィールドを選択します。

  4. キャンバス上のテキストフィールドからエディタの右側に表示されているコードにControl+ドラッグをし、XYZAddToDoItemViewController.mの@interface行の下の行でドラッドを止めます。

    assistant_editor_drag_2x.png

  5. 表示されるダイアログで、Name欄にtextFieldと入力します。

    残りのオプションはそのままにしておきます。
    ダイアログは次のようになります。

    configure_text_field_outlet_2x.png

  6. Connectをクリックします。

    Xcodeはテキストフィールドへのポインタを格納するためにXYZAddToDoItemViewController.mに必要なコードを追加し、その接続を設定するためにストーリーボードを構成します。

更に項目を作成する時に知っておく必要があることがあります。
貴方はDoneボタンがタップされた場合のみ、項目を作成したいでしょう。
これを行うには、アウトレットとしてDoneボタンを追加します。


ビューコントローラにDoneボタンを接続

  1. ストーリーボードでアシスタントエディタを開き、ウィンドウの右端にXYZAddToDoItemViewController.mを設定します。

  2. ストーリーボードでDoneボタンを選択します。

  3. キャンバス上のDoneボタンからエディタの右側に表示されているコードにControl+ドラッグをし、XYZAddToDoItemViewController.mのtextFieldプロパティの下の行でドラッドを止めます。

  4. 表示されるダイアログで、Name欄にdoneButtonと入力します。

    残りのオプションはそのままにしておきます。
    ダイアログは次のようになります。

    configure_done_button_outlet_2x.png

  5. Connectをクリックします。

これでDoneボタンを識別するための方法を持ちました。
Doneボタンをタップすると項目が作成されるので、貴方はこれが発生すると知っている必要があります。

ユーザがDoneボタンをタップすると、(2つ目のチュートリアルで構成したインターフェイスの)ToDoリストに戻るアンワインドセグエが始動します。
セグエが実行される前に、システムはprepareForSegue:を呼び出すことによって、関連するビューコントローラを準備する機会を与えます。
これはユーザがDoneボタンをタップしたかどうかを厳密に確認するためのもので、そうであれば新しいToDo項目を作成します。
いずれかのボタンをタップした場所を確認し、それがDoneボタンであった場合に項目を作成することができます。


Doneボタンをタップした後に項目を作成

  1. プロジェクトナビゲータでXYZAddToDoItemViewController.mを選択します。

  2. @imprementation行の下にprepareForSegue:メソッドを追加します。

    - (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
    }

  3. このメソッドでは、Doneボタンがタップされたかどうかを確認します。
    そうでない場合は項目を保存する代わりに、メソッドは何も実行せずに返すようにします。

    if (sender != self.doneButton) return;

  4. テキストフィールドにテキストがあるかどうかを確認します。

    if (self.textField.text.length > 0) {
    }

  5. テキストがある場合、新しい項目を作成してテキストフィールドのテキストに名前を付けます。
    また、完了状態がNOに設定されていることを確実にします。

    self.toDoItem = [[XYZToDoItem alloc] init];
    self.toDoItem.itemName = self.textField.text;
    self.toDoItem.completed = NO;

    テキストが無い場合、項目を保存したくないため何もしません。

prepareForSegue:メソッドは以下のようになります。

- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if (sender != self.doneButton) return;
    if (self.textField.text.length > 0) {
        self.toDoItem = [[XYZToDoItem alloc] init];
        self.toDoItem.itemName = self.textField.text;
        self.toDoItem.completed = NO;
    }
}

これで新しい項目を作成できるようになったので、ToDoリストに項目を追加できるように、XYZToDoListViewControllerに戻って項目を渡す必要があります。
これを実現するには、2つ目のチュートリアルで記述したunwindToList:メソッドを再考する必要があります。
このメソッドは、ユーザがCancelまたはDoneボタンのどちらかをタップした時に生ずる、XYZAddToDoItemViewControllerのシーンが閉じる際に呼び出されます。

unwindToList:メソッドは、アンワインドセグエのターゲットとして使用されている全てのメソッドのように、パラメータとしてセグエを取ります。
セグエパラメータは、XYZAddToDoItemViewControllerからXYZToDoListViewControllerに巻き戻るセグエです。
セグエは2つのビューコントローラ間の遷移なので、ソースビューコントローラ(XYZAddToDoItemViewController)を認識しています。
ソースビューコントローラのセグエオブジェクトを尋ねることによって、unwindToList:メソッドのソースビューコントローラに格納されているデータにアクセスすることができます。
この場合はtoDoItemへのアクセスです。
(テキストフィールドがテキストを持っていない、あるいはユーザがCancelボタンをタップして)nilの場合、項目は作成されません。
toDoItemの値がある場合、項目を取得してtoDoItems配列に追加し、テーブルビューのデータを再読み込みすることによってToDoリストに表示します。


新しい項目の格納と表示

  1. プロジェクトナビゲータでXYZToDoListViewController.mを選択します。

  2. XYZToDoListViewController.mで、@interface行の上にXYZAddToDoItemViewController.hのインポート宣言を追加します。

    #import "XYZAddToDoItemViewController.h"

  3. 2つ目のチュートリアルで追加したunwindToList:メソッドを探します。

  4. このメソッドで、XYZAddToDoItemViewControllerからアンワインドするコントローラであるソースビューコントローラを取得します。

    XYZAddToDoItemViewController *source = [segue sourceViewController];

  5. コントローラのToDo項目を取得します。

    XYZToDoItem *item = source.toDoItem;

    これはDoneボタンがタップされた時に作成された項目です。

  6. 項目が存在するかどうかを確認します。

    if (item != nil) {
    }

    nilの場合はCancelボタンで画面を閉じたか、またはテキストフィールドがテキストを持っていなかったかのどちらかで、項目を保存しません。

    存在する場合、toDoItems配列に項目を追加します。

    [self.toDoItems addObject:item];

  7. テーブルのデータを再読み込みします。

    テーブルビューはデータの追跡を保持しないため、表示するための新しいデータがある時にテーブルビューに通知するのはデータソース(この場合、テーブルビューコントローラ)の責任です。

    [self.tableView reloadData];

unwindToList:メソッドは次のようになります。

- (IBAction)unwindToList:(UIStoryboardSegue *)segue
{
    XYZAddToDoItemViewController *source = [segue sourceViewController];
    XYZToDoItem *item = source.toDoItem;
    if (item != nil) {
        [self.toDoItems addObject:item];
        [self.tableView reloadData];
    }
}

チェックポイント:アプリを実行します。
今では追加ボタン(+)をクリックして新しい項目を作成すると、ToDoリストに表示される必要があります。
おめでとう!
ユーザからの入力を受け取ってオブジェクトに格納し、2つのビューコントローラ間でオブジェクトを渡すアプリを作成できました。
これはストーリーボードベースのアプリでシーン間のデータ移動の基礎になっています。



●要約

貴方はiOSアプリ開発の入門ツアーをほとんど終えました。
最後の章ではドキュメントに関して貴方のやり方を検索する方法についてより多くの情報を与え、より高度なアプリを作成する方法を学ぶ上で採用する可能性のあるいくつかの次なる手順を提案しています。



参考文献

Apple/Start Developing iOS Apps Today

0 CommentsPosted in 資料

Start Developing iOS Apps Today(10)~カスタムクラスの記述

2014. 03. 24
●カスタムクラスの記述

iOSアプリを開発する際、独自のカスタムクラスを記述する必要がある場合には、多くの要因があることを知るでしょう。
カスタムクラスは、データと一緒にカスタム動作をパッケージ化する必要がある場合に有益です。
カスタムクラスでは、データの保存、操作、そして表示するための独自の動作を定義することができます。

例えば、iOSのClockアプリでのWorld Clockタブを考えてみます。
このテーブルビューのセルは、標準のテーブルビューセルよりも多くのコンテンツを表示する必要があります。
これは指定したテーブルビューセル用に追加のカスタムデータを表示できるように、UITableViewCellの動作を拡張するサブクラスを実装するための良い機会です。
このカスタムクラスを設計していた場合、時差情報を表示するためのラベルのアウトレットと、セルの右側にカスタム時計を表示するためのイメージビューを追加するでしょう。

custom_class_2x.png

この章ではToDoListアプリの動作の実装を完了するため、Objective-Cの構文とクラスの構造について知っておくべきことを教えます。
すなわちToDoリスト上の単一項目を表すカスタムクラスであるXYZToDoItemの設計について説明します。
3つ目のチュートリアルでは、実際にこのクラスを実装してアプリに追加します。



●クラスの宣言と実装

Objective-Cのクラスの仕様では、インターフェイスと実装の2つの別個な部品を必要とします。
インターフェイスは、オブジェクトの型を他のオブジェクトによって使用されることを意図して、正確に指定します。
言い換えると、クラスのインスタンスと外界との間のパブリックインターフェイスを定義します。
実装は、インターフェイスで宣言された各メソッドの実行可能なコードが含まれています。

オブジェクトは内部実装の詳細を隠すように設計する必要があります。
Objective-Cではインターフェイスのみを公開する必要があるため、通常はインターフェイスと実装を別々のファイルに置きます。
Cコードと同様に、コードの実装の詳細からパブリック宣言を分離するため、ヘッダファイルとソースファイルを定義します。
インターフェイスファイルは.h拡張子を持ち、実装ファイルは.m拡張子を持っています。
(この後「チュートリアル:データの追加」で部品の全てに導入されるように、今のXYZToDoItemクラス用にこれらのファイルを作成します。)


インターフェイス

Objective-Cでクラスのインターフェイスを宣言する構文は、以下のようになります。

@interface XYZToDoItem : NSObject

@end

この例では、NSObjectから継承したXYZToDoItemという名前のクラスを宣言しています。

パブリックなプロパティと動作は@interface宣言の内側で定義されています。
この例では、スーパークラス以降に何も指定されていないので、XYZToDoItemのインスタンスで利用可能と想定される動作のみがNSObjectから継承された動作になります。
全てのオブジェクトは最小限の動作を有する事を求められるため、デフォルドではNSObject(またはそのサブクラスの一つ)から継承する必要があります。


実装

Objective-Cでクラスの実装を宣言する構文は、以下のようになります。

#import "XYZToDoItem.h"

@implementation XYZToDoItem

@end

クラスのインターフェイスで何らかのメソッドを宣言する場合、このファイル内で実装する必要があります。



●プロパティはオブジェクトのデータを格納する

保持する必要のあるToDo項目の情報を検討します。
おそらくそれを作成した時の名前や、それが完了したかどうかが必要となります。
カスタムXYZToDoItemクラスでは、プロパティにこの情報を格納します。

これらのプロパティの宣言は、インターフェイスファイル(XYZToDoItem.h)内に存在します。
ここでは以下のようにします。

@interface XYZToDoItem : NSObject

@property NSString *itemName;
@property BOOL completed;
@property NSDate *creationDate;

@end

この例では、XYZToDoItemクラスは3つのパブリックプロパティを宣言しています。
これらのプロパティは、完全なパブリックアクセスのために用意されています。
パブリックアクセスでは、他のオブジェクトがプロパティ値の読み込みと変更の両方を行う事ができます。

貴方はプロパティが変更されてはならない(つまり読み込みのみ)と宣言を決定する事ができます。
プロパティが(特に)読み込みのみであることを意図しているかどうかを示すために、Objective-Cのプロパティ宣言にはプロパティ属性が含まれています。
例えばXYZToDoItemの作成日を変更可能にしたくない場合は、以下のようにXYZToDoItemクラスのインターフェイスを更新します。

@interface XYZToDoItem : NSObject

@property NSString *itemName;
@property BOOL completed;
@property (readonly) NSDate *creationDate;

@end

プロパティはプライベートまたはパブリックにすることができます。
時には他のクラスから参照やアクセスができないように、プロパティをプライベートにした方が良いこともあります。
例えば完了したと印された項目の日付を表すプロパティの追跡を、他のクラスにこの情報へのアクセスを与えずに維持したい場合、実装ファイル(XYZToDoItem.m)の先頭にクラス拡張でそれを置く事によってプロパティをプライベートにすることができます。

#import "XYZToDoItem.h"

@interface XYZToDoItem ()
@property NSDate *completionDate;
@end

@implementation XYZToDoItem

@end

プロパティへのアクセスにはゲッタセッタを使用します。
ゲッタはプロパティの値を返し、セッタはそれを変更します。
ゲッタとセッタにアクセスするための一般的な構文的短縮形はドット表記です。
読み込みと書き込みのアクセスができるプロパティは、プロパティ値の取得と設定の両方にドット表記を使用することができます。
クラスXYZToDoItemのオブジェクトtoDoItemがある場合、以下の操作を行うことができます。

toDoItem.itemName = @"Buy milk";    // itemNameの値の設定
NSString *selectedItemName = toDoItem.itemName;    // itemNameの値の取得



●オブジェクトの動作をメソッドで定義

メソッドは、オブジェクトが何ができるかを定義します。
メソッドは、クラス内のタスクまたはサブルーチンを実行するために定義するコードの一部です。
メソッドはクラス内に格納されているデータにアクセスすることができ、何らかの操作を実行するためにその情報を使用することができます。

例えば、ToDo項目(XYZToDoItem)に完了したと印されたものを取得する機能を提供するには、クラスインターフェイスにmarkAsCompletedメソッドを追加することができます。
クラスの実装でこのメソッドの動作を実装する方法については、後ほど「メソッドの実装」で説明します。

@interface XYZToDoItem : NSObject

@property NSString *itemName;
@property BOOL completed;
@property (readonly) NSDate *creationDate;
- (void)markAsCompleted;

@end

メソッド名の前にあるマイナス記号(-)はインスタンスメソッドであることを示し、そのクラスのオブジェクト上で呼び出すことができます。
このマイナス記号は、プラス記号(+)で示されるクラスメソッドと区別するものです。
クラスメソッドはクラス自体で呼び出すことができます。
クラスメソッドの一般的な例は、「Foundationでの処理」で学んだクラスファクトリメソッドです。
またクラスに関連付けられた共有情報のいくつかの部分にアクセスするために、クラスメソッドを使用することができます。

voidキーワードはメソッドが値を返さないことを示すために、宣言の文頭で丸括弧内で使用されます。
この場合、markAsCompletedメソッドはパラメータを取り込みません。
パラメータは「メソッドのパラメータ」で詳細を説明します。



メソッドのパラメータ

メソッドを呼び出す時に、情報のいくつかの部品に渡すためのパラメータと共にメソッドを宣言します。

例えば、項目が完了または未了と印されているかどうかを決定する単一のパラメータを取得するために、以前のコードの断片からmarkAsCompletedメソッドを修正することができます。
以下のように項目を完了済みとしてのみ設定していた代わりに、完了状態を切り替えることができます。

@interface XYZToDoItem : NSObject

@property NSString *itemName;
@property BOOL completed;
@property (readonly) NSDate *creationDate;
- (void)markAsCompleted:(BOOL)isComplete;

@end

ここでメソッドはisCompleteとというBOOL型のパラメータを一つ取ります。

名前でパラメータを指定してメソッドを参照する時は、メソッド名の一部としてコロンを含めるため、更新されたメソッド名は現在markAsCompleted:となっています。
メソッドが複数のパラメータを持っている場合、メソッド名は分割されてパラメータ名が点在します。
このメソッドに別のパラメータを追加したい場合は、次のように宣言します。

- (void)markAsCompleted:(BOOL)isComplete onDate:(NSDate *)date;

ここではメソッド名をmarkAsCompleted:onDate:と記述しています。
isCompleteとdateという名前は、これらの名前が変数であるとした場合、メソッドが呼び出された時に提供された値にアクセスするために実装で使用されています。



メソッドの実装

メソッドの実装は、関連するコードを含めるために中括弧を使用します。
メソッド名はインターフェイスファイル内で対応するものと同一である必要があり、パラメータや戻り値の型も厳密に一致する必要があります。

XYZToDoItemクラスインターフェイスに追加される、markAsCompleted:メソッドの単純な実装は以下の通りです。

@implementation XYZToDoItem
- (void)markAsCompleted:(BOOL)isComplete {
    self.completed = isComplete;
}
@end

プロパティと同様に、メソッドもプライベートまたはパブリックにすることができます。
パブリックメソッドはパブリックインターフェイスで宣言され、他のオブジェクトから見たり呼び出したりすることができます。
これらに対応する実装は実装ファイルに在り、他のオブジェクトから見ることはできません。
プライベートメソッドはクラス内部にのみ実装を持ち、これはクラスの実装の内部でのみ呼び出して利用することができることを意味します。
これは他のオブジェクトからのアクセスを排除し、クラスの内部動作を追加するための強力なメカニズムです。

例えば、更新されたToDo項目のcompletionDateを維持したいとします。
ToDo項目が完了したと印されていた場合、completionDateを現在に日時に設定します。
未了であると印されていた場合、まだ完了していないのでcompletionDateをnilに設定します。

ToDo項目のcompletionDateの更新は自己完結型のタスクであるため、ベストプラクティスは独自のメソッドを記述することです。
ただし他のオブジェクトがこのメソッド呼び出すことできないことを確認することが重要で、そうしないと他のオブジェクトがいつでもToDo項目のcompletionDateを設定することができてしまいます。
このような理由から、このメソッドをプライベートにします。

それでは完了または未了と印されたことを取得する毎に、ToDo項目のcompletionDateを更新するために内部でmarkAsCompleted:を呼び出す、プライベートメソッドのsetCompletionDateを含めるためにXYZToDoItemの実装を更新します。
他のオブジェクトにこのメソッドを見せたくないので、インターフェイスファイルには何も追加しないことに注意してください。

@implementation XYZToDoItem

- (void)markAsCompleted:(BOOL)isComplete {
    self.completed = isComplete;
    [self setCompletionDate];
}

- (void)setCompletionDate {
    if (self.completed) {
        self.completionDate = [NSDate date];
    } else {
        self.completionDate = nil;
    }
}

@end

ここで、XYZToDoItemクラスを使用してToDoリスト項目の基本的な表現を定義しました。
XYZToDoItemはプロパティの形式で自身についての情報(名前、作成日時、完了状態)を格納し、メソッドを使用して何ができるか(完了または未了との印の取得)を定義します。
これは次のチュートリアルでToDoListアプリの実装を完了するために必要な機能の範囲です。
ただし貴方は常にアプリに新しい動作を統合するために、クラスに独自のプロパティやメソッドを追加して試すことができます。



参考文献

Apple/Start Developing iOS Apps Today

0 CommentsPosted in 資料

Start Developing iOS Apps Today(9)~Foundationでの処理

2014. 03. 17
●Foundationでの処理

貴方がアプリのコードの記述を始める際に、利用することができる多くのObjective-Cフレームワークを見つけることができます。
特に重要なのは、全てのアプリのための基本的なサービスを提供するFoundationフレームワークです。
Foundationフレームワークは、文字列や数値など基本的なデータ型を表す値クラスだけでなく、他のオブジェクトを格納するためのコレクションクラスが含まれています。
ToDoListアプリの多くのコードを記述する中で、値とコレクションクラスに依存することになります。

foundation_2x.png



●値オブジェクト

Foundationフレームワークは文字列、バイナリデータ、日付と時刻、数値、およびその他の値の値オブジェクトを生成するクラスを提供します。

値オブジェクトは(Cデータ型の)プリミティブ値カプセル化したオブジェクトで、値と関連するサービスを提供します。
貴方はアプリが呼び出すメソッドや関数のパラメータや戻り値として、値オブジェクトに頻繁に遭遇します。
フレームワークの様々な部品(または別のフレームワーク)は値オブジェクトを渡すことによってデータを交換することができます。

Foundationフレームワークの値オブジェクトの例をいくつか示します。

  • NSStringとNSMutableString
  • NSDataとNSMutableData
  • NSDate
  • NSNumber
  • NSValue

値オブジェクトはスカラー値を表すため、コレクション内でオブジェクトが要求する場所の指定に使用することもできます。
値オブジェクトはカプセル化したプリミティブ型以上の利点があります。
簡単かつ効率的にカプセル化された値で特定の操作を実行してみましょう。
例えばNSStringクラスは、ファイルまたは(できれば)URLの文字列を記述し、ファイルシステムパスを構築する、部分文字列を検索して置換するためのメソッドを持っています。

プリミティブ型のデータから(その後、おそらくメソッドのパラメータに渡す)値オブジェクトを作成します。
コードでは、後でオブジェクトからカプセル化したデータにアクセスします。
NSNumberクラスは、このアプローチの最も明確な例を提供しています。

int n = 5;    // プリミティブ型に値を割り当てる
NSNumber *numberObject = [NSNumber numberWithInt:n];    // プリミティブ型から値オブジェクトを作成
int y = [numberObject intValue];    // 値オブジェクトからカプセル化した値を取得(y == n)

ほとんどの値クラスは、イニシャライザとクラスファクトリメソッドの両方を宣言することによって、そのインスタンスを作成します。
(クライアントのための便宜としてクラスによって実装されている)クラスファクトリメソッドは割り当てと初期化を1つの手順に組み合わせたもので、作成したオブジェクトを返します。
例えばNSStringクラスには、クラスの新しいインスタンスを割り当てて初期化し、貴方のコードに返す文字列クラスメソッドが宣言されています。

NSString *string = [NSString string];

値オブジェクトを作成、しカプセル化した値へのアクセスを許可することに加えて、ほとんどの値クラスはオブジェクトの比較などの単純な操作のためのメソッドを提供しています。


・文字列

Objective-CはCと同様に、文字列を指定するための同じ規則をサポートしています。
単一の文字は一重引用符で囲み、文字の文字列は二重引用符で囲みます。
ただし、Objective-Cフレームワークは通常C文字列を使用しません。
代わりにNSStringオブジェクトを使用します。

NSStringクラスは、任意長の文字列を格納するための組み込みメモリ管理や、様々な文字エンコーディング(特にUnicode)のサポート、そして文字列のフォーマットのためのユーティリティなどの利点を提供するため、文字列のオブジェクトラッパーを提供しています。
上記のように文字列は頻繁に使用されるため、Objective-Cは定数値からNSStringオブジェクトを作成するための簡略表記を提供しています。
このNSStringリテラルを使用するには、次の例のように二重引用符で囲んだ文字列の前にアットマーク(@)を置きます。

// 文字列「My String」+改行を作成します。
NSString *myString = @"My String\n";
// フォーマット済み文字列「1 String」を作成します。
NSString *anotherString = [NSString stringWithFormat:@"%d %@", 1, @"String"];
// C文字列からObjective-Cを作成します。
NSString *fromCString = [NSString stringWithCString:"A C string" encoding:NSUTF8StringEncoding];


・数字

Objective-CはNSNumberオブジェクトを作成するための簡略表記を提供しており、オブジェクトを作成するためにイニシャライザまたはクラスファクトリメソッドを呼び出す必要がありません。
単に数値の前にアットマーク(@)を置き、オプションで値の型を示すインジケータを後に付けます。
例えば、整数値やdouble値をカプセル化するNSNumberオブジェクトを作成するには、次のように記述することができます。

NSNumber *myIntValue = @32;
NSNumber *myDoubleValue = @3.22346432;

カプセル化したブール値や文字値の作成にもNSNumberリテラルを使用することができます。

NSNumber *myBoolValue = @YES;
NSNumber *myCharValue = @'V';

表記した値にそれぞれU、L、LL、Fという字を追加することによって、符号無し整数、long整数、long long整数、float値を表すNSNumberオブジェクトを作成することができます。
例えばfloat値をカプセル化したNSNumberオブジェクトを作成するには、次のように記述することができます。

NSNumber *myFloatValue = @3.2F



●コレクションオブジェクト

Objective-Cコードでのほとんどのコレクションオブジェクトは、NSArray、NSSet、そしてNSDictionaryといった基本的なコレクションクラスのいずれかのインスタンスです。
これらのクラスはオブジェクトのグループを管理するために使用されているので、コレクションに追加したい全ての項目はObjective-Cクラスのインスタンスにする必要があります。
スカラー値を追加する必要がある場合、最初にそれを表すのに適切なNSNumberまたはNSValueのインスタンスを作成する必要があります。

コレクションに追加した全てのオブジェクトは、少なくともコレクションが存在している間は存在し続けます。
なぜならコレクションクラスは、その内容の追跡を維持するために強い参照を使用しているからです。
内容の追跡を維持することに加えて、各コレクションクラスは列挙や特定の項目へのアクセス、または特定のオブジェクトがコレクションの一部であるかどうかを調べるなど、特定のタスクを実行することを容易にします。

NSArray、NSSet、そしてNSDictionaryクラスの内容は作成時に設定されます。
これらは経時変化しないため、不変と呼ばれます。
また、それぞれはオブジェクトを自由に追加や削除することができる、可変のサブクラスを持っています。
様々な種類のコレクションは、特有の方法でそれに含まれるオブジェクトを編成します。

  • NSArrayとNSMutableArray
    配列は順序付けされたオブジェクトのコレクションです。
    配列の位置(つまりインデックス)を指定することによってオブジェクトにアクセスします。
    配列の最初の要素はインデックス0(ゼロ)です。

  • NSSetとNSMutableSet
    セットは順序の無いオブジェクトのコレクションを格納しており、各オブジェクトは一度だけ発生します。
    一般的には、セット内のオブジェクトにテストまたはフィルタを適用することによって、セット内のオブジェクトにアクセスします。

  • NSDictionaryとNSMutableDictionary
    辞書はキーと値のペアとしてエントリを格納します。
    キーは一意の識別子で通常は文字列であり、値は格納したいオブジェクトです。
    キーを指定することによって、オブジェクトにアクセスします。


・配列

配列(NSArray)は、オブジェクトの順序付けされたリストを表すために使用されます。
唯一の要件は各項目がObjective-Cオブジェクトであるということで、各オブジェクトが同じクラスのインスタンスである必要はありません。

配列内の順序を維持するために、各要素はゼロベースのインデックスで格納されています。

orderedarrayofobjects.png


配列の作成

この章の前半で説明した値クラスと同様に、クラスファクトリメソッドまたは配列リテラルによって割り当てと初期化をし、配列を作成することができます。
オブジェクトの数に応じて、利用可能な様々な初期化とファクトリメソッドがあります。

+ (id)arrayWithObject:(id)anObject;
+ (id)arrayWithObjects:(id)firstObject, ...;
- (id)initWithObjects:(id)firstObject, ...;

arrayWithObjects:とinitWithObjects:メソッドは両方ともnil終端であるため引数は可変であり、最後の値としてnilを含める必要があります。

NSArray *someArray = [NSArray arrayWithObjects:someObject, someString, someNumber, someValue, nil];

この例では、先に示したような配列を作成しています。
最初のオブジェクトがsomeObjectで、0の配列インデックスを持っています。
最後のオブジェクトはsomeValueで、3のインデックスを持っています。

提供された値の一つがnilの場合、意図せずに項目のリストが切り詰められる可能性があります。

id firstObject = @"someString";
id secondObject = nil;
id thirdObject = @"anotherString";
NSArray *someArray = [NSArray arrayWithObjects:firstObject, secondObject, thirdObject, nil];

この場合secondObjectがnilなため項目のリストの最後として解釈され、someArrayにはfirstObjectのみが含まれます。

簡潔な構文を使用して配列リテラルを作成することが可能です。

NSArray *someArray = @[firstObject, secondObject, thirdObject];

この構文を使用する時、nilは実際には無効な値なので、nilを持つオブジェクトのリストで終了させないでください。
例えば以下のコードを実行しようとした場合、実行時に例外が発生します。

id firstObject = @"someString";
id secondObject = nil;
NSArray *someArray = @[firstObject, secondObject];
// 例外:"nilオブジェクトを挿入しようとしています"


配列オブジェクトの照会

配列を作成した後、オブジェクトをいくつ持っているか、または指定した項目が含まれているかどうかなどの情報を照会することができます。

NSUInteger numberOfItems = [someArray count];
if ([someArray containsObject:someString]) {
    ...
}

また指定したインデックスに在る配列の項目を照会することもできます。
無効なインデックスを要求しようとすると、実行時に範囲外の例外が発生します。
例外の発生を避けるために、最初に項目数を常に確認してください。

if ([someArray count] > 0) {
    NSLog(@"First item is: %@", [someArray objectAtIndex:0]);
}

この例では、項目数がゼロより大きいかどうかをチェックしています。
その場合、0のインデックスを持つ最初の項目の説明を、Foundation関数のNSLogで記録します。

objectAtIndex:を使用する代わりに、標準C配列で値にアクセスするように、添字構文を使用して配列の照会をすることもできます。
前述の例は以下のように書き換えることができます。

if ([someArray count] > 0) {
    NSLog(@"First item is: %@", someArray[0]);
}


配列オブジェクトの整列

NSArrayクラスは、収集されたオブジェクトを整列するための様々なメソッドを提供しています。
NSArrayは不変なので、それぞれのメソッドは整列された順序の項目を含む新しい配列を返します。

例えば各文字列でcompare:を呼び出すことによって、文字列の配列を整列することができます。

NSArray *unsortedStrings = @[@"gammaString", @"alphaString", @"betaString"];
NSArray *sortedStrings = [unsortedStrings sortedArrayUsingSelector:@selector(compare:)];


可変性

NSArrayクラス自体は不変ですが、可変オブジェクトを含めることができます。
例えば不変配列に可変文字列を追加する場合は以下のようになります。

NSMutableString *mutableString = [NSMutableString stringWithString:@"Hello"];
NSArray *immutableArray = @[mutableString];

文字列が変化しても貴方がすることはありません。

if ([immutableArray count] > 0) {
    id string = immutableArray[0];
    if ([string isKindOfClass:[NSMutableString class]]) {
        [string appendString:@" World!"];
    }
}

最初に作成した後で配列にオブジェクトを追加または削除したい場合、1つ以上のオブジェクトを追加や削除または置換するための様々なメソッドが追加されているNSMutableArrayを使用します。

NSMutableArray *mutableArray = [NSMutableArray array];
[mutableArray addObject:@"gamma"];
[mutableArray addObject:@"alpha"];
[mutableArray addObject:@"beta"];

[mutableArray replaceObjectAtIndex:0 withObject:@"epsilon"];

この例では、オブジェクト@"epsilon"、@"alpha"、@"beta"で構成された配列を作成しています。

これは2つ目の配列を作成することなく、所定の位置に可変配列を整列することもできます。

[mutableArray sortUsingSelector:@selector(caseInsensitiveCompare:)];

この場合、含まれる項目は大文字・小文字を区別しない順序で@"alpha"、@"beta"、@"epsilon"と昇順で整列されます。


・セット

セット(NSSet)オブジェクトは配列に似ていますが、個別のオブジェクトの順序無しグループを保持します。

unorderedsetofobjects.png

セットは順序を保持しないため、構成要素のテストの際は配列よりも高速な性能を提供します。

基本的なNSSetクラスは不変なため、割り当てと初期化、またはクラスファクトリメソッドのいずれかを使用して、作成時に内容を指定する必要があります。

NSSet *simpleSet = [NSSet setWithObjects:@"Hello, World!", @42, aValue, anObject, nil];

NSArrayと同様に、
initWithObjects:とsetWithObjects:メソッドは両方ともnil終端で、引数は可変です。
可変のNSSetサブクラス名はNSMutableSetです。

貴方が何度もオブジェクトの追加を試みた場合でも、セットは個々のオブジェクトに唯一の参照を格納します。

NSNumber *number = @42;
NSSet *numberSet = [NSSet setWithObjects:number, number, number, number, nil];
// numberSetには1つのみオブジェクトが含まれます


・辞書

辞書(NSDictionary)は単なる順序付けされた、または順序無しのオブジェクトのコレクションではなく、検索に使用することができる指定したキーに関連付けされたオブジェクトを格納するものです。

ベストプラクティスは、辞書のキーとして文字列オブジェクトを使用することです。

dictionaryofobjects.png

キーとして他のオブジェクトを使用することもできますが、各キーは辞書で使用するためにコピーされるため、NSCopyingをサポートしている必要があることを心に留めておいてください。
ただしキー値コーディング(詳細はキー値コーディングプログラミングガイド参照)を使用する場合は、辞書オブジェクト用に文字列キーを使用する必要があります。


辞書の作成

割り当てと初期化、またはクラスファクトリメソッドを使用して、以下のように辞書を作成することができます。

NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys:
                     someObject, @"anObject",
              @"Hello, World!", @"helloString",
                                @42, @"magicNumber",
                      someValue, @"aValue",
                                    nil];

dictionaryWithObjectsAndKeys:とinitWithObjectsAndKeys:メソッドでは、各オブジェクトはキーの前で指定し、オブジェクトとキーのリストはnil終端にする必要があります。

Objective-Cは辞書リテラルを作成するための簡易構文を提供しています。

NSDictionary *dictionary = @{
                    @"anObject" : someObject,
                  @"helloString" : @"Hello, World!",
            @"magicNumber" : @42,
                        @"aValue" : someValue
};

辞書リテラルの場合、キーはオブジェクトの前で指定し、オブジェクトとキーのリストはnil終端ではありません。


辞書の照会

辞書を作成した後、指定されたキーに対して格納されているオブジェクトを尋ねることができます。

NSNumber *storedNumber = [dictionary objectForKey:@"magicNumber"];

オブジェクトが見つからない場合、objectForKey:メソッドはnilを返します。

またobjectForKey:の代わりに添字構文を使用することもできます。

NSNumber *storedNumber = dictionary[@"magicNumber"];


可変性

作成した後に辞書にオブジェクトを追加したり削除する必要がある場合は、NSMutableDictionaryサブクラスを使用します。

[dictionary setObject:@"another string" forKey:@"secondString"];
[dictionary removeObjectForKey:@"anObject"];


・nilとNSNullの表現

Objective-Cにおけるnilは"オブジェクトが無い"ことを意味するため、この節で説明したコレクションクラスにはnilを追加することはできません。
コレクション内で"オブジェクトが無い"ことを表す必要がある場合は、NSNullクラスを使用します。

NSArray *array = @[ @"string", @42, [NSNull null] ];

NSNullのnullメソッドは、常に同じインスタンスを返します。
このように動作するクラスをシングルトンクラスと呼びます。
配列内のオブジェクトが共有されているNSNullインスタンスと等しいかどうかは、以下のように確認することができます。

for (id object in array) {
    if (object == [NSNull null]) {
        NSLog(@"Found a null object");
    }
}



Foundationフレームワークにはここで説明したものよりも多くの機能が含まれていますが、すぐに個々の詳細を知っている必要はありません。
Foundationについてより学びたい場合は、Foundation Framework Referenceを見てください。
貴方は今ではカスタムデータクラスを記述することによる、ToDoListアプリの実装を継続するために十分な情報を持っています。



参考文献

Apple/Start Developing iOS Apps Today

0 CommentsPosted in 資料

Start Developing iOS Apps Today(8)~デザインパターンの使用

2014. 03. 10
●デザインパターンの使用

デザインパターンは一般的なソフトウェア技術の問題を解決します。
パターンは抽象的な設計で、コードではありません。
デザインを採用する時は、特定のニーズに一般的なパターンを適用します。
作成するアプリの種類が何であろうとも、フレームワークで使用されている基本的なデザインパターンを知るのは良いことです。
デザインパターンを理解すると、より効果的にフレームワークを使用するのに役立ち、より再利用や拡張、変更が容易なアプリを記述することができます。

design_patterns_2x.png



●MVC

モデル - ビュー - コントローラ(MVC)は、どのiOSアプリにおいても優れた設計の中心となります。
MVCでは、アプリのオブジェクトをモデル、ビュー、またはコントローラの3つの役割のいずれかに割り当てます。
このパターンでは、モデルはアプリのデータの追跡を維持し、ビューはユーザインターフェイスの表示とアプリのコンテンツを構成し、コントローラはビューを管理します。
コンテンツを持つビューを生成してユーザ操作に応答することによって、コントローラはモデルとビュー間の通信のためのゲートウェイとして機能します。

ModelViewController_2x.png

ToDoListアプリを構築していく中で、MVC中心の設計を踏襲してきました。
ストーリーボードに構築したインターフェイスはビュー層を構成します。
XYZAddToDoItemViewControllerとXYZToDoListViewControllerはビューを管理するコントローラです。
チュートリアル:データの追加」で、アプリのビューとコントローラで動作するデータモデルを組み込みます。
独自のアプリの設計を始める時は、設計の中心にMVCを維持することが重要です。



●ターゲット - アクション

ターゲット - アクションは特定のイベントが発生した時に、あるオブジェクトが別のオブジェクトにメッセージを送信するという、概念的に単純な設計です。
アクションメッセージはソースコードで定義されたセレクタであり、(メッセージを受信するオブジェクトである)ターゲットはアクションを実行することができるオブジェクトで、一般的にビューコントローラです。
アクションメッセージを送信するオブジェクトは通常、タップやドラッグ、値変更等のユーザ操作に応答してイベントをトリガすることができる(ボタン、スライダ、スイッチなどの)コントロールです。


例えば、ユーザが(ユーザインターフェイスに作成した)Restore Defaultsボタンをタップする度に、アプリのデフォルト設定を復元すると仮定します。
まずデフォルト設定を復元するためのロジックを実行する、アクションrestoreDefaults:を実装します。
次に、そのメソッドを実装したビューコントローラにrestoreDefaults:アクションメソッドを送信するために、ボタンのTouch Up Insideイベントを登録します。

target_action_2x_20140220154507194.png

貴方はToDoListアプリで既にターゲット - アクションを使用しています。
ユーザがXYZAddToDoItemViewControllerのDoneボタンをタップした時、unwindToList:アクションをトリガします。
この場合、Doneボタンはメッセージを送信するオブジェクトであり、ターゲットオブジェクトはXYZToDoListViewControllerで、アクションメッセージはunwindToList:、そして送信されるアクションメッセージをトリガするイベントはDoneボタンをタップするユーザです。
ターゲット - アクションは、相互作用を定義してアプリの様々な部品間で情報を送信するための強力な仕組みです。



●デリゲーション

デリゲーションは、アプリ内のあるオブジェクトの代理、または別のオブジェクトと連携する単純かつ強力なパターンです。
デリゲートオブジェクトは他のオブジェクト(デリゲート)との参照を保持し、適切な時にメッセージを送信します。
メッセージは、デリゲートオブジェクトが処理しようとする、またはちょうど処理したイベントのデリゲートに通知します。
デリゲートは、アプリ内で自身あるいは他のオブジェクトの外観(または状態)が更新することによってメッセージに応答することができ、場合によってはすぐに起こりそうなイベントが処理される方法に影響を与える値を返します。

delegation_2x_20140220215301aae.png

デリゲートパターンは既存のフレームワーククラスで広く使われていますが、アプリ内の2つのカスタムオブジェクト間のデリゲーションを実装することもできます。
一般的な設計では、子ビューコントローラがその親ビューコントローラに何らかの値(通常はユーザが入力した値)を通信できるようにするための手段として、デリゲーションを使用します。

貴方はまだデリゲーションで作業をしていませんが、「チュートリアル:データの追加」でXYZToDoListViewControllerクラスに動作を追加する時に一例を見ることができます。

これらはiOSの開発中に遭遇する最も一般的なデザインパターンで、数ある中の極一部です。
Objective-Cの詳細について学ぶと、貴方のアプリに適用することができる他のデザインパターンを発見するでしょう。



参考文献

Apple/Start Developing iOS Apps Today

0 CommentsPosted in 資料

Start Developing iOS Apps Today(7)~データの組み込み

2014. 03. 03
●データの組み込み

アプリのデータモデルはデータ構造で構成され、(オプションで)一貫した状態のデータを維持するために必要なカスタムビジネスロジックです。
アプリのユーザインターフェイスから完全に分離したデータモデルを設計する必要はありません。
ただし特定のビューまたはビューコントローラの存在に依存することなく、別々にデータモデルオブジェクトを実装するようにします。
データをユーザインターフェイスからの分離を維持すると、ユニバーサルアプリ(iPadとiPhoneの両方で実行できるアプリ)の実装や、後からコードの一部を再利用することが容易になることが分かります。

ModelViewController_m_2x.png



●モデルの設計

単純に少量のデータを格納する必要がある場合、Foundationフレームワーククラスが最良の選択肢かもしれません。
自身で同じことをする実装を試みる代わりに、既存の動作が利用可能かを確認するため、Foundationクラスを調べます。
例えば貴方のアプリが文字列のリストの追跡し続ける必要がある場合、その役割をこなすためにNSArrayとNSStringに依存することができます。
これらや他のFoundationクラスについては「Foundationでの処理」で詳細を学びます。

データモデルがデータの格納だけではなくカスタムビジネスロジックを必要とする場合は、カスタムクラスを記述することができます。
独自のクラスの実装に、どのように既存のフレームワーククラスを組み込むことができるかを検討してください。
それらを作り直そうとする代わりに、カスタムクラス内で既存のフレームワーククラスを使用することが有益です。
例えば、カスタムクラスは情報を処理するために独自の機能を定義するが、情報を格納するためにNSMutableArrayを使用する場合があります。

データモデルを設計する時に心に留めておくべき、いくつかの質問を以下に示します。

格納する必要があるデータの種類は何か?
格納するのがテキスト、ドキュメント、大きな画像、または別の種類の情報なのかどうか、適切にコンテンツの特定の種類を処理するデータモデルを設計します。

使用できるデータ構造は何か?
使用することができるフレームワーククラスを決定し、カスタム機能を持つクラスを定義する必要があります。

どのようにユーザインターフェイスにデータを提供するか?
モデルはインターフェイスと直接通信するべきではありません。
モデルとインターフェイス間で相互作用を処理するには、コントローラにロジックを追加する必要があります。



●モデルの実装

効率の良いコードを記述するには、Objective-Cとその昨日の詳細について学ぶ必要があります。
このガイドでは簡単なアプリの構築方法を教えていますが、独自の完全に機能するアプリを記述する前に言語に精通したいでしょう。

Objective-Cを学ぶには、いくつかの良い取り組み方があります。
一部の人々はObjective-Cによるプログラミングを読んで概念を学び、それから言語の理解を固めるために多くの小さなテストアプリを記述し、良いコードを記述する練習をします。

それ以外にプログラミングにすぐに飛び込んで、何かを達成する方法が分からない時に更に情報を探す手法もあります。
この手法を好む場合、Objective-Cによるプログラミングを参考として概念を学び、開発するアプリに適用する演習をします。

最初のデータモデルを開発する上で最も重要な目標は、動作する何かを取得することです。
データモデルの構造については綿密に検討しますが、完璧にすることについて心配しないでください。
実装を開始した後にモデルの改良を繰り返すことを恐れては行けません。



参考文献

Apple/Start Developing iOS Apps Today

0 CommentsPosted in 資料






SoundSport Pulse wireless headphones
Calendar
02 | 2014/03 | 04
Sun Mon Tue Wed Thu Fri Sat
- - - - - - 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 - - - - -
Recent Articles
iTunes


Swift
Categories
Tips
Profile

水月杏香

Author:水月杏香
永遠の初心者プログラマ。

Wish List
WACOM


ARC
Technical Q&A
情報プロパティリストキー
Start Developing iOS Apps Today
BOSE

SoundSport Pulse wireless headphones
Reference
NSApplicationDelegateプロトコル
NSArrayクラス
NSAutoreleasePoolクラス
NSBundleクラス
NSBundle UIKit追加分
NSCalendarクラス
NSCoderクラス
NSCodingプロトコル
NSCopyingプロトコル
NSDataクラス
NSDateクラス
NSDateFormatterクラス
NSDictionaryクラス
NSEntityDescriptionクラス
NSEnumeratorクラス
NSErrorクラス
NSExceptionクラス
NSFetchRequestクラス
NSFileHandleクラス
NSFileManagerクラス
NSIndexPathクラス
NSIndexPath UIKit追加分
NSKeyedArchiverクラス
NSKeyedUnarchiverクラス
NSKeyValueCodingプロトコル
NSLocaleクラス
NSManagedObjectクラス
NSManagedObjectContextクラス
NSManagedObjectModelクラス
NSMutableArrayクラス
NSMutableCopyingプロトコル
NSMutableDictionaryクラス
NSMutableSetクラス
NSNotificationクラス
NSNotificationCenterクラス
NSNullクラス
NSNumberクラス
NSObjectクラス
NSObject UIKit追加分
NSObjectプロトコル
NSPersistentStoreクラス
NSPersistentStoreCoordinatorクラス
NSPredicateクラス
NSPropertyListSerializationクラス
NSRunLoopクラス
NSSetクラス
NSStringクラス
NSString UIKit追加分
NSTimerクラス
NSTimeZoneクラス
NSURLクラス
NSURLProtectionSpaceクラス
NSURLRequestクラス
NSUserDefaultsクラス
NSValueクラス

UIActionSheetクラス
UIActionSheetDelegateプロトコル
UIActivityIndicatorViewクラス
UIAlertViewクラス
UIAlertViewDelegateプロトコル
UIApplicationクラス
UIApplicationDelegateプロトコル
UIBarButtonItemクラス
UIBarItemクラス
UIButtonクラス
UIColorクラス
UIControlクラス
UIDatePickerクラス
UIDeviceクラス
UIEventクラス
UIFontクラス
UIGestureRecognizerクラス
UIImageクラス
UIImageViewクラス
UIKit Function
UILabelクラス
UINavigationControllerクラス
UINavigationItemクラス
UIPickerViewクラス
UIPickerViewDataSourceプロトコル
UIPickerViewDelegateプロトコル
UIPinchGestureRecognizerクラス
UIResponderクラス
UIScreenクラス
UIScrollViewクラス
UISearchBarクラス
UISearchBarDelegateプロトコル
UISegmentedControlクラス
UISliderクラス
UISwipeGestureRecognizerクラス
UISwitchクラス
UITableViewクラス
UITableViewCellクラス
UITableViewControllerクラス
UITableViewDataSourceプロトコル
UITableViewDelegateプロトコル
UITapGestureRecognizerクラス
UITextFieldクラス
UITextFieldDelegateプロトコル
UITextInputTraitsプロトコル
UITextViewクラス
UITextViewDelegateプロトコル
UIToolbarクラス
UITouchクラス
UIViewクラス
UIViewControllerクラス
UIWebViewクラス
UIWebViewDelegateプロトコル
UIWindowクラス

AVAudioPlayerクラス
AVAudioPlayerDelegateプロトコル

CADisplayLinkクラス
CAEAGLLayerクラス
CALayerクラス

CGAffineTransform
CGBitmapContext
CGColor
CGColorSpace
CGContext
CGGeometry
CGImage
CGPath

EAGLContextクラス
EAGLDrawableプロトコル

Foundation Constants
Foundation Data Types
Foundation Functions

MPMediaItemクラス
MPMediaItemArtworkクラス
MPMediaPlaylistクラス
MPMediaPropertyPredicateクラス
MPMediaQueryクラス
MPMusicPlayerControllerクラス

Randomization Services

System Sound Services
Amazon


OpenGL ES
SQLite
Monthly Archives
Recent Comments
Recent TrackBacks
RSS Link
Visitors
QR Code
QR