非表示のUIViewsを持つAutoLayout?


164

ビジネスロジックによってはUIViews、ほとんどの場合UILabels、を表示/非表示にするのはかなり一般的なパラダイムだと思います。私の質問は、フレームが0x0であるかのように、オートレイアウトを使用して非表示のビューに応答する最良の方法は何ですか?これは、1〜3個の機能の動的リストの例です。

動的機能リスト

現在、ボタンから最後のラベルまでの上部に10pxのスペースがあります。ラベルが非表示の場合は、上にスライドしません。今のところ、この制約へのアウトレットを作成し、表示しているラベルの数に応じて定数を変更しています。非表示のフレームの上にボタンを押し上げるために負の定数値を使用しているので、これは明らかに少しハックです。また、実際のレイアウト要素に制約されず、他の要素の既知の高さ/パディングに基づいた静的な計算をこっそりと行い、オートレイアウトが何のために構築されたかと明らかに戦うため、それも悪いことです。

動的ラベルに応じて新しい制約を作成することもできますが、これは、いくつかの空白を折りたたみようとするための多くの細かい管理と冗長性です。より良いアプローチはありますか?フレームサイズ0、0を変更し、オートレイアウトに制約を操作せずに機能させるには?ビューを完全に削除しますか?

正直なところ、非表示ビューのコンテキストから定数を変更するだけでは、簡単な計算で1行のコードが必要になります。を使用して新しい制約を再作成すること constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:は非常に重いようです。

2018年2月を編集UIStackViewsでベンの答えを見る


この質問をライアンに感謝します。あなたが尋ねたように、私は事件のために何をすべきかを真剣に考えていました。autolayoutのチュートリアルをチェックアウトするたびに、ほとんどの人がraywenderlichチュートリアルサイトを参照しているので、理解するのが少し難しいと言っています。
ナシフ2014

回答:


82

UIStackViewおそらくiOS 9以降を使用する方法です。非表示のビューを処理するだけでなく、正しく設定されていれば、余計なスペースやマージンも削除されます。


8
UITableViewCellでUIStackViewを使用する場合は注意してください。私にとって、ビューが非常に複雑になると、セルがスクロールイン/スクロールアウトされるたびにスクロールが途切れるようになり始めました。StackViewは内部で制約を追加/削除しているため、スムーズなスクロールを行うには必ずしも効率的ではありません。
ケント

7
スタックビューがこれを処理する方法の小さな例を示すとよいでしょう。
lostintranslation 2016

@ケントこれはまだiOS 10の問題ですか?
クラシャロット2017年

3
5年後...これを更新して承認済みの回答に設定する必要がありますか?現在、3つのリリースサイクルで利用できます。
Ryan Romanchuk

1
@RyanRomanchukする必要があります、これは間違いなく今最高の答えです。ベン、ありがとう!
Duncan Luk、2018

234

ビューを表示/非表示にするための個人的な好みは、適切な幅または高さの制約を持つIBOutletを作成することです。

次に、constant値を0に更新して非表示にするか、表示する値を変更します。

この手法の大きな利点は、相対的な制約が維持されることです。たとえば、水平ギャップがxのビューAとビューBがあるとします。ビューAの幅constantがに設定されている0.f場合、ビューBは左に移動してそのスペースを埋めます。

重い操作である制約を追加または削除する必要はありません。制約を更新するだけでconstantうまくいきます。


1
丁度。この例では、水平ギャップを使用して、ビューBをビューAの右側に配置します。私はこのテクニックをいつも使用していて、とてもうまくいきます
Max MacLeod 2013年

9
@MaxMacLeod念のため:2つのビュー間のギャップがxの場合、ビューBをビューAの位置から開始するには、ギャップ制約定数も0に変更する必要がありますよね?
kernix 2014年

1
@kernixはい、2つのビューの間に水平方向のギャップ制約が必要な場合。もちろん定数0はギャップがないことを意味します。したがって、ビューAの幅を0に設定してビューAを非表示にすると、ビューBがコンテナと同じ高さになるように左に移動します。ところで、賛成票をありがとう!
Max MacLeod 2014年

