web-dev-qa-db-ja.com

iPhone:Interface Builderの一部とコードを含む再利用可能なコンポーネント(コントロール)を作成します

IPhone用の再利用可能なコンポーネント(カスタムコントロール)を作成したいと思います。これは、ビューに事前に配置されたいくつかの標準コントロールと、それに関連するコードで構成されています。私の目標は次のとおりです。

  1. Interface Builderを使用して、カスタムコントロールにサブビューをレイアウトできるようにしたいと思います。
  2. どういうわけか全体をパッケージ化して、結果のカスタムコンポーネントを他のビューにかなり簡単にドラッグアンドドロップできるようにしたいのですが、多数のアウトレットなどを手動で再配線する必要はありません。 (少し手作業で再配線しても問題ありません。何トンもやりたくないだけです。)

もっと具体的に、私のコントロールが何をするのかを具体的にお話ししましょう。私のアプリでは、ユーザーが入力したデータを検証するためにWebサービスにアクセスする必要がある場合があります。 Webサービスからの応答を待っている間に、スピナー(アクティビティインジケーター)を表示したいと思います。 Webサービスが成功コードで応答する場合、「成功」チェックマークを表示したいと思います。 Webサービスがエラーコードで応答した場合、エラーアイコンとエラーメッセージを表示したいと思います。

これを行うシングルユースの方法は非常に簡単です。UIActivityIndi​​catorView、2つのUIImage(1つは成功アイコン用、もう1つはエラーアイコン用)、およびエラーメッセージ用のUILabelを含むUIViewを作成するだけです。これがスクリーンショットで、関連する部分が赤でマークされています。

alt text

次に、部品をコンセントに配線し、コントローラーにコードを挿入します。

しかし、それらを再利用できるように、これらの部分(コードとビューの小さなコレクション)をどのようにパッケージ化するのですか?これが私を途中まで連れて行ってくれたいくつかのことですが、それほど素晴らしいものではありません:

  • ビューとコントロールのコレクションをライブラリのカスタムオブジェクトセクションにドラッグできます。その後、それらをドラッグして他のビューに戻すことができます。しかし、(a)どの画像が2つのUIImageに関連付けられているかを忘れ、(b)4つまたは5つのコンセントを手動で再配線することが多く、(c)最も重要なことに、これではコードが実行されません。 (おそらく、コードを配線する簡単な方法はありますか?)
  • IBPluginを作成できると思います。それが役立つかどうかはわかりませんし、それは大変な作業のようです。また、IBPluginsがiPhone開発で機能するかどうかは私には完全にはわかりません。
  • 「うーん、これに関連付けられたコードがあります。コントローラーのような匂いがします」と思ったので、XIBファイルに関連付けられたカスタムコントローラー(例:WebServiceValidatorController)を作成してみました。それは実際には本当に有望だと感じますが、その時点では、InterfaceBuilderでこのコンポーネントを他のビューにドラッグする方法がわかりません。 WebServiceValidatorControllerはコントローラーであり、ビューではないため、ドキュメントウィンドウにドラッグできますが、ビューにはドラッグできません。

明らかな何かが欠けているような気がします...

40
Mike Morearty

IBで結果を使用しないことを除いて、同様の構造を作成しましたが、コードを使用してインスタンス化します。それがどのように機能するかを説明し、最後に、それを使用してあなたが求めていることを達成するためのヒントを示します。

空のXIBファイルから始めて、トップレベルに1つのカスタムビューを追加します。そのカスタムビューを自分のクラスとして構成します。以下のビュー階層では、必要に応じてサブビューを作成および構成します。

カスタムビュークラスにすべてのIBOutletを作成し、そこで接続します。この演習では、「ファイルの所有者」を完全に無視します。

ここで、ビューを作成する必要がある場合(通常、必要な数のビューを作成するためにwhile/for-loopの一部としてコントローラーで)、次のようなNSBundleの機能を使用します。

- (void)viewDidLoad
{
    CGRect fooBarViewFrame = CGRectMake(0, 0, self.view.bounds.size.width, FOOBARVIEW_HEIGHT);
    for (MBSomeData *data in self.dataArray) {
        FooBarView *fooBarView = [self loadFooBarViewForData:data];
        fooBarView.frame = fooBarViewFrame;
        [self.view addSubview:fooBarView];

        fooBarViewFrame = CGRectOffset(fooBarViewFrame, 0, FOOBARVIEW_HEIGHT);
    }
}

- (FooBarView*)loadFooBarViewForData:(MBSomeData*)data
{
    NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:@"FooBarView" owner:self options:nil];
    FooBarView *fooBarView = [topLevelObjects objectAtIndex:0];
    fooBarView.barView.amountInEuro = data.amountInEuro;
    fooBarView.barView.gradientStartColor = data.color1;
    fooBarView.barView.gradientMidColor = data.color2;
    fooBarView.titleLabel.text = data.name;
    fooBarView.titleLabel.textColor = data.nameColor;
    return fooBarView;
}

Nibの所有者をselfに設定したことに注意してください。「ファイルの所有者」を何にも接続しなかったので、害はありません。このペン先をロードすることによる唯一の価値ある結果は、その最初の要素である私の見解です。

