ビジネスロジックに応じて、UIViews
、ほとんどの場合はUILabels
を表示/非表示するのはかなり一般的なパラダイムだと思います。私の質問は、AutoLayoutを使用して、フレームが0x0であるかのように非表示のビューに応答する最良の方法は何ですか。 1〜3個の機能の動的リストの例を次に示します。
現時点では、ボタンから最後のラベルまでの上部に10ピクセルのスペースがありますが、ラベルが非表示の場合は明らかに上にスライドしません。現時点では、この制約へのアウトレットを作成し、表示するラベルの数に応じて定数を変更しています。負の定数値を使用して非表示のフレーム上でボタンを押し上げるため、これは明らかに少しハッキーです。また、実際のレイアウト要素に制約されておらず、既知の高さ/他の要素のパディングに基づいた卑劣な静的計算だけでなく、明らかにAutoLayoutが構築されたものと戦っているので、それも悪いです。
動的なラベルに応じて新しい制約を作成することは明らかにできますが、それはいくつかの空白を単に折りたたむための多くの細かな管理と冗長性です。より良いアプローチはありますか?フレームサイズ0,0を変更し、AutoLayoutに制約を操作せずに処理させますか?ビューを完全に削除しますか?
正直なところ、隠されたビューのコンテキストから定数を変更するには、簡単な計算で1行のコードが必要です。 constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:
を使用して新しい制約を再作成するのはとても重いようです。
2018年2月編集:UIStackView
sを使用したBenの回答を参照
UIStackView
は、おそらくiOS 9以降で使用する方法です。非表示のビューを処理するだけでなく、正しく設定されていれば、追加のスペースとマージンも削除されます。
ビューの表示/非表示の個人的な好みは、適切な幅または高さの制約を持つIBOutletを作成することです。
次に、constant
の値を0
に更新して、非表示にするか、表示する値を変更します。
この手法の大きな利点は、相対的な制約が維持されることです。たとえば、ビューAとビューBにxの水平方向のギャップがあるとします。ビューAの幅constant
が0.f
に設定されている場合、ビューBは左に移動してそのスペースを埋めます。
制約を追加または削除する必要はありません。これは重い操作です。制約のconstant
を更新するだけでうまくいきます。
非表示のときに定数0
を使用し、再度表示する場合は別の定数を使用するソリューションは機能しますが、コンテンツのサイズが柔軟な場合は満足できません。柔軟なコンテンツを測定し、一定に戻す必要があります。これは間違っていると感じており、サーバーまたはUIイベントのためにコンテンツのサイズが変更されると問題が発生します。
より良い解決策があります。
アイデアは、要素を非表示にするときに高さ0のルールを高い優先度に設定して、自動レイアウトスペースを占有しないようにすることです。
その方法は次のとおりです。
1。低優先度のインターフェイスビルダーで幅(または高さ)を0に設定します。
優先順位が低いため、Interface Builderは競合について怒鳴りません。優先度を一時的に999に設定して、高さの動作をテストします(1000はプログラムで変更することは禁止されているため、使用しません)。インターフェイスビルダーは、おそらく競合する制約について叫ぶでしょう。関連オブジェクトの優先順位を900程度に設定することで、これらを修正できます。
2。アウトレットを追加して、コード内の幅制約の優先度を変更できるようにします:
。要素を非表示にするときに優先度を調整します:
cell.alertTimingView.hidden = place.closingSoon != true
cell.alertTimingWidth.priority = place.closingSoon == true ? 250 : 999
ビューをサブクラス化し、func intrinsicContentSize() -> CGSize
をオーバーライドします。ビューが非表示の場合は、単にCGSizeZero
を返します。
この望ましい動作に対してUIKit
が提供するよりエレガントなアプローチがないことに驚いています。できるようになりたいと思うことは非常に一般的なことのようです。
制約をIBOutlets
に接続し、定数を0
に設定すると不愉快な(そしてビューにサブビューがある場合にNSLayoutConstraint
警告を引き起こした)ため、私は 自動レイアウト制約を持つUIView
ビューを非表示にし、外部の制約を削除するだけです。ビューを再度表示すると、制約が追加されます。唯一の注意点は、周囲のビューに柔軟なフェイルオーバー制約を指定する必要があるということです。
Editこの回答は、iOS 8.4以下を対象としています。 iOS 9では、UIStackView
アプローチを使用してください。
ここには多くの解決策がありますが、私の通常のアプローチは再び異なります:)
Jorge ArimanyとTMinの答えに似た2セットの制約を設定します。
マークされた3つの制約はすべて、定数の値が同じです。 A1およびA2とマークされた制約の優先度は500に設定され、Bとマークされた制約の優先度は250(またはコードのUILayoutProperty.defaultLow
)に設定されます。
制約BをIBOutletに接続します。次に、要素を非表示にするときは、制約の優先度を高(750)に設定するだけです。
constraintB.priority = .defaultHigh
ただし、要素が表示されたら、優先度を低(250)に戻します。
constraintB.priority = .defaultLow
制約BのisActive
を変更するよりも、このアプローチの(確かにマイナーな)利点は、一時的な要素が他の手段でビューから削除された場合でも、作業制約があることです。
UILabelがスペースを占有しないようにするには、非表示にし、テキストを空の文字列に設定する必要があることがわかりました。 (iOS 9)
この事実/バグを知ることは、一部の人々がレイアウトを簡素化するのに役立つ可能性があり、おそらく元の質問のレイアウトを簡素化するので、私はそれを投稿すると思いました。
制約を簡単に更新するカテゴリを作成します。
[myView1 hideByHeight:YES];
ベストプラクティスは、すべてが正しいレイアウト制約を持つようになったら、周囲のビューをどのように移動して制約を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に設定することですが、再度最高の優先度に戻すことを忘れないでください。
私が好む方法は、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()
}
}
私もさまざまなソリューションを提供します。)各アイテムの幅/高さにスペースを加えたアウトレットを作成するのはばかげていると思い、コード、考えられるエラー、複雑さの数が増えます。
私のメソッドは、すべてのビュー(私の場合は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