Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mono0926/8835861 to your computer and use it in GitHub Desktop.
Save mono0926/8835861 to your computer and use it in GitHub Desktop.

storyboard上で、initial view controllerから他のview controllerに対してrelationshipを確立します。同様に、それらのview controllerから他のview controllerにrelationshipを確立します。最終的に、storyboard上のほとんど、あるいは全てのview controllerを一つのグラフに接続します。接続されたview controllerが、iOSによっていつインスタンス化されるかは、relationshipのタイプによって決まります。

  • relationshipがsegueの場合、destination view controllerは、segueが実行された場合にインスタンス化されます。
  • relationshipがcontainmentの場合、child view controllerは、親がインスタンス化された時にインスタンス化されます。
  • view controllerが他のview controllerのdestinationでも子供でもない場合、自動的にインスタンス化されません。storyboardからプログラム的にインスタンス化する必要があります。

storyboardからview controllerをプログラム的にロードするために、view controllerにidentifierを設定する必要があります。同様に、segueをプログラム的に起動するために、segueにidintifierを設定する必要があります。segueが起動された時、そのsegueのidentifierがsource view controllerに渡され、それによりどのsegueが起動されたかを知ることができます。このため、全てのsegueにidentifierをラベリングするようにしてください。

storyboardを使用してアプリを作る場合、全てのview controllerを一つのstoryboardが保持するようにすることもできますし、複数のstoryboardを作成し、それぞれにユーザーインターフェイスの一部を実装することもできます。main storyboardが存在すれば、iOSはそれを自動的にロードします。他のstoryboardは明示的にロードする必要があります。

Main StoryboardがアプリのUIを初期化する

main storyboardは、Information property listファイル内で定義されます。このファイル内でmain storyboardが定義されている場合、アプリ起動時にiOSは次のことを行います。

  1. windowをインスタンス化します。
  2. main storyboardをロードし、initial view controllerをインスタンス化します。
  3. view controllerをwindowのrootView Controllerプロパティに設定し、screen上のwindowをvisibleにします。

initial view controllerが表示される前に、initial view controllerをコンフィグレーションするためにapp delegateが呼ばれます。詳細

SegueはDestination View Controllerを自動的にインスタンス化する

sequeはtransitionについてのたくさんの情報を持っています。

  • senderと呼ばれる、segueの起動を引き起こしたオブジェクト
  • segueが開始されたsource view controller
  • インスタンス化されるdestination view controller
  • destination view controllerがスクリーンに表示される際に使用されるtransitionの種類
  • storyboard内で特定のsegueを識別するためのidentifier string

segueが起動された時、iOSは次のことを行います。

  1. storyboardで指定したattribute valuesを使用してdestination view controllerをインスタンス化します。
  2. source view controllerに、このview controllerをコンフィグレーションする機会を与えます。
  3. transitionが実行されます。

Segueをプログラム的に起動する

segueは、通常controlやgesture recognizerのようにsource view controllerに関連付けられたオブジェクトから起動されます。しかし、segueにidentifierが設定されていれば、アプリからプログラム的に起動することができます。例えば、ゲームを作っている時に、試合が終わった時にsegueを起動するなどです。

segueをプログラム的に起動するには、source view controllerのperformSegueWithIdentifier:sender:メソッドをコールします。

次のサンプルは、ランドスケープ時に別のインターフェイスを表示するプログラムです。

- (void)orientationChanged:(NSNotification *)notification
{
    UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
    if (UIDeviceOrientationIsLandscape(deviceOrientation) &&
        !isShowingLandscapeView)
    {
        [self performSegueWithIdentifier:@"DisplayAlternateView" sender:self];
        isShowingLandscapeView = YES;
    }
// Remainder of example omitted.
}

segueをプログラム的にのみ起動するのであれば、segueの接続は、通常source view controllerからdestination view controllerに直接接続します。

StoryboardのView Controllerをプログラム的にインスタンス化する