このアプローチをIBでビューを作成するために適応させたい場合、それは非常に簡単です。メインカスタムビューにサブビューの読み込みを実装します。サブビューのフレームをメインビューの境界に設定して、同じサイズになるようにします。メインビューは、実際のカスタムビューのコンテナになり、外部コードのインターフェイスにもなります。サブビューの必要なプロパティのみを開き、残りはカプセル化されます。次に、カスタムビューをIBにドロップし、クラスとして構成して、通常どおりに使用します。

12
Michal

これが私が同様の問題を解決する方法です:私はしたかった:

  • 複数のUIKitコンポーネント(および他のネストされたウィジェットモジュールクラス!)から構築された複雑なビューを実装する、再利用可能な自己完結型の「ウィジェットモジュールクラス」を作成します。これらのウィジェットクラスの上位レベルの顧客クラスは、UILabelとは何か、ウィジェット内のUIImageViewとは何であるかを気にせず、顧客クラスは「スコアの表示」や「チームロゴの表示」などの概念のみを気にします。

  • ウィジェットモジュールクラス内で、インターフェイスビルダーを使用してウィジェットとフックアップアウトレットのUIをレイアウトします

  • ウィジェットの上位レベルの顧客の場合、IBなどのカスタムプラグインを設計しなくても、インターフェイスビルダーの顧客のビュー内にウィジェットのフレームを配置できるようにしたかったのです。

これらのウィジェットはトップレベルのコントローラーではないため、UIViewControllerのサブクラスであることは意味がありません。次に、一度に複数のAppleを表示画面に表示しないようにするためのVCアドバイスもあります。また、次のようなアドバイスや意見がたくさんあります。 「MVC!MVC!ビューとコントロールを分離する必要があります!MVC!」ということで、UIViewをサブクラス化したり、UIViewサブクラス内にアプリロジックを配置したりすることは強く推奨されません。

しかし、とにかくそれを行うことにしました(サブクラスUIView)。私は何度かブロックを回っていますが(iPhone SDK/UIKitはまだかなり新しいです)、醜いデザインに非常に敏感で、問題を引き起こす可能性がありますが、率直に言って問題はわかりませんUIViewをサブクラス化し、アウトレットとアクションを追加します。実際、再利用可能なウィジェットをUIViewControllerベースではなく、UIViewのサブクラスにすることには多くの利点があります。

  • ウィジェットクラスの直接インスタンスをカスタマービューのインターフェイスビルダーレイアウトに配置できます。ウィジェットビューのUIViewフレームは、カスタマークラスがxibから読み込まれるときに適切に初期化されます。これは、顧客に「プレースホルダービュー」を配置し、プログラムでウィジェットモジュールをインスタンス化し、ウィジェットのフレームをプレースホルダーのフレームに設定してから、プレースホルダービューをウィジェットのビューに交換するよりもはるかにクリーンです。

  • クリーンでシンプルなxibファイルを作成して、インターフェイスビルダーでウィジェットのコンポーネントをレイアウトできます。ファイルの所有者はウィジェットクラスに設定されます。 nibファイルには、すべてのGUIが配置されたルート(Vanilla)UIViewが含まれ、ウィジェットのawakeFromNib:メソッドで、このnibビューがサブビューとしてウィジェット自体に追加されます。フレームサイズの調整は、必要に応じて、ウィジェットコード内で完全にここで処理できます。そのようです:

- (void) awakeFromNib
{
    [[NSBundle mainBundle] loadNibNamed:@"MyWidgetView" owner:self options:nil];
    [self addSubview:self.view];
}
  • ウィジェットは、独自のawakeFromNibメソッドでNSBundleのloadNibNamed:owner:optionsメソッドを使用してユーザーインターフェイスを自己初期化するため、ウィジェットの顧客クラスへの統合はクリーンです。UIViewをIBの顧客ビューにドロップし、UIViewのクラスを次のように変更します。 MyWidgetView、および顧客のinitまたはviewDidLoadで、自分で作成したウィジェットのAPI(setScore:setTeamImage:など)を使用して、ウィジェットの表示にデフォルトを設定します。

この方法でUIViewをサブクラス化して再利用可能な「ウィジェット」を作成することと、UIViewControllerを使用することの間に、結果のコードに本質的な違いはないように思われます。どちらの場合も、.hファイルには、ウィジェットクラスのメンバー、アウトレット、アクション宣言、および特別なAPI宣言が含まれています。どちらの場合も、.mファイルにはアクションの実装と特別なAPIの実装が含まれています。そして、ビューISはコントロールから分離されています-ビューはxibファイルにあります!

要約すると、これは複雑な再利用可能なビューウィジェットをパッケージ化するために私が行うことです。

  • ウィジェットクラスをUIViewのサブクラスにします
  • ツールライブラリからUIViewをドラッグし、クラスを「MyWidgetClass」に変更することで、IBのアプリの他のビューでウィジェットを好きな場所にインスタンス化します。
  • ルートVanillaUIView内のペン先のIBでウィジェットのUIを設計します。
  • ウィジェットクラスのawakeFromNib:メソッドで、ウィジェットのUIをxibからロードし、[self addSubview:self.theXibRootView]を実行します。

  • UIViewControllerと同じようにウィジェットをコーディングします:アウトレット、アクション、特別なAPI

再利用可能なウィジェットを作成するための他の構造システムと、このアプローチに勝る利点について聞いてみたいと思います。

ウィジェットがイベントを顧客クラスに報告する必要がある場合は、UIViewControllerの場合と同様に、顧客がウィジェットのデリゲートを自分自身に設定するデリゲートプロトコルを使用するだけです。

21
Bogatyr