web-dev-qa-db-ja.com

非表示のUIViewを使用した自動レイアウト?

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

Dynamic features list

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

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

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

2018年2月編集UIStackViewsを使用したBenの回答を参照

158
Ryan Romanchuk

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

78
Ben Packard

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

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

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

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

231
Max MacLeod

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

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

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

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

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

setting width 0 rule

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

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

outlet connection

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

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

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

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

そして、制約の高さを0.0fに設定すると、「パディング」が保持されます。これは、再生ボタンの高さが許容するためです。

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

enter image description here

11
Mitul Marsoniya

ビューをサブクラス化し、func intrinsicContentSize() -> CGSizeをオーバーライドします。ビューが非表示の場合は、単にCGSizeZeroを返します。

5
Daniel

この望ましい動作に対してUIKitが提供するよりエレガントなアプローチがないことに驚いています。できるようになりたいと思うことは非常に一般的なことのようです。

制約をIBOutletsに接続し、定数を0に設定すると不愉快な(そしてビューにサブビューがある場合にNSLayoutConstraint警告を引き起こした)ため、私は 自動レイアウト制約を持つUIView

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

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

4
Albert Bori

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

Jorge ArimanyとTMinの答えに似た2セットの制約を設定します。

enter image description here

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

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

constraintB.priority = .defaultHigh

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

constraintB.priority = .defaultLow

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

4
SeanR

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

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

3
Matt Koala

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

[myView1 hideByHeight:YES];

回答: 自動レイアウトUIViewを非表示:既存のNSLayoutConstraintを取得してこれを更新する方法

enter image description here

3
Damien Romito

ベストプラクティスは、すべてが正しいレイアウト制約を持つようになったら、周囲のビューをどのように移動して制約をIBOutletプロパティに接続するかによって、高さまたは制約付きを追加します。

プロパティがstrongであることを確認してください

コードでは、定数を0とactivateに設定するか、コンテンツを非表示にするか、またはdeactivateでコンテンツを表示する必要があります。これは、一定の値を保存して復元することを台無しにするよりも優れています。後で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に設定することですが、再度最高の優先度に戻すことを忘れないでください。

3
Jorge Arimany

私が好む方法は、Jorge Arimanyが提案した方法と非常に似ています。

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

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

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

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

ライアン・ロマンチュクは複数の制約を使用したくないと言っていましたが、これはミクロ管理ではなく、プログラムでビューと制約を動的に作成する方が簡単だと思います(私が彼が避けたいのは正しい質問を読んでいます)。

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

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()
    }
}

enter image description here

2
TMin

私もさまざまなソリューションを提供します。)各アイテムの幅/高さにスペースを加えたアウトレットを作成するのはばかげていると思い、コード、考えられるエラー、複雑さの数が増えます。

私のメソッドは、すべてのビュー(私の場合は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);;
}];

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

0
Zoltán