ビューが非表示のときに自動レイアウトを使用して他のビューを移動するにはどうすればよいですか?


333

私はカスタムセルをIBで設計し、サブクラス化し、アウトレットをカスタムクラスに接続しました。セルコンテンツには、UIView(cdView)と2つのラベル(titleLabelとemailLabel)の3つのサブビューがあります。各行で使用可能なデータによっては、UIViewと2つのラベルをセルに表示したい場合と、2つのラベルのみを表示したい場合があります。私がやろうとしているのは、UIViewプロパティを非表示に設定した場合、またはスーパービューから削除した場合に、2つのラベルが左に移動するように制約を設定することです。UIpx先行制約を10pxのスーパービュー(セルコンテンツ)に設定し、UILabelsが10 pxの制約を次のビュー(UIView)に設定しようとしました。後で私のコードで

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(IndexPath *)indexPath {
...
Record *record = [self.records objectAtIndex:indexPath.row];

if ([record.imageURL is equalToString:@""]) {
     cell.cdView.hidden = YES;
}

私は自分のcell.cdViewを非表示にしていて、ラベルを左に移動させたいのですが、それらはセル内の同じ位置にとどまっています。superviewからcell.cdViewを削除しようとしましたが、うまくいきませんでした。私が何であるかを明確にするために画像を添付しました。

細胞

私はこれをプログラムで行う方法を知っており、その解決策を探していません。私が欲しいのはIBに制約を設定することです。他のビューが削除または非表示にされた場合、私のサブビューが動的に移動することを期待しています。自動レイアウトでIBでこれを行うことは可能ですか?

.....

ランタイムの制約値を変更-この回答を
Kampai 2018

この特定のケースでは、UIStackViewを使用することもできます。CDを非表示にすると、ラベルがそのスペースを占有します
Marco Pappalardo 2018

回答:


373

それは可能ですが、少し余分な作業を行う必要があります。最初に邪魔にならないようにするための概念的なものがいくつかあります。

  • 非表示のビューは、描画しなくて、自動レイアウトに参加し、通常はフレームを保持して、他の関連するビューをそのまま残します。
  • スーパービューからビューを削除すると、関連するすべての制約もそのビュー階層から削除されます。

あなたの場合、これはおそらく次のことを意味します:

  • 左側のビューを非表示に設定した場合、左側のビューはスペースを占有しているため(表示されていなくても)、ラベルはそのまま残ります。
  • 左側のビューを削除すると、ラベルの左端の制約がなくなるため、ラベルが曖昧に制約されたままになる可能性があります。

あなたがする必要があるのはあなたのラベルを慎重に過剰に制限することです。既存の制約(他のビューまで10ポイントのスペース)はそのままにして、別の制約を追加します。ラベルの左端を必須ではない優先順位でスーパービューの左端から10ポイント離します(デフォルトの高優先順位がおそらくうまく機能します)。

次に、左に移動させたい場合は、左側のビューを完全に削除します。左側のビューへの必須の10ポイントの制約は、関連するビューとともに消え、ラベルがスーパービューから10ポイント離れるという優先度の高い制約が残ります。次のレイアウトパスでは、これにより、スーパービューの幅を埋めるまで左に拡大しますが、エッジの周りのスペースは必要です。

重要な注意点の1つは、左側のビューを画像に戻したい場合は、ビューの階層に再び追加する必要があるだけでなく、すべての制約を同時に再確立する必要があります。つまり、そのビューが再び表示されるときはいつでも、ビューとそのラベルの間に10ptの間隔制約を戻す方法が必要です。


8
この回答は確かに機能しますが、IMO、さまざまなユースケースを処理するための過剰な制約は、コードのにおいのように見えます。
memmons 2013年

私の意見では、これは進むべき道ではありません。代わりに、非表示にするビューの幅/高さの制約を使用する必要があります。
ullstrm 2014

26
私は敬意を払いません。たとえば、ビューの幅を0に設定すると、2つの問題が発生します。まず、あなたは今、スーパーと見えるビューの間に二重間隔を有する:|-(space)-[hidden(0)]-(space)-[visible]効果的です|-(2*space)-[visible]。第2に、そのビューは、独自のビューサブツリーと制約に応じて制約違反をスローし始める可能性があります。ビューを0の幅で任意に制約して動作を継続できるとは限りません。
Tim

1
本質的なコンテンツサイズのビューを使用している場合、ティムの答えはユニークな方法です。
バルコト2014年

ティムありがとう。制約の非互換性を回避するために、優先順位を0に設定して制約を設定しましたが、ダブルスペースに問題があることに気付きました。2つのビューを一緒に表示することはなかったので(私の場合:)、その問題はありませんでしたが、|-[otherViews]-[eitherThis][orThis]-|最終的にはその問題に遭遇しました。
Ferran Maylinch 2015

207

実行時の制約の追加または削除は、パフォーマンスに影響を与える可能性がある重い操作です。ただし、より簡単な代替手段があります。

非表示にするビューに対して、幅の制約を設定します。他のビューを、そのビューに対する先行水平ギャップで拘束します。

非表示にするに.constantは、幅制約のを0.f に更新します。他のビューは自動的に左に移動して位置を決めます。

詳細については、こちらの他の回答をご覧ください。

実行時にラベル制約を変更するにはどうすればよいですか?


29
このソリューションの唯一の問題は、左側のマージンが必要なものの2倍になることです。そのため、これらの制約の1つも更新しますが、それでも、サブビューを削除するよりも作業は少ないと思います。
ホセマヌエルサンチェス2014年

2
@ skinsfan00atg固有のコンテンツサイズのビューを使用している場合、このソリューションは使用できません。
バルコト2014年

2
@MaxMacLeodコンテンツハグの優先度を下げると、固有のコンテンツサイズを使用せず、制約によって示されるサイズを使用します。
バルコト2014年

2
@MaxMacLeodわかりました。ビューを非表示にしたい場合、および再度表示したい場合は、コードで圧縮抵抗の優先度を0(コンテンツハグではない)に設定して、その値を復元する必要があります。それ以外に、ビューのサイズを0に設定するには、インターフェイスビルダーで制約を追加する必要があります。コードでこの制約に触れる必要はありません。
バルコト2014年

1
これは実際に私のために働きます、どうもありがとう
FritzB '30

87

iOS 8以降のみをサポートする人のために、新しいブール型プロパティactiveがあります。必要な制約のみを動的に有効化するのに役立ちます

PS制約アウトレットは、弱くてはならず、強くなければなりません。

例:

@IBOutlet weak var optionalView: UIView!
@IBOutlet var viewIsVisibleConstraint: NSLayoutConstraint!
@IBOutlet var viewIsHiddenConstraint: NSLayoutConstraint!

func showView() {
    optionalView.isHidden = false
    viewIsVisibleConstraint.isActive = true
    viewIsHiddenConstraint.isActive = false
}

func hideView() {
    optionalView.isHidden = true
    viewIsVisibleConstraint.isActive = false
    viewIsHiddenConstraint.isActive = true
}

また、ストーリーボードのエラーを修正するにはInstalled、これらの制約のいずれかのチェックボックスをオフにする必要があります。

UIStackView (iOS 9以降)
もう1つのオプションは、ビューをでラップすることですUIStackView。ビューが非表示にUIStackViewなると、レイアウトが自動的に更新されます


部分的にアクティブに動作します。しかし、再利用可能なセル内で使用すると、アクティブ化から非アクティブ化まで機能しますが、非アクティブ化からアクティブ化までは機能しません。何か案は?または、例を挙げていただけますか?
Aoke Li 2015

10
非アクティブ化された制約がロードまたは割り当て解除されていないために発生します。制約アウトレットは、弱くはなく、強力でなければなりません
Silmaril

その「アクティブ」なプロパティはどこにありますか?
ラクシュミレディ


過剰制約+制約設定アクティビティが最も合理的な答えであると思われます。
Ting Yi Shih

68

UIStackViewhiddenプロパティがサブビューのいずれかで変更されると、ビューを自動的に再配置します(iOS 9以降)。

UIView.animateWithDuration(1.0) { () -> Void in
   self.mySubview.hidden = !self.mySubview.hidden
}

デモ用のこのWWDCビデオで11:48にジャンプします。

自動レイアウトの謎、パート1


ネストされたスタックビューを非表示にすると、含まれているスタックビュー全体が消えました。
Ian Warburton

5
これは受け入れられる答えになるはずです。インターフェイスビルダーの設計に関するwwdc 2015のアップルの推奨事項に従います。
ティボーノア

@thibautnoahでは、iOS 8のサポートについてはどうですか?
手荷物

5
@bagageあなたがfacebookやgoogleでない限り、あなたはそれを落とすことができます。iOS 9に対応したカバレッジを使用すると、デバイスの90%以上をサポートできます。以下をサポートすると、開発プロセスが妨げられ、最新の機能
imo

16

私のプロジェクトは(色、フォント、インセットなどの一貫性を確保するために)のカスタム@IBDesignableサブクラスを使用しUILabelており、次のようなものを実装しています。

override func intrinsicContentSize() -> CGSize {
    if hidden {
        return CGSizeZero
    } else {
        return super.intrinsicContentSize()
    }
}

これにより、ラベルサブクラスは自動レイアウトに参加できますが、非表示の場合はスペースを取りません。


13

Google社員向け: Maxの答えに基づいて、多くの人が気付いたパディングの問題を解決するには、ラベルの高さを増やし、実際のパディングの代わりにその高さをセパレーターとして使用しました。このアイデアは、ビューを含むシナリオで拡張できます。

以下に簡単な例を示します。

IBスクリーンショット

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

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

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


新しいしている人のためにNSLayoutConstraint私は更新したいと考えていconstant財産あなたのをauthorLabelHeight
カイル

8

uiviewとラベルの間の制約をIBOutletとして接続し、hidden = YESに設定した場合、優先メンバーをより小さい値に設定します


3
NSLayoutConstraintがいったん確立されると、優先度を調整することはできません。優先度の異なる新しい制約を削除して再読み込みする必要があります。
Tim

7
私はこの方法を機能させました。ラベルとボタンを使用するケースがあり、ボタンを非表示にしてラベルを展開する必要があります。2つの制約があります。1つは優先度751、もう1つは優先度750です。次にボタンを非表示にすると、優先度が反転し、ラベルの長さが長くなります。より高い優先度を1000にしようとすると、「優先度の必須からインストール済みの制約なしへの変更(またはその逆)はサポートされていません。」というエラーが発生することに注意してください。そうしないでください、あなたは元気そうです。Xcode 5.1 / viewDidLoad。
John Bushnell 2014年

8

私がやったことは、2つのxibを作成することでした。左側のビューがあるものとないもの。両方をコントローラーに登録してから、cellForRowAtIndexPathのどちらを使用するかを決定しました。

同じUITableViewCellクラスを使用します。欠点は、xibs間でコンテンツの重複があることですが、これらのセルはかなり基本的なものです。利点は、ビューの削除や制約の更新などを手動で管理するためのコードがたくさんないことです。

一般的に、これらは技術的に異なるレイアウトであり、したがって異なるxibを持つ必要があるため、おそらくより良いソリューションです。

[self.table registerNib:[UINib nibWithNibName:@"TrackCell" bundle:nil] forCellReuseIdentifier:@"TrackCell"];
[self.table registerNib:[UINib nibWithNibName:@"TrackCellNoImage" bundle:nil] forCellReuseIdentifier:@"TrackCellNoImage"];

TrackCell *cell = [tableView dequeueReusableCellWithIdentifier:(appDelegate.showImages ? @"TrackCell" : @"TrackCellNoImage") forIndexPath:indexPath];

7

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

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

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

cell.authorLabelHeight.constant = 0;

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


6

2つのUIStackView Horizo​​ntalとVerticalを使用します。スタック内の一部のサブビュービューが非表示の場合、他のスタックサブビューが移動されます。2つのUILabelsを使用して垂直スタックに対して[分布]-> [比例配分]を使用し、最初のUIViewに幅と高さの設定を設定する必要があります。ここに画像の説明を入力してください


2

これを試してください、私は以下のコードを実装しました、

他の3つのビューが追加されたViewControllerに1 つのビューがあります。いずれかのビューが非表示になると、他の2つのビューが移動します。以下の手順に従ってください。、

1.ViewController.hファイル

#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (strong, nonatomic) IBOutlet UIView *viewOne;
@property (strong, nonatomic) IBOutlet UIView *viewTwo;
@property (strong, nonatomic) IBOutlet UIView *viewThree;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewOneWidth;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewTwoWidth;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewThreeWidth;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewBottomWidth;
@end

2.ViewController.m

 #import "ViewController.h"
 @interface ViewController ()
{
  CGFloat viewOneWidthConstant;
  CGFloat viewTwoWidthConstant;
  CGFloat viewThreeWidthConstant;
  CGFloat viewBottomWidthConstant;
}
@end

@implementation ViewController
@synthesize viewOne, viewTwo, viewThree;

- (void)viewDidLoad {
  [super viewDidLoad];
 // Do any additional setup after loading the view, typically from a 
  nib.

  /*
   0  0   0
   0  0   1
   0  1   0
   0  1   1
   1  0   0
   1  0   1
   1  1   0
   1  1   1
   */

  //    [viewOne setHidden:NO];
  //    [viewTwo setHidden:NO];
  //    [viewThree setHidden:NO];

  //    [viewOne setHidden:NO];
  //    [viewTwo setHidden:NO];
  //    [viewThree setHidden:YES];

  //    [viewOne setHidden:NO];
  //    [viewTwo setHidden:YES];
  //    [viewThree setHidden:NO];

  //    [viewOne setHidden:NO];
  //    [viewTwo setHidden:YES];
  //    [viewThree setHidden:YES];


  //    [viewOne setHidden:YES];
  //    [viewTwo setHidden:NO];
  //    [viewThree setHidden:NO];

  //    [viewOne setHidden:YES];
  //    [viewTwo setHidden:NO];
  //    [viewThree setHidden:YES];

 //    [viewOne setHidden:YES];
 //    [viewTwo setHidden:YES];
 //    [viewThree setHidden:NO];

//    [viewOne setHidden:YES];
//    [viewTwo setHidden:YES];
//    [viewThree setHidden:YES];

 [self hideShowBottomBar];
  }

- (void)hideShowBottomBar
{
  BOOL isOne = !viewOne.isHidden;
  BOOL isTwo = !viewTwo.isHidden;
  BOOL isThree = !viewThree.isHidden;

  viewOneWidthConstant = _viewOneWidth.constant;
  viewTwoWidthConstant = _viewTwoWidth.constant;
  viewThreeWidthConstant = _viewThreeWidth.constant;
  viewBottomWidthConstant = _viewBottomWidth.constant;

   if (isOne && isTwo && isThree) {
    // 0    0   0
    _viewOneWidth.constant = viewBottomWidthConstant / 3;
    _viewTwoWidth.constant = viewBottomWidthConstant / 3;
    _viewThreeWidth.constant = viewBottomWidthConstant / 3;
    }
    else if (isOne && isTwo && !isThree) {
     // 0    0   1
    _viewOneWidth.constant = viewBottomWidthConstant / 2;
    _viewTwoWidth.constant = viewBottomWidthConstant / 2;
    _viewThreeWidth.constant = 0;
    }
   else if (isOne && !isTwo && isThree) {
    // 0    1   0
    _viewOneWidth.constant = viewBottomWidthConstant / 2;
    _viewTwoWidth.constant = 0;
    _viewThreeWidth.constant = viewBottomWidthConstant / 2;
    }
    else if (isOne && !isTwo && !isThree) {
    // 0    1   1
    _viewOneWidth.constant = viewBottomWidthConstant;
    _viewTwoWidth.constant = 0;
    _viewThreeWidth.constant = 0;
   }
   else if (!isOne && isTwo && isThree) {
    // 1    0   0
    _viewOneWidth.constant = 0;
    _viewTwoWidth.constant = viewBottomWidthConstant / 2;
    _viewThreeWidth.constant = viewBottomWidthConstant / 2;
   }
   else if (!isOne && isTwo && !isThree) {
    // 1    0   1
    _viewOneWidth.constant = 0;
    _viewTwoWidth.constant = viewBottomWidthConstant;
    _viewThreeWidth.constant = 0;
   }
   else if (!isOne && !isTwo && isThree) {
    // 1    1   0
    _viewOneWidth.constant = 0;
    _viewTwoWidth.constant = 0;
    _viewThreeWidth.constant = viewBottomWidthConstant;
   }
   else if (isOne && isTwo && isThree) {
    // 1    1   1
    _viewOneWidth.constant = 0;
    _viewTwoWidth.constant = 0;
    _viewThreeWidth.constant = 0;
   }
  }

 - (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
 // Dispose of any resources that can be recreated.
 }
 @end

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

ですから、このロジックは誰かを助けるでしょう。


1

私の場合は、一定の設定の高さの制約にし0.0f、また、セットhiddenにプロパティをYES

ビュー(サブビューを含む)をもう一度表示するには、反対のことを行いました。高さ定数をゼロ以外の値に設定し、hiddenプロパティをに設定しましたNO


1

水平スタックビューを使用します。サブビューが非表示のときにフレームを削除できます。

下の画像では、赤いビューがコンテンツの実際のコンテナーであり、オレンジのスーパービュー(ShowHideView)に10 ptの後続スペースがあります。次に、ShowHideViewをIBOutletに接続し、プログラムで表示/非表示/削除します。

  1. これは、ビューが表示/インストールされたときです。

ビューが見える

  1. これは、ビューが非表示/インストールされていない場合です。

ビューは非表示/削除されています


1

これは、優先順位制約を使用した別のソリューションです。アイデアは幅を0に設定します。

  1. コンテナービュー(オレンジ)を作成し、幅を設定します。 ここに画像の説明を入力してください

  2. コンテンツビュー(赤)を作成し、トレーリングスペース10ptをスーパービュー(オレンジ)に設定します。末尾のスペース制約に注意してください。優先順位が異なる2つの末尾の制約があります。低(= 10)および高(<= 10)。これはあいまいさを避けるために重要です。 ここに画像の説明を入力してください

  3. オレンジ色のビューの幅を0に設定して、ビューを非表示にします。 ここに画像の説明を入力してください


0

no_sceneが示唆しているように、実行時に制約の優先度を変更することで確実にこれを行うことができます。削除する必要がある複数のブロッキングビューがあったので、これは私にとってはるかに簡単でした。

以下は、ReactiveCocoaを使用したスニペットです。

RACSignal* isViewOneHiddenSignal = RACObserve(self.viewModel, isViewOneHidden);
RACSignal* isViewTwoHiddenSignal = RACObserve(self.viewModel, isViewTwoHidden);
RACSignal* isViewThreeHiddenSignal = RACObserve(self.viewModel, isViewThreeHidden);
RAC(self.viewOne, hidden) = isViewOneHiddenSignal;
RAC(self.viewTwo, hidden) = isViewTwoHiddenSignal;
RAC(self.viewThree, hidden) = isViewThreeHiddenSignal;

RAC(self.viewFourBottomConstraint, priority) = [[[[RACSignal
    combineLatest:@[isViewOneHiddenSignal,
                    isViewTwoHiddenSignal,
                    isViewThreeHiddenSignal]]
    and]
    distinctUntilChanged]
    map:^id(NSNumber* allAreHidden) {
        return [allAreHidden boolValue] ? @(780) : @(UILayoutPriorityDefaultHigh);
    }];

RACSignal* updateFramesSignal = [RACObserve(self.viewFourBottomConstraint, priority) distinctUntilChanged];
[updateFramesSignal
    subscribeNext:^(id x) {
        @strongify(self);
        [self.view setNeedsUpdateConstraints];
        [UIView animateWithDuration:0.3 animations:^{
            [self.view layoutIfNeeded];
        }];
    }];

0

これが誰かを助ける場合に備えて、視覚的フォーマット制約を使用するためのヘルパークラスを作成しました。現在のアプリで使用しています。

AutolayoutHelper

それは私のニーズに少し合わせられるかもしれませんが、あなたはそれが便利だと思うかもしれません、あるいはあなたはそれを修正してあなた自身のヘルパーを作成したいかもしれません。

上記の彼の答えUIScrollViewに関するこの答え、そしてこのチュートリアルについてもTimに感謝しなければなりません。


0

これは、uiviewを再調整してソリューションを取得する方法です。

  1. 1つのUIImageViewをドラッグアンドドロップして左側に配置します。
  2. 1つのUIViewをドラッグアンドドロップし、UIImageViewの右側に配置します。
  3. 先頭と末尾の制約がゼロであるUIView内に2つのUILabelをドラッグアンドドロップします。
  4. 2つのラベルを含むUIViewの先行制約をUIImagViewではなくスーパービューに設定します。
  5. UIImageViewが非表示の場合、スーパービューにするには、先行制約定数を10ピクセルに設定します。ELSE、先行制約定数を10 px + UIImageView.width + 10 pxに設定します。

私は自分の経験則を作成しました。制約が影響を受ける可能性のあるuiviewを非表示/表示する必要がある場合は常に、影響を受ける/依存するすべてのサブビューをuiview内に追加し、プログラムの先頭/末尾/上部/下部の制約定数を更新します。


0

これは古い質問ですが、それでもお役に立てば幸いです。Androidから来て、このプラットフォームでは便利な方法がありますisVisibleでは、ビューから非表示にがありますが、自動レイアウトがビューを描画するときに考慮されるフレームもありません。

拡張機能と「拡張」uiviewを使用すると、iOSで同様の機能を実行できます(なぜUIKitにないのかは不明です)。

    func isVisible(_ isVisible: Bool) {
        self.isHidden = !isVisible
        self.translatesAutoresizingMaskIntoConstraints = isVisible
        if isVisible { //if visible we remove the hight constraint 
            if let constraint = (self.constraints.filter{$0.firstAttribute == .height}.first){
                self.removeConstraint(constraint)
            }
        } else { //if not visible we add a constraint to force the view to have a hight set to 0
            let height = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal , toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: 0)
            self.addConstraint(height)
        }
        self.layoutIfNeeded()
    }

0

これを行う適切な方法は、isActive = falseで制約を無効にすることです。ただし、制約を非アクティブ化すると、その制約が削除および解放されるため、それらには強力なアウトレットが必要になることに注意してください。


0

最も簡単な解決策は、UIStackView(水平)を使用することです。スタックビューに追加:最初のビューと2番目のビューにラベルを付けます。次に、最初のビューのisHiddenプロパティをfalseに設定します。すべての制約が計算され、自動的に更新されます。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.