segueを使用せずに、プログラム的にview controllerをインスタンス化したい場合があるかもしれません。その場合でも、storyboardは役に立ちます。storyboardからview controllerのattributeやviewの階層構造を設定したりできるからです。ただし、view controllerをプログラム的にインスタンス化する場合、segueの振る舞いを活かすことはできません。view controllerを表示するために、追加のコードを書く必要があります。そのため、できるだけsegueを使用すべきです。

以下が実装に必要なステップです。

  1. storyboardオブジェクト(UIStoryboardクラスのオブジェクト)を取得します。

もし既に同じstoryboardからインスタンス化したview controllerを持っている場合、そのstoryboardプロパティから取得できます。異なるstoryboardをロードする場合、UIStoryboardクラスのstoryboardWithName:bundle:クラスメソッドを呼びます。

  1. storyboardオブジェクトのinstantiateViewControllerWithIdentifier:メソッドを呼びます。initial view controllerをインスタンス化する場合は、instantiateInitialViewControllerを使用することができます。

  2. インスタンス化したview controllerのプロパティを設定します。

  3. view controllerを表示します。

以下は、同じstoryboardからview controllerをインスタンス化する例です。

- (IBAction)presentSpecialViewController:(id)sender {
    UIStoryboard *storyboard = self.storyboard;
    SpecialViewController *svc = [storyboard instantiateViewControllerWithIdentifier:@"SpecialViewController"];
 
    // Configure the new view controller here.
 
    [self presentViewController:svc animated:YES completion:nil];
}

以下は、新しいstoryboardをロードし、initial view controllerをインスタンス化します。外部スクリーンに表示するために、新しいwindowのroot view controllerとしてそのview controllerを使用します。

- (UIWindow*) windowFromStoryboard: (NSString*) storyboardName
                                   onScreen: (UIScreen*) screen
{
    UIWindow *window = [[UIWindow alloc] initWithFrame:[screen bounds]];
    window.screen = screen;
 
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
    MainViewController *mainViewController = [storyboard instantiateInitialViewController];
    window.rootViewController = mainViewController;
 
    // Configure the new view controller here.
 
    return window;
}

新しいStoryboardに遷移するにはプログラム的なアプローチが必要

複数のstoryboardを使用したほうが便利な場合の例。

  • チームが大きく、ユーザーインターフェイスによって担当する人が分かれている場合。
  • view controllerが含まれるライブラリがあり、それらのview controllerがstoryboard内に定義されている場合。
  • 外部スクリーン用に表示するものがある場合。

コンテナは自動的に子供をインスタンス化する

storyboard内のcontainer view controllerがインスタンス化されるとき、その子供も同時にインスタンス化されます。

view controllerクラスのクライアントは、view controllerが何のviewを表示する必要があるかを知る必要はありません。また、viewが何のアクションを起動するかを知る必要はありません。可能な限り、outletやactionは、クラスの実装ファイルの中のカテゴリに定義すべきです。

MyViewController.mの例

@interface MyViewController()
// Outlets and actions here.
@end

@implementation MyViewController
// Implementation of the privately declared category must go here.
@end

プライベートなカテゴリ内に定義されたoutletやactionは、Interface Builderからは見えますが、他のクラスからは見えません。

view controllerが初めてインスタンス化されるとき、view controllerは、そのライフタイムを通じて必要とするオブジェクトを生成します。view階層やコンテンツの表示に関係するオブジェクトは生成すべきではありません。生成するのは、データオブジェクトと重要な動作に必要なオブジェクトのみにすべきです。

StoryboardからロードされたView Controllerの初期化

storyboard内でview controllerを生成する場合、Interface Builder内で設定したattributeは、アーカイブ内にシリアライズされます。view controllerがインスタンス化されるときに、このアーカイブがメモリにロードされ処理されます。アーカイブは、view controllerのinitWithCoder:メソッドが呼ばれた時にロードされます。そして、awakeFromNibメソッドが実装されていれば、そのメソッドが呼ばれます。このメソッドは、設定する際に他のオブジェクトがインスタンス化されている必要がある場合に利用することができます。