2
@MaxMacLeodご回答ありがとうございます。他のビューを含むUIViewでこれを試しましたが、高さの制約を0に設定すると、そのビューのサブビューが非表示になりません。このソリューションは、サブビューを含むビューで機能するはずですか?ありがとう!
shaunlim 2014

6
また、他のビューを含むUIViewで機能するソリューションを評価します...
Mark Gibaud 2014年

96

0非表示のときに定数を使用し、再び表示するときに別の定数を使用するという解決策は機能しますが、コンテンツのサイズが柔軟である場合は満足できません。柔軟なコンテンツを測定し、一定に戻す必要があります。これは間違っていると感じ、サーバーまたはUIイベントが原因でコンテンツのサイズが変更されると問題が発生します。

より良い解決策があります。

要素を非表示にするときに高さ0のルールを高い優先度に設定して、自動レイアウトスペースを占有しないようにするという考え方です。

その方法は次のとおりです。

1.優先度の低いインターフェイスビルダーで幅(または高さ)を0に設定します。

幅0ルールの設定

優先度が低いため、Interface Builderは競合について怒鳴りません。優先度を一時的に999に設定して高さの動作をテストします(1000はプログラムで変更することが禁止されているため、使用しません)。インターフェースビルダーはおそらく競合する制約について叫ぶでしょう。これらを修正するには、関連オブジェクトの優先度を900程度に設定します。

2.アウトレットを追加して、コードで幅制約の優先度を変更できるようにします。

コンセント接続

3.要素を非表示にするときに優先度を調整します。

cell.alertTimingView.hidden    = place.closingSoon != true
cell.alertTimingWidth.priority = place.closingSoon == true ? 250 : 999

@SimplGyこの場所は何ですか?
Niharika 2017

それは一般的なソリューションの一部ではありません、@ Niharika、それは私の場合のビジネスルールでした(レストランがすぐに閉店していない場合は、ビューを表示します)。
SimplGy 2017

11

この場合、Authorラベルの高さを適切なIBOutletにマッピングします。

@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;

また、制約の高さを0.0fに設定すると、「パディング」が保持されます。これは、[再生]ボタンの高さで可能になるためです。

cell.authorLabelHeight.constant = 0; //Hide 
cell.authorLabelHeight.constant = 44; //Show

ここに画像の説明を入力してください


9

ここには多くの解決策がありますが、私の通常のアプローチは再び異なります:)

ホルヘアリマニーとTMinの答えに似た2つの制約セットを設定します。

ここに画像の説明を入力してください

マークされた3つの制約はすべて、定数の値が同じです。A1およびA2とマークされた制約の優先度は500に設定され、Bとマークされた制約の優先度は250(またはUILayoutProperty.defaultLowコード内)に設定されます。

制約BをIBOutletに接続します。次に、要素を非表示にするときに、制約の優先度を高(750)に設定するだけです。

constraintB.priority = .defaultHigh

ただし、要素が表示されたら、優先度を低(250)に戻します。

constraintB.priority = .defaultLow

isActive制約Bを変更するだけのこのアプローチの(確かにマイナーな)利点は、一時的な要素が他の方法でビューから削除された場合でも機能する制約があることです。


これには、ストーリーボードや要素の高さや幅などのコードに重複する値がないという利点があります。とてもエレガント。
Robin Daugherty、2018年

ありがとう!! それは私を助けました
Parth Barot

素晴らしいアプローチ、ありがとう
バジル

5

ビューをサブクラス化してオーバーライドしますfunc intrinsicContentSize() -> CGSizeCGSizeZeroビューが非表示の場合は戻るだけです。


2
これは素晴らしい。invalidateIntrinsicContentSizeただし、一部のUI要素にはトリガーが必要です。あなたはオーバーライドされた中でそれを行うことができますsetHidden
DrMickeyLauer 2016年

5

UILabelが領域を占有しないようにするには、UILabelを非表示にし、そのテキストを空の文字列に設定する必要があることがわかりました。(iOS 9)

この事実/バグを知っていると、一部の人々がレイアウトを単純化するのに役立つ可能性があり、おそらく元の質問のレイアウトでさえもそうなので、私はそれを投稿すると思いました。


