web-dev-qa-db-ja.com

UIViewをサブクラス化するための適切な実践?

私はいくつかのカスタムUIViewベースの入力コントロールに取り組んでおり、ビューを設定するための適切なプラクティスを確認しようとしています。 UIViewControllerを使用する場合、loadViewおよび関連するviewWillviewDidメソッドを使用するのは非常に簡単ですが、UIViewをサブクラス化するとき、最も近いメソッドは`awakeFromNibです。 drawRect、およびlayoutSubviews。 (セットアップとティアダウンコールバックの観点から考えています。)私の場合、フレームビューと内部ビューをlayoutSubviewsにセットアップしていますが、画面には何も表示されません。

ビューに適切な高さと幅を持たせるための最良の方法は何ですか? (私の質問は、2つの答えがあるかもしれませんが、自動レイアウトを使用しているかどうかに関係なく適用されます。)適切な「ベストプラクティス」は何ですか。

151
Moshe

Appleは、ドキュメントでUIViewをサブクラス化する方法を明確に定義しました。

以下のリストを確認してください。特にinitWithFrame:layoutSubviewsをご覧ください。前者はUIViewのフレームを設定するためのものであり、後者はフレームとそのサブビューのレイアウトを設定するためのものです。

initWithFrame:は、プログラムでUIViewをインスタンス化する場合にのみ呼び出されることも忘れないでください。 nibファイル(またはストーリーボード)から読み込む場合は、initWithCoder:が使用されます。 initWithCoder:では、フレームはまだ計算されていないため、Interface Builderで設定したフレームを変更することはできません。 この回答では 提案されているように、フレームをセットアップするためにinitWithFrame:からinitWithCoder:を呼び出すことを考えるかもしれません。

最後に、UIViewをnib(またはストーリーボード)からロードすると、awakeFromNibが呼び出されたときに階層内のすべてのビューがアーカイブ解除されて初期化されていることが保証されるため、カスタムフレームとレイアウトの初期化を実行するawakeFromNibの機会もあります。

NSNibAwaking のドキュメントから( awakeFromNib のドキュメントに置き換えられました):

他のオブジェクトへのメッセージは、awakeFromNib内から安全に送信できます。その時点までに、すべてのオブジェクトがアーカイブ解除および初期化されることが保証されます(もちろん、必ずしも起こされる必要はありません)

また、自動レイアウトでは、ビューのフレームを明示的に設定しないでください。代わりに、フレームがレイア​​ウトエンジンによって自動的に計算されるように、十分な制約のセットを指定することになっています。

ドキュメント から直接:

オーバーライドするメソッド

初期化

  • initWithFrame:このメソッドを実装することをお勧めします。このメソッドに加えて、またはこのメソッドの代わりに、カスタム初期化メソッドを実装することもできます。

  • initWithCoder:Interface Builder nibファイルからビューをロードし、ビューでカスタム初期化が必要な場合、このメソッドを実装します。

  • layerClassこのメソッドは、ビューでバッキングストアに別のCore Animationレイヤーを使用する場合にのみ実装します。たとえば、OpenGL ESを使用して描画を行う場合、このメソッドをオーバーライドしてCAEAGLLayerクラスを返します。

描画と印刷

  • drawRect:ビューがカスタムコンテンツを描画する場合、このメソッドを実装します。ビューでカスタム描画が行われない場合は、このメソッドをオーバーライドしないでください。

  • drawRect:forViewPrintFormatter:印刷中にビューのコンテンツを異なる方法で描画したい場合にのみ、このメソッドを実装します。

制約

  • requiresConstraintBasedLayoutビュークラスで制約が適切に機能する必要がある場合、このクラスメソッドを実装します。

  • updateConstraintsビューでサブビュー間にカスタム制約を作成する必要がある場合は、このメソッドを実装します。

  • alignmentRectForFrame:frameForAlignmentRect:これらのメソッドを実装して、ビューの整列方法をオーバーライドします他のビュー。

レイアウト

  • sizeThatFits:ビューのサイズを通常のサイズ変更操作とは異なるデフォルトサイズにする場合は、このメソッドを実装します。たとえば、このメソッドを使用して、サブビューを正しく表示できないポイントまでビューが縮小しないようにすることができます。

  • layoutSubviews制約または自動サイズ変更動作が提供するよりも、サブビューのレイアウトをより正確に制御する必要がある場合は、このメソッドを実装します。

  • didAddSubview:willRemoveSubview:必要に応じてこれらのメソッドを実装して、追加と削除を追跡しますサブビューの。

  • willMoveToSuperview:didMoveToSuperview必要に応じてこれらのメソッドを実装し、ビュー階層の現在のビュー。

  • willMoveToWindow:didMoveToWindow必要に応じてこれらのメソッドを実装して、あなたの動きを追跡します別のウィンドウに表示します。

イベント処理:

  • touchesBegan:withEvent:touchesMoved:withEvent:touchesEnded:withEvent:touchesCancelled:withEvent:タッチイベントを直接処理する必要がある場合は、これらのメソッドを実装します。 (ジェスチャーベースの入力の場合は、ジェスチャー認識機能を使用します。)

  • gestureRecognizerShouldBegin:ビューがタッチイベントを直接処理し、付加されたジェスチャレコグナイザーが追加のアクションをトリガーしないようにする場合は、このメソッドを実装します。

286

これは今でもGoogleで高く浮上しています。以下は、Swiftの更新された例です。

didLoad関数を使用すると、すべてのカスタム初期化コードを配置できます。他の人が述べたように、didLoadは、ビューがinit(frame:)を介してプログラムで作成されたとき、またはXIB deserializerがXIBをマージするときに呼び出されます= init(coder:)を介してビューにテンプレート

layoutSubviewsおよびupdateConstraintsは、ほとんどのビューで複数回呼び出されます。これは、ビューの境界が変更されたときの高度なマルチパスレイアウトと調整を目的としています。個人的には、CPUサイクルを消費し、すべてが頭痛の種になるため、可能な限りマルチパスレイアウトを避けています。また、制約コードを無効化することはめったにないので、初期化子自体に制約コードを配置します。

import UIKit

class MyView: UIView {
  //-----------------------------------------------------------------------------------------------------
  //Constructors, Initializers, and UIView lifecycle
  //-----------------------------------------------------------------------------------------------------
  override init(frame: CGRect) {
      super.init(frame: frame)
      didLoad()
  }

  required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    didLoad()
  }

  convenience init() {
    self.init(frame: CGRectZero)
  }

  func didLoad() {
    //Place your initialization code here

    //I actually create & place constraints in here, instead of in
    //updateConstraints
  }

  override func layoutSubviews() {
     super.layoutSubviews()

     //Custom manually positioning layout goes here (auto-layout pass has already run first pass)
  }

  override func updateConstraints() {
    super.updateConstraints()

    //Disable this if you are adding constraints manually
    //or you're going to have a 'bad time'
    //self.translatesAutoresizingMaskIntoConstraints = false

    //Add custom constraint code here
  }
}
35
seo

Apple documentation にはまともな要約があり、これはiTunesで利用できる無料の Stanfordコース で十分にカバーされています。 TL; DRバージョンをここに示します:

クラスが主にサブビューで構成されている場合、それらを割り当てる適切な場所はinitメソッドです。ビューには、ビューがコードからインスタンス化されているか、nib /ストーリーボードからインスタンス化されているかに応じて、呼び出される可能性のある2つの異なるinitメソッドがあります。独自のsetupメソッドを作成し、initWithFrame:メソッドとinitWithCoder:メソッドの両方から呼び出します。

カスタム描画を行う場合は、ビューでdrawRect:を実際にオーバーライドする必要があります。ただし、カスタムビューのほとんどがサブビューのコンテナである場合、おそらくそれを行う必要はありません。

縦向きか横向きかに応じてサブビューを追加または削除したい場合にのみ、layoutSubViewsをオーバーライドします。それ以外の場合は、そのままにしておく必要があります。

14
dpassage

layoutSubviewsは、ビュー自体ではなく、子ビューにフレームを設定するためのものです。

UIViewの場合、指定されたコンストラクタは通常initWithFrame:(CGRect)frameであり、フレームを(またはinitWithCoder:で)設定する必要があります。おそらくフレームに渡された値は無視されます。別のコンストラクタを提供して、そこにフレームを設定することもできます。

1
proxi