View Controllerのプログラム的な初期化

view controllerをプログラム的に生成する場合、view controllerにカスタムな初期化メソッドを作ります。そしてそのメソッドは、スーパークラスのinitメソッドを呼び、その後固有の初期処理を実行します。

一般的には、複雑な初期化メソッドを書かないでください。シンプルな初期化メソッドを実装し、view controllerの振る舞いを設定するためのプロパティを提供してください。

アプリのどこかからview controllerのviewにアクセスし、そのviewがメモリ上にない場合、view controllerはそのview階層をメモリにロードし、今後使用するためにviewプロパティに保持します。

  1. view controllerは、loadViewメソッドを呼びます。loadViewメソッドのデフォルトの実装は、次のどちらかを実行します。

    • storyboardが使用されている場合、viewをstoryboardからロードします。
    • storyboardが使用されていな場合、空のUIViewオブジェクトを生成し、viewプロパティに設定します。
  2. view controllerは、viewDidLoadメソッドを呼びます。これにより、そのサブクラスが付加的なタスクを実行することができます。

アプリは、必要であればloadViewとviewDidLoadをオーバーライドすることができます。例えば、アプリがstoryboardを使用しておらず、view階層にviewを追加したい場合、viewをプログラム的にインスタンス化するためにloadViewをオーバーライドします。

viewをプログラム的に生成するために、loadViewメソッドをオーバーライドする場合、superを呼ばないでください。superを呼ぶと、デフォルトのviewをロードする仕組みが開始され、通常はCPUの無駄になります。loadViewメソッドの実装は、view controllerのroot viewとsubviewを生成するのに必要な全てのことを行ってください。

view controllerとmemory managementについて、2つの考慮すべき事があります。

  • どのようにメモリを効率的にアロケートするか
  • いつどのようにメモリをリリースするか
タスク メソッド 解説
view controllerに必要な重要なデータ構造のアロケート 初期化メソッド initで始まるメソッドは、view controllerを問題のない状態にする責任があります。それには、適切な処理を行うために必要となるあらゆるデータ構造のアロケートが含まれます。
viewオブジェクトの生成 loadView loadViewメソッドのオーバーライドは、viewをプログラム的に生成する場合のみに必要となります。
カスタムオブジェクトの生成 カスタムプロパティとメソッド 他の設計を使用してもいいですが、loadViewメソッドと同じパターンを使用することを検討してください。カスタムオブジェクトを保持するプロパティと、それを初期化するメソッドを生成してください。プロパティが取得され、その値がnilだった場合、そのメソッドを呼んでください。
viewに表示されるデータのアロケートやロード viewDidLoad データオブジェクトは、一般的にview controllerのプロパティに設定することによって提供されます。view controllerが必要とする付加的なデータオブジェクトの生成は、viewDidLoadメソッドをオーバーライドすることによって行ってください。このメソッドが呼ばれるときまでには、viewオブジェクトが存在し、問題のない状態となっていることが保証されています。
メモリ不足通知への対応 didReceiveMemoryWarning view controllerに関係している、全ての重要ではないオブジェクトをデアロケートするためにこのメソッドを使用してください。iOS 6では、viewオブジェクトへのリファレンスをリリースするためにこのメソッドを使用することもできます。
view controllerに必要な重要なデータ構造のリリース dealloc view controllerの最後のクリーンナップを行うことのためだけにこのメソッドをオーバーライドしてください。インスタンス変数やプロパティに保存されるオブジェクトは、自動的にリリースされるので、明示的にリリースする必要はありません。

iOS 6以降の場合、View Controllerは必要なときにViewをアンロードする

view controllerのデフォルトの振る舞いは、viewプロパティが初めてアクセスされた時にそのview階層をロードし、その後view controllerが破棄されるまでメモリ上にキープします。オンスクリーンに描画するためにviewによって使用されるメモリは、非常に大きくなる可能性があります。しかし、システムはviewがwindowにアタッチされていない時に、自動的にこれらのメモリをリリースします。

