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


158

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

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

回答:


298

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

以下のリストをチェックし、特に見てみましょうinitWithFrame:layoutSubviews。前者はフレームを設定するためのものでUIView、後者はフレームとそのサブビューのレイアウトを設定するためのものです。

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

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

のドキュメントからNSNibAwaking(現在はのドキュメントに置き換えられていますawakeFromNib):

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

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

ドキュメントから直接:

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

初期化

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

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

  • layerClassこのメソッドを実装するのは、ビューでバッキングストアに別のコアアニメーションレイヤーを使用する場合のみです。たとえば、描画に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: ビューがタッチイベントを直接処理し、アタッチされたジェスチャー認識機能が追加のアクションをトリガーしないようにする場合は、このメソッドを実装します。


-(void)setFrame:(CGRect)frameはどうですか?
pfrank 2013

間違いなくオーバーライドできますが、どのような目的で使用しますか?
ガブリエレペトロネラ2013

フレームサイズまたは位置が変更されるたびにレイアウト/描画を変更するには
pfrank

1
どうlayoutSubviewsですか?
ガブリエレペトロネラ2013

stackoverflow.com/questions/4000664/…から、「これの問題は、サブビューがサイズを変更できるだけでなく、サイズの変更をアニメーション化できることです。UIViewがアニメーションを実行するとき、毎回layoutSubviewsを呼び出すことはありません。」個人的にはテストしていません
pfrank

38

これはまだGoogleで高まっています。以下は、swiftの更新された例です。

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

layoutSubviewsupdateConstraintsビューの大半に対して複数回呼ばれています。これは、ビューの境界が変更されたときの高度なマルチパスレイアウトと調整を目的としています。個人的には、マルチパスレイアウトは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
  }
}

initプロセスから呼び出されたメソッドとlayoutSubviewsおよびupdateConstraintsの間でレイアウト制約コードを分割する理由/理由を説明できますか?これらはすべて、レイアウトコードを配置するための3つの候補となる場所のようです。では、レイアウトコードを3つにいつ/何で/なぜ分割するかをどのようにして知るのでしょうか。
クレイエリス

3
updateConstraintsは使用しません。updateConstraintsは、ビューの階層がinitで完全に設定されていることがわかっているので便利です。階層にない2つのビューの間に制約を追加して例外を発生させることはできません。レイアウトパス中に制約が「無効化」されると、layoutSubviewsが呼び出されるため、無限再帰を簡単に引き起こす可能性があります。手動レイアウト設定(直接フレームを設定する場合のように、パフォーマンス上の理由がない限り、これ以上行う必要はほとんどありません)は、layoutSubviewsに含まれます。個人的に、私は、initに制約の作成を置く
SEO

カスタムレンダリングコードの場合、drawメソッドをオーバーライドする必要がありますか?
ペトルスセロン2018

14

Appleのドキュメントにはまともな要約があり、これはiTunesで入手できる無料のスタンフォードコースで十分にカバーされています。ここにTL; DRバージョンを示します。

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

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

layoutSubViews縦向きか横向きかによって、サブビューの追加や削除などを行う場合にのみオーバーライドします。それ以外の場合は、そのままにしておくことができます。


私はあなたの答えを使用して、ビュー(awakeFromNib)のsubViewのフレームを変更しましたがlayoutSubViews、うまくいきました。
航空機は

1

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

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


もっと詳しく教えてもらえますか?ビューのsubViewのフレームを設定する方法を教えてください。眺めはawakeFromNib
航空機

2016年に早送りします。おそらくフレームを設定してオートレイアウト(制約)を使用するべきではありません。ビューがXIB(またはストーリーボード)からのものである場合、サブビューは既にセットアップされているはずです。
プロキシ2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.