web-dev-qa-db-ja.com

UIViewのsetNeedsLayout、layoutIfNeeded、およびlayoutSubviewsの関係は何ですか?

UIView'ssetNeedsLayoutlayoutIfNeeded、およびlayoutSubviewsの各メソッドの関係について、誰でも明確な説明を提供できますか?そして、3つすべてが使用される実装例。ありがとう。

混乱するのは、カスタムビューにsetNeedsLayoutメッセージを送信すると、このメソッドの後に呼び出されるのはlayoutSubviewsであり、layoutIfNeededをスキップすることです。ドキュメントから、フローはsetNeedsLayout>であると予想されますlayoutIfNeededが呼び出されます> layoutSubviewsが呼び出されます。

105
Tarfa

私はまだ自分でこれを理解しようとしていますので、これを懐疑的に考えて、エラーが含まれていても許してください。

setNeedsLayoutは簡単です。UIViewのどこかに、レイアウトが必要であるとマークするフラグを設定するだけです。これにより、次の再描画が行われる前に、ビューでlayoutSubviewsが強制的に呼び出されます。 autoresizesSubviewsプロパティのため、多くの場合、これを明示的に呼び出す必要はありません。それが設定されている場合(デフォルト)、ビューのフレームを変更すると、ビューでサブビューがレイアウトされます。

layoutSubviewsは、すべての興味深いことを行う方法です。必要に応じて、レイアウトのdrawRectと同等です。簡単な例は次のとおりです。

-(void)layoutSubviews {
    // Child's frame is always equal to our bounds inset by 8px
    self.subview1.frame = CGRectInset(self.bounds, 8.0, 8.0);
    // It seems likely that this is incorrect:
    // [self.subview1 layoutSubviews];
    // ... and this is correct:
    [self.subview1 setNeedsLayout];
    // but I don't claim to know definitively.
}

AFAIK layoutIfNeededは通常、サブクラスでオーバーライドされることを意図していません。これは、ビューをレイアウトするときに呼び出すことを意図したメソッドです。 Appleの実装は次のようになります。

-(void)layoutIfNeeded {
    if (self._needsLayout) {
        UIView *sv = self.superview;
        if (sv._needsLayout) {
            [sv layoutIfNeeded];
        } else {
            [self layoutSubviews];
        }
    }
}

ビューでlayoutIfNeededを呼び出して、ビュー(および必要に応じてそのスーパービュー)をすぐにレイアウトするように強制します。

103
n8gray

場合によっては、setNeedsLayoutに続いてlayoutIfNeededを呼び出す必要があるというn8grayの答えを付け加えます。

たとえば、サブビューの配置が複雑で、autoresizingMaskまたはiOS6 AutoLayoutでは実行できないUIViewを拡張するカスタムビューを作成したとします。 layoutSubviewsをオーバーライドすることにより、カスタムポジショニングを行うことができます。

例として、contentViewプロパティとcontentViewの周囲にマージンを設定できるedgeInsetsプロパティを持つカスタムビューがあるとします。 layoutSubviewsは次のようになります。

- (void) layoutSubviews {
    self.contentView.frame = CGRectMake(
        self.bounds.Origin.x + self.edgeInsets.left,
        self.bounds.Origin.y + self.edgeInsets.top,
        self.bounds.size.width - self.edgeInsets.left - self.edgeInsets.right,
        self.bounds.size.height - self.edgeInsets.top - self.edgeInsets.bottom); 
}

edgeInsetsプロパティを変更するたびにフレームの変更をアニメーション化できるようにするには、次のようにedgeInsetsセッターをオーバーライドし、setNeedsLayoutに続いてlayoutIfNeeded

- (void) setEdgeInsets:(UIEdgeInsets)edgeInsets {
    _edgeInsets = edgeInsets;
    [self setNeedsLayout]; //Indicates that the view needs to be laid out 
                           //at next update or at next call of layoutIfNeeded, 
                           //whichever comes first 
    [self layoutIfNeeded]; //Calls layoutSubviews if flag is set
}

そのようにして、以下を行う場合、アニメーションブロック内のedgeInsetsプロパティを変更すると、contentViewのフレーム変更がアニメーション化されます。

[UIView animateWithDuration:2 animations:^{
    customView.edgeInsets = UIEdgeInsetsMake(45, 17, 18, 34);
}];

SetEdgeInsetsメソッドでlayoutIfNeededへの呼び出しを追加しない場合、layoutSubviewsは次の更新サイクルで呼び出されるため、アニメーションは機能しません。これは、アニメーションブロックの外部で呼び出すことに相当します。

SetEdgeInsetsメソッドでlayoutIfNeededのみを呼び出す場合、setNeedsLayoutフラグが設定されていないため、何も起こりません。

34
quentinadam