アプリにメモリが必要な場合、view階層を明示的にリリースすることができます。

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Add code to clean up any of your own resources that are no longer necessary.
    if ([self.view window] == nil)
    {
        // Add code to preserve data stored in the views that might be
        // needed later.
 
        // Add code to clean up other strong references to the view in
        // the view hierarchy.
        self.view = nil;
    }

次回view propertyにアクセスされた時、viewは初回アクセスされた時と同じようにリロードされます。

iOS 5以前の場合、システムはメモリが足りなくなった時にViewをアンロードする

以前のiOSのバージョンは、空きメモリが少なくなった場合に、システムが自動的にview controllerのviewをアンロードしようとします。

  1. アプリがシステムからlow-memory warningを受け取ります。
  2. それぞれのview controllerは、自身ののdidReceiveMemoryWarningを呼びます。このメソッドをオーバーライドする場合、view controllerがもう必要としないメモリやオブジェクトをリリースします。 デフォルトの実装が動作するよう、superを呼ぶ必要があります。iOS 5以前の場合、デフォルトの実装はviewをリリースしようとします。iOS 6以降の場合、デフォルトの実装は終了します。
  3. もしviewが安全にリリースできない場合、例えばオンスクリーンに表示されている場合、デフォルトの実装は終了します。
  4. view controllerは、自身のviewWillUnloadメソッドを呼びます。一般的にサブクラスがこのメソッドをオーバーライドするのは、viewが破壊される前にプロパティを保存する必要がある場合です。
  5. viewプロパティにnilを設定します。
  6. view controllerは、自身のviewDidUnloadメソッドを呼びます。一般的にサブクラスがこのメソッドをオーバーライドするのは、viewに対するstrongな参照をリリースする必要がある場合です。

UIViewControllerクラスは、viewの表示や非表示がなぜ発生したかを調べる方法を提供します。以下にそのメソッドと使用方法を説明します。これらのメソッドは、viewWillAppear:、viewDidAppear:、viewWillDisappear:、viewDidDisappear:メソッドの中で呼ぶことができます。

メソッド名 使用方法
isMovingFromParentViewController このメソッドは、viewWillDisappear:とviewDidDisappear:メソッドの中で呼ぶことができます。viewが非表示になるのは、view controllerがcontainer view controllerからremoveされたためかどうかを知ることができます。
isMovingToParentViewController このメソッドは、viewWillAppear:とviewDidAppear:メソッドの中で呼ぶことができます。viewが表示されるのは、view controllerがcontainer view controllerにaddされたためかどうかを知ることができます。
isBeingPresented このメソッドは、viewWillAppear:とviewDidAppear:メソッドの中で呼ぶことができます。viewが表示されるのは、view controllerが他のview controllerによってpresentされたためかどうかを知ることができます。
isBeingDismissed このメソッドは、viewWillDisappear:とviewDidDisappear:メソッドの中で呼ぶことができます。viewが非表示になるのは、view controllerがdismissされたためかどうかを知ることができます。

view controllerのviewのサイズが変化した時、subviewは新しいスペースにフィットするよう再配置されます。controllerのview階層内のviewは、ほとんどの処理をlayout constraintやautoresizing maskによって自分自身で行います。しかし、view controllerも、このプロセスに関与できるよう様々な箇所で呼ばれます。

  1. view controllerのviewは新しいサイズにリサイズされます。

  2. もしautolayoutが使用されていない場合、viewはautoresizing maskにしたがってリサイズされます。

  3. view controllerのviewWillLayoutSubviewsメソッドが呼ばれます。

  4. viewのlayoutSubviewsメソッドが呼ばれます。もしautolayoutが使用されている場合、次のステップを実行することにより、layout constraintを更新します。

    1. view controllerのupdateViewConstraintsメソッドが呼ばれます。
    2. UIViewControllerクラスのupdateViewConstraintsメソッドが、viewのupdateConstraintメソッドを呼びます。
    3. layout constraintが更新された後、新しいレイアウトが計算され、viewが再配置されます。
  5. view controllerのviewDidLayoutSubviewsメソッドが呼ばれます。