1
隠す必要はないと思います。テキストを空の文字列に設定するだけで十分です。
ロブVS

4

UIKitこの望ましい振る舞いのために提供されるよりエレガントなアプローチがないことに私は驚いています。できることをしたいというのは非常に一般的なことのようです。

制約を接続してIBOutletsそれらの定数を0不快に感じた(そしてNSLayoutConstraintビューにサブビューがあったときに警告を引き起こした)ので、自動レイアウト制約を持つを非表示/表示するシンプルでステートフルなアプローチを提供する拡張を作成することにしましたUIView

ビューを非表示にし、外部の制約を削除するだけです。ビューを再度表示すると、拘束が元に戻ります。唯一の注意点は、周囲のビューに柔軟なフェイルオーバー制約を指定する必要があることです。

編集 この回答はiOS 8.4以下を対象としています。iOS 9では、このUIStackViewアプローチを使用してください。


1
これも私の好みのアプローチです。これは、ビューをゼロサイズに押しつぶすだけではないため、他の回答に対する大幅な改善です。スカッシュアプ​​ローチでは、かなり些細な支払い以外の問題が発生します。これの例は、隠されたアイテムがこの他の答えにある大きなギャップです。そして、前述のように制約違反が発生する可能性があります。
ベンジョン、2015年

@DanielSchlaug指摘してくれてありがとう。MITのライセンスを更新しました。ただし、誰かがこのソリューションを実装するたびに、メンタルハイタッチが必要です。
Albert Bori

4

ベストプラクティスは、すべてに適切なレイアウト制約が設定されたら、周囲のビューを移動して制約をIBOutletプロパティに接続する方法に応じて、高さまたは制約付きを追加します。

プロパティが strong

コードでは、定数を0に設定してアクティブ化し、コンテンツを非表示にするか、非アクティブ化してコンテンツを表示する必要があります。これは、定数値をいじって保存を復元するよりも優れています。layoutIfNeeded後で電話することを忘れないでください。

非表示にするコンテンツがグループ化されている場合、ベストプラクティスは、すべてをビューに入れ、そのビューに制約を追加することです。

@property (strong, nonatomic) IBOutlet UIView *myContainer;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *myContainerHeight; //should be strong!!

-(void) showContainer
{
    self.myContainerHeight.active = NO;
    self.myContainer.hidden = NO;
    [self.view layoutIfNeeded];
}
-(void) hideContainer
{
    self.myContainerHeight.active = YES;
    self.myContainerHeight.constant = 0.0f;
    self.myContainer.hidden = YES;
    [self.view layoutIfNeeded];
}

セットアップが完了したら、制約を0に設定してからIntefaceBuilderでテストしてから、元の値に戻すことができます。他の制約の優先順位をチェックすることを忘れないでください。そうすれば、非表示になっても競合はまったく発生しません。それをテストする他の方法は、それを0にして優先順位を0に設定することですが、再び最高の優先順位に戻すことを忘れないでください。


1
これは、制約とビューが常にビュー階層によって保持されるとは限らないためです。そのため、制約を非アクティブにしてビューを非表示にすると、ビュー階層が保持するかどうかに関係なく、それらを保持して再び表示します。
ホルヘアリマニー、2016年

3

制約を簡単に更新するためのカテゴリを作成します。

[myView1 hideByHeight:YES];

ここで回答:autolayout UIViewを非表示にする: 既存のNSLayoutConstraintを取得してこれを更新する方法

ここに画像の説明を入力してください


8
残念ながら、黄色の矢印が指しているところに、2倍の間隔があります。
ゾルタン2015年

上記の他のソリューションと同様に、私は思います。まったく理想的ではありません。
ベンジョン、2015年

2

私が好む方法は、ホルヘアリマニーが提案した方法とよく似ています。

複数の制約を作成することを好みます。最初に、2番目のラベルが表示されるときの制約を作成します。ボタンと2番目のラベルの間の制約のアウトレットを作成します(objcを使用している場合は、そのストロングを確認してください)。この制約は、ボタンが表示されているときのボタンと2番目のラベルの間の高さを決定します。