理想的には、viewはそれ自身を再配置するのに必要な作業全てをview自身で行います。そのプロセスに対するview controllerの関与なしにです。大抵の場合、レイアウトを完全にInterface Builderで設定することが可能です。しかし、view controllerが、viewを動的に追加したり削除したりする場合は、Interface Builderの静的なレイアウトではできないかもしれません。この場合は、view controllerがこの処理をコントロールするのが良いです。多くの場合、view自身は、シーン内の他のviewのことをよく知らないからです。以下が、view controllerにおける正しいアプローチです。

  • (iOS 6以降)viewを自動的に配置するために、layout constraintsを使用してください。まだviewによって設定されていないlayout constraintを追加するには、updateViewConstraintsをオーバーライドしてください。その際に、[super updateViewConstraints]を必ず呼んでください

  • (iOS 5)autoresizing maskと、viewを手動で配置するコードを組み合わせて使用してください。resizing maskを使用して自動的に位置を設定できないviewを再配置するために、layoutSubviewsをオーバーライドしてください。

デバイスの向きが変化した時、システムは、UIDeviceOrientationDidChangeNotificationを送信します。デフォルトで、UIKitフレームワークはこのnotificationをlistenし、インターフェイスの向きを自動的に更新するために使用します。つまり、少々の例外を除いて、このnotificationをハンドルする必要はありません。

ユーザーインターフェイスがrotateした時、windowは新しい無機にマッチするようリサイズされます。windowは、root view controllerのframeを新しいサイズにマッチするよう調整します。そして今度はこのサイズがview階層を下って他のviewに伝搬します。つまり、複数の向きをサポートする最もシンプルな方法は、root viewのframeが変化した時は常にsubviewの位置が更新されるよう、view階層を設定することです。

UIKitがorientation notificationを受信した時、UIKitは、新しい向きが許されるかを決めるために、UIApplicationオブジェクトとroot view controllerを使用します。この両方のオブジェクトが同意した場合に、新しい向きがサポートされます。

view controllerがroot view controllerの上にpresentされる場合、システムの動作は2つの点で変化します。1つ目は、presented view controllerがroot view controllerの代わりに、向きがサポートされるかを決めるために使用されます。2つ目は、presented view controllerは、preferred orientationを提供することができます。もしview controllerがフルスクリーンでpresentされる場合、ユーザーインターフェイスはpreferred orientationで表示されます。ユーザーは、この場合のインターフェイスはデバイスの向きと異なり、デバイスの向きを変えることが期待されます。preferred orientationは、コンテンツが新しい向きで表示される必要がある場合によく使用されます。

Preferred Presentation Orientationの宣言

view controllerがコンテンツを表示するためにフルスクリーンでpresentされる場合、特定の向きで表示されるのが良い場合があります。もし、コンテンツが特定の向きのみで表示される場合、単にsupportedInterfaceOrientationsメソッドでその向きだけを返してください。もしview controllerは複数の向きをサポートしますが、特定の向きで表示するのがより良い場合、preferredInterfaceOrientationForPresentationメソッドをオーバーライドすることにより、優先する向きを提供することができます。

rotationが行われる時にメソッドが呼ばれるため、そこで時間がかかる処理を行うべきではありません。また、view階層を新しいものと完全に置き換えるようなこともすべきではありません。異なる向きに対して専用のviewを提供する場合は、新しいview controllerを表示する方が適切です。

rotationメソッドはroot view controllerに対して送信されます。root view controllerは、これらのイベントを必要に応じてその子供に送信します。次にrotationが行われるときに発生するイベントのシーケンスを示します。

  1. windowは、root view controllerのwillRotateToInterfaceOrientation:duration:メソッドを呼びます。

    container view controllerは、このメッセージを現在表示しているcontent view controllerにforwardします。インターフェイスがrotateする前にviewを非表示にしたり、viewレイアウトを変更したりするために、custom content view controller内でこのメソッドをオーバーライドすることができます。

  2. windowは、view controllerのviewのboundsを調整します。これは、view controllerのviewWillLayoutSubviewsメソッドを呼び出し、viewのsubviewのレイアウトを引き起こします。このメソッドが動作する際に、現在のユーザーインターフェイスのレイアウトを決めるために、appオブジェクトのstatusBarOrientationプロパティを参照することができます。

  3. view controllerのwillAnimateRotationToInterfaceOrientation:duration:メソッドが呼ばれます。このメソッドは、アニメーションブロック内から呼ばれます。よって、ここで行ったプロパティの変更も同時にアニメーションされます。

  4. アニメーションが実行されます。

  5. windowは、view controllerのdidRotateFromInterfaceOrientation:メソッドを呼びます。

    container view controllerは、このメッセージを現在表示しているcontent view contorlllerにforwardします。これがrotationプロセスの最後を示します。このメソッドをviewを表示したりレイアウトを変更したりすることに使用することができます。

Processing an interface rotation

rotationが発生した時に、view controllerのコンテンツがオンスクリーンでない場合、一連のrotationメッセージを受けることができません。例えば、次のようなシーケンスを考えてみてください。

  1. view controllerが、他のview controllerのコンテンツをフルスクリーンで表示。
  2. ユーザーは、ユーザーインターフェイスの向きを変えるためにデバイスをrotate。
  3. presented view controllerをdismiss。

この例では、rotationが発生した時に、元のview controllerは表示されていません。そのため、rotationイベントは受信しません。代わりに、再度表示される時、viewは単にリサイズされ、通常のレイアウト処理を使用して配置されます。もしレイアウトが現在のデバイスの向きを知る必要がある場合は、appオブジェクトのstatusBarOrientationプロパティを参照することができます。

デバイスがportraitかlandscapeによって、同じデータを異なる表示のさせ方をしたい場合、2つの別のview controllerを使用することで実現できます。一つのview controllerは、portraitでのデータの表示を管理し、もう一つのview controllerは、landscapeでのデータの表示を管理します。向きが変化するたびにview階層を大きく変化させることと比較して、2つのview controllerを使えば、シンプルで効率が良くなります。

別のlandscapeインターフェイスをサポートするには、次のようにします。

  • 2つのview controllerオブジェクトを実装します。ひとつはportraitのみのインターフェイスを表示し、もうひとつはlandscapeのみのインターフェイスを表示します。

  • UIDeviceOrientationDidChangeNotificationに登録します。ハンドラメソッドで、現在のデバイスの向きに応じて別ののview controllerをpresentしたりdismissしたりします。

@implementation PortraitViewController
- (void)awakeFromNib
{
    isShowingLandscapeView = NO;
    [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                 selector:@selector(orientationChanged:)
                                 name:UIDeviceOrientationDidChangeNotification
                                 object:nil];
}
 
- (void)orientationChanged:(NSNotification *)notification
{
    UIDeviceOrientation deviceOrientation = [UIDevice currentDevice].orientation;
    if (UIDeviceOrientationIsLandscape(deviceOrientation) &&
        !isShowingLandscapeView)
    {
        [self performSegueWithIdentifier:@"DisplayAlternateView" sender:self];
        isShowingLandscapeView = YES;
    }
    else if (UIDeviceOrientationIsPortrait(deviceOrientation) &&
             isShowingLandscapeView)
    {
        [self dismissViewControllerAnimated:YES completion:nil];
        isShowingLandscapeView = NO;
    }
}