次に、2番目のボタンが非表示のときにボタンと上部ラベルの間の高さを指定する別の制約を作成します。2番目の制約へのアウトレットを作成し、このアウトレットに強いポインタがあることを確認します。次にinstalled、インターフェイスビルダーのチェックボックスをオフにし、最初の制約の優先順位がこの2番目の制約の優先順位よりも低いことを確認します。

最後に、2番目のラベルを非表示にしたら.isActive、これらの制約のプロパティを切り替えて呼び出しますsetNeedsDisplay()

マジックナンバーも数学もありません。オンとオフを切り替える制約が複数ある場合は、アウトレットコレクションを使用して、状態ごとに整理することもできます。(AKAは、1つのOutletCollectionにすべての非表示の制約を保持し、別のOutletCollectionに非表示の制約を保持し、各コレクションを反復処理して.isActiveステータスを切り替えます)。

私はRyan Romanchukが複数の制約を使用したくないと言ったのを知っていますが、これはマイクロマネージメントではなく、プログラムでビューと制約を動的に作成する方が簡単だと感じています(これは、質問を読んでいます)。

簡単な例を作成しました。それが役立つことを願っています...

import UIKit

class ViewController: UIViewController {

    @IBOutlet var ToBeHiddenLabel: UILabel!


    @IBOutlet var hiddenConstraint: NSLayoutConstraint!
    @IBOutlet var notHiddenConstraint: NSLayoutConstraint!


    @IBAction func HideMiddleButton(_ sender: Any) {

        ToBeHiddenLabel.isHidden = !ToBeHiddenLabel.isHidden
        notHiddenConstraint.isActive = !notHiddenConstraint.isActive
        hiddenConstraint.isActive = !hiddenConstraint.isActive

        self.view.setNeedsDisplay()
    }
}

ここに画像の説明を入力してください


0

私もソリューションを提供し、多様性を提供します。)各アイテムの幅/高さにプラススペースを追加するためのアウトレットを作成することは、とんでもないことであり、コード、起こり得るエラー、および複雑さの数を爆発させます。

私のメソッドはすべてのビュー(私の場合はUIImageViewインスタンス)を削除し、どのビューを追加する必要があるかを選択し、ループでそれぞれを追加して新しい制約を作成します。それは実際には本当に簡単です、フォロースルーしてください。これを行うための、すばやく簡単なコードを次に示します。

// remove all views
[self.twitterImageView removeFromSuperview];
[self.localQuestionImageView removeFromSuperview];

// self.recipients always has to be present
NSMutableArray *items;
items = [@[self.recipients] mutableCopy];

// optionally add the twitter image
if (self.question.sharedOnTwitter.boolValue) {
    [items addObject:self.twitterImageView];
}

// optionally add the location image
if (self.question.isLocal) {
    [items addObject:self.localQuestionImageView];
}

UIView *previousItem;
UIView *currentItem;

previousItem = items[0];
[self.contentView addSubview:previousItem];

// now loop through, add the items and the constraints
for (int i = 1; i < items.count; i++) {
    previousItem = items[i - 1];
    currentItem = items[i];

    [self.contentView addSubview:currentItem];

    [currentItem mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.centerY.equalTo(previousItem.mas_centerY);
        make.right.equalTo(previousItem.mas_left).offset(-5);
    }];
}


// here I just connect the left-most UILabel to the last UIView in the list, whichever that was
previousItem = items.lastObject;

[self.userName mas_remakeConstraints:^(MASConstraintMaker *make) {
    make.right.equalTo(previousItem.mas_left);
    make.leading.equalTo(self.text.mas_leading);
    make.centerY.equalTo(self.attachmentIndicator.mas_centerY);;
}];

クリーンで一貫したレイアウトと間隔が得られます。私のコードはメイソンリーを使用しています、私はそれを強くお勧めします:https : //github.com/SnapKit/Masonry


0

BoxViewを試してください。動的なレイアウトが簡潔で読みやすくなります。
あなたの場合は:

    boxView.optItems = [
        firstLabel.boxed.useIf(isFirstLabelShown),
        secondLabel.boxed.useIf(isSecondLabelShown),
        button.boxed
    ]
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.