viewの複雑さによって、rotationをサポートする多くのコードを書く必要があるかもしれませんし、あるいは全く必要ないかもしれませんが、次のtipsをコードを書く際のガイドとして利用することができます。

  • rotation中は一時的にイベントの配送を止めてください。

    そうすることで、向きが変わっている最中に不要なコードが実行されることを防ぐことができます。

  • mapの可視領域を保存してください。

    アプリがmap viewを持っている場合、rotationが開始される前にmapの可視領域の範囲の値を保存します。rotationが完了した後、表示領域が以前とほぼ同じになるように、保存した値を使用してください。

  • view階層が複雑な場合は、viewをスナップショットイメージと置き換えます。

    たくさんのviewのアニメーションがパフォーマンス問題を引き起こしている場合、一時的にそれらのviewをimage viewで置き換え、rotationが完了した後、viewを再度追加し、image viewを削除します。

  • rotationの後に表示しているテーブルのコンテンツをリロードします。

    rotationが完了した時に強制的にリロードを実行することで、新たに表示されるようになったテーブルの行のデータが適切に表示されるようにします。

  • アプリの状態の情報を更新するには、rotation通知を使用します。

    アプリがコンテンツをどのように表示するかを決めるために、現在の向きを使用している場合は、表示を変え必要な調整を行うために、view controllerのrotationメソッド(またはそれに対応するdevice orientation notification)を使用してください。

custom view controllerクラスをコンテンツの表示と編集の両方に使用する場合は、setEditing:animated:メソッドをオーバーライドしてください。このメソッドが呼ばれたら、指定されたモードとマッチするよう、view controllerのviewを追加したり隠したり調整したりしてください。例えば、viewが編集可能であることを伝えるために、コンテンツや見た目を変える必要があるかもしれません。view controllerがテーブルを管理している場合は、テーブルを適切なモードにするために、テーブル自身のsetEditing:animated:を呼ぶことができます。

表示モードと編集モードをトグルで行き来する場合、通常はview階層を完全に置き換えたりはしません。setEditing:animated:メソッドのポイントは、既存のviewを少しだけ変化させることができることです。もし、編集するために新しいviewを表示したい場合は、新しいview controllerをpresentしたり、navigation controllerを使用すべきです。

- (void)setEditing:(BOOL)flag animated:(BOOL)animated
{
    [super setEditing:flag animated:animated];
    if (flag == YES){
        // Change views to edit mode.
    }
    else {
        // Save the changes if needed and change the views to noneditable.
    }
}

編集可能なviewを使用するためによく使用される場所は、navigationインターフェイスです。navigation interfaceを実装する場合、navigation bar内に編集ボタンを含めることができます。このボタンを取得するには、view controller内でeditButtonItemメソッドを呼びます。このボタンをタップすると、ボタンは自動的に編集ボタンと完了ボタンをトグルし、view controllerのsetEditing:animated:を呼びます。モードを変えるために、このメソッドをプログラムから呼ぶこともできますし、view controllerのeditingプロパティの値を変えることもできます。

container view controllerは、iOSアプリ設計の重要な部分です。これを使用することで、アプリを小さくてシンプルな、特定のタスクに専念するview controllerのパーツに分解することができます。containerを用いることで、これらのview controllerを協力させ、シームレスなインターフェイスを表示することができます。

多くの点で、container view controllerは、content view controllerに似ています。viewとコンテンツを管理し、他のオブジェクトとの調整を行い、responder chainの中でイベントに応答します。そのため、container controllerを設計する前に、content view controllerの設計について知っている必要があります。

containerを設計す場合、view controller間に明確な親子関係を作る必要があります。containerが親で、他のview controllerが子供です。さらに、viewの間にも接続な関係があります。containerは、他のview controllerのcontent viewを自身のview階層に追加します。containerのview階層に子供のviewが表示されている場合、containerはchild view controllerに対して接続を確立し、全てのview controllerイベントが親から子へ送信されるようにします。

A container view controller’s view hierarchy contains another controller’s views

containerがルールを作り、子供はそのルールに従う必要があります。view階層の中で、いつ子供のコンテンツが表示されるかを決めるのは、親次第です。containerは、view階層の中のどこにそのviewを配置し、どのようなサイズとポジションにするかを決めます。この設計の原則は、content view controllerのものと同じです。view controllerは、自分ののview階層を管理する責任があり、他のクラスがそのコンテンツを操作すべきではありません。必要であれば、containerクラスは、その振る舞いをコントロールするためのパブリックなメソッドやプロパティを公開しても構いません。例えば、もし他のオブジェクトが、containerに新しいviewを表示することを伝えられるようにする必要がある場合は、containerクラスはこの遷移を起こすパブリックなメソッドを公開する必要があります。view階層を変化させる実際の実装は、containerクラスの中にあるべきです。常にview controllerは自分のview階層の責任を持つようにすることで、containerと子供の責任の分担が明確になります。

containerを実装する際のゴールは、他のview controllerのviewをcontainerのview階層のサブツリーとして追加できるようにすることです。子供のviewを追加する際に、両方のview controllerにイベントが配送されるようにする必要がありますが、これはview controllerをcontainerの子供として明示的に関連付けることによってなされます。

子供の追加と削除

追加する場合。

- (void) displayContentController: (UIViewController*) content;
{
   [self addChildViewController:content];                 // 1
   content.view.frame = [self frameForContentController]; // 2
   [self.view addSubview:self.currentClientView];
   [content didMoveToParentViewController:self];          // 3
}
  1. 子供を追加するために、containerのaddChildViewController:メソッドを呼びます。addChildViewController:メソッドを呼ぶと、自動的に子供のwillMoveToParentViewController:メソッドが呼ばれます。
  2. 子供のviewを取得するために、viewプロパティにアクセスし、自分のview階層にそのviewを追加します。containerは、viewを追加する前に子供のサイズと位置を設定します。このサンプルでは明示的にframeを設定していますが、layout constraintsを使用することもできます。
  3. 処理が完了したことを知らせるために、明示的に子供のdidMoveToParentViewController:メソッドを呼びます。

削除する場合。

- (void) hideContentController: (UIViewController*) content
{
   [content willMoveToParentViewController:nil];  // 1
   [content.view removeFromSuperview];            // 2
   [content removeFromParentViewController];      // 3
}
  1. 子供に削除されることを伝えるために、子供のwillMoveToParentViewController:メソッドをnilのパラメータと共に呼びます。
  2. view階層をクリーンナップします。
  3. containerからさくじょするために、子供のremoveFromParentViewControllerメソッドを呼びます。removeFromParentViewControllerメソッドを呼ぶと、自動的に子供のdidMoveToParentViewController:メソッドが呼ばれます。

追加と削除を同時に行いたい場合。

- (void) cycleFromViewController: (UIViewController*) oldC
            toViewController: (UIViewController*) newC
{
    [oldC willMoveToParentViewController:nil];                        // 1
    [self addChildViewController:newC];
 
    newC.view.frame = [self newViewStartFrame];                       // 2
    CGRect endFrame = [self oldViewEndFrame];
 
    [self transitionFromViewController: oldC toViewController: newC   // 3
          duration: 0.25 options:0
          animations:^{
             newC.view.frame = oldC.view.frame;                       // 4
             oldC.view.frame = endFrame;
           }
           completion:^(BOOL finished) {
             [oldC removeFromParentViewController];                   // 5
             [newC didMoveToParentViewController:self];
            }];
}
  1. 両方のview controllerの遷移を開始します。
  2. 遷移のアニメーションに使用される、2つの新しいframeの位置を計算します。
  3. 入れ替えを行うために、transitionFromViewController:toViewController:duration:options:animations:completion:メソッドを呼びます。このメソッドは、自動的に新しいviewを追加し、アニメーションし、古いviewを削除します。
  4. viewを入れ替えるアニメーションのステップです。
  5. 最後に2つの通知を送信することにより処理を完了します。
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment