web-dev-qa-db-fra.com

Comment utiliser la mise en page automatique pour déplacer d'autres vues lorsqu'une vue est masquée?

J'ai conçu ma cellule personnalisée dans IB, je l'ai subdivisée et j'ai connecté mes prises à ma classe personnalisée. J'ai trois sous-vues dans le contenu de la cellule qui sont: UIView (cdView) et deux étiquettes (titleLabel et emailLabel). En fonction des données disponibles pour chaque ligne, je souhaite parfois avoir UIView et deux étiquettes affichées dans ma cellule et parfois seulement deux étiquettes. Ce que j'essaie de faire est de définir les contraintes de cette manière si je mets la propriété UIView sur masquée ou si je la supprime de la vue d'ensemble, les deux étiquettes se déplaceront à gauche. J'ai essayé de définir la contrainte principale d'UIView sur Superview (contenu de la cellule) pour 10px et UILabels contraignant les contraintes pour 10 px vers la vue suivante (UIView). Plus tard dans mon code 

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

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

Je cache mon cell.cdView et j'aimerais que les étiquettes se déplacent vers la gauche, mais restent dans la même position dans Cell. J'ai essayé de supprimer cell.cdView de Superview mais cela n'a pas fonctionné non plus. J'ai joint l'image pour clarifier ce que je suis.

cell

Je sais comment faire cela par programmation et je ne cherche pas cette solution. Ce que je veux, c'est définir des contraintes dans IB et je m'attends à ce que mes sous-vues se déplacent de manière dynamique si d'autres vues sont supprimées ou masquées. Est-il possible de faire cela dans IB avec auto-layout?

.....
289
Guferos

C'est possible, mais vous devrez faire un peu de travail supplémentaire. Il y a quelques éléments conceptuels à résoudre en premier:

  • Les vues masquées, même si elles ne sont pas dessinées, participe toujours à la mise en page automatique et généralement conservent leurs cadres, laissant ainsi d'autres vues associées à leur place.
  • Lors de la suppression d'une vue de sa vue d'ensemble, toutes les contraintes connexes sont également supprimés de cette hiérarchie de vues.

Dans votre cas, cela signifie probablement:

  • Si vous définissez votre vue de gauche comme étant masquée, les étiquettes restent en place, car cette vue de gauche occupe toujours de la place (même si elle n’est pas visible).
  • Si vous supprimez la vue de gauche, vos étiquettes resteront probablement contraintes de manière ambiguë, car vous n’avez plus de contrainte pour les bords gauche de vos étiquettes.

Ce que vous devez faire, c'est bien contraindre _ vos étiquettes. Laissez vos contraintes existantes (espace de 10 points par rapport à l'autre vue), mais ajoutez une autre contrainte: placez les bords gauches de vos étiquettes à 10 points du bord gauche de leur superview avec une priorité non requise (la priorité haute par défaut fonctionnera probablement bien).

Ensuite, lorsque vous souhaitez les déplacer à gauche, supprimez complètement la vue de gauche. La contrainte obligatoire de 10 points sur la vue de gauche disparaîtra avec la vue à laquelle elle se rapporte, et il ne vous restera plus qu'une contrainte de priorité élevée voulant que les étiquettes soient distantes de 10 points de leur vue. Lors du passage suivant, cela devrait les amener à s’agrandir à gauche jusqu’à ce qu’ils remplissent la largeur de la vue supérieure, mais pour votre espacement sur les bords.

Une mise en garde importante: si vous souhaitez que votre vue gauche revienne dans l'image, vous devez non seulement la rajouter dans la hiérarchie des vues, mais vous devez également rétablir toutes ses contraintes en même temps. Cela signifie que vous avez besoin d'un moyen de rétablir votre contrainte d'espacement de 10 points entre la vue et ses étiquettes chaque fois que cette vue est à nouveau affichée.

339
Tim

L'ajout ou la suppression de contraintes pendant l'exécution est une opération très lourde susceptible d'affecter les performances. Cependant, il existe une alternative plus simple.

Pour la vue que vous souhaitez masquer, définissez une contrainte de largeur. Contraignez les autres vues avec un écart horizontal dominant par rapport à cette vue.

Pour masquer, mettez à jour le .constant de la contrainte de largeur en 0.f. Les autres vues se déplaceront automatiquement à gauche pour prendre position.

Voir mon autre réponse ici pour plus de détails:

Comment changer les contraintes d'étiquette pendant l'exécution?

194
Max MacLeod

Pour ceux qui ne prennent en charge que iOS 8+, il existe une nouvelle propriété booléenne active . Cela aidera à activer uniquement les contraintes nécessaires de manière dynamique

P.S. La sortie de contrainte doit être forte, pas faible

Exemple:

@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
}

Pour corriger une erreur dans le storyboard, vous devez également décocher la case Installed pour l'une de ces contraintes .

UIStackView (iOS 9+)
Une autre option consiste à envelopper vos vues dans UIStackView. Une fois la vue masquée, UIStackView mettra automatiquement la mise à jour

74
Silmaril

UIStackView repositionne automatiquement ses vues lorsque la propriété hidden est modifiée sur l'une de ses sous-vues (iOS 9+). 

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

Aller à 11:48 dans cette vidéo WWDC pour une démonstration:

Les mystères de la mise en page automatique, première partie

56
bonus

Mon projet utilise une sous-classe @IBDesignable personnalisée de UILabel (pour assurer la cohérence de la couleur, de la police, des encarts, etc.) et j'ai implémenté les éléments suivants:

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

Cela permet à la sous-classe d'étiquettes de participer à la mise en page automatique, mais ne prend aucun espace lorsqu'elle est masquée.

17
Robert Atkins

Pour les Googlers: en me basant sur la réponse de Max, pour résoudre le problème de rembourrage que beaucoup ont remarqué, j'ai simplement augmenté la hauteur de l'étiquette et utilisé cette hauteur comme séparateur au lieu d'un rembourrage réel. Cette idée pourrait être développée pour tout scénario contenant des vues.

Voici un exemple simple:

IB Screenshot

Dans ce cas, je mappe la hauteur de Author label sur une IBOutlet appropriée:

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

et lorsque je règle la hauteur de la contrainte sur 0.0f, nous conservons le "padding", car la hauteur du bouton Play le permet.

13
jterry

Ce que j'ai fini par créer, c'est créer 2 xibs. Un avec la vue de gauche et l'autre sans. Je me suis inscrit à la fois dans le contrôleur et ensuite décidé lequel utiliser pendant cellForRowAtIndexPath.

Ils utilisent la même classe UITableViewCell. L'inconvénient est qu'il y a une duplication du contenu entre les xibs, mais ces cellules sont assez basiques. L'avantage est que je n'ai pas beaucoup de code pour gérer manuellement la suppression de la vue, les contraintes de mise à jour, etc.

En général, il s’agit probablement d’une meilleure solution car il s’agit de dispositions techniques différentes qui doivent donc comporter des xib différents.

[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];
8
Michael Peterson

connecte la contrainte entre uiview et les étiquettes en tant que IBOutlet et définit le membre prioritaire sur une valeur inférieure s'il est masqué = YES

8
n0c

Dans ce cas, je mappe la hauteur de l'étiquette Author sur un IBOutlet approprié:

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

et lorsque je règle la hauteur de la contrainte sur 0.0f, nous conservons le "padding", car la hauteur du bouton Play le permet.

cell.authorLabelHeight.constant = 0;

 enter image description here  enter image description here

6
Mitul Marsoniya

Utilisez deux UIStackView Horizontal et Vertical, quand une vue de sous-vue dans la pile est masquée, d'autres sous-vues de pile seront déplacées, utilisez Distribution -> Remplir proportionnellement pour la pile verticale avec deux étiquettes UIL et vous devez définir des constances de largeur et de hauteur pour la première utilisation d'UIView  enter image description here

5

Essayez ceci, j'ai implémenté le code ci-dessous,

J'ai une vue sur ViewController dans cette trois autres vues ajoutées, Quand une vue est masquée, deux autres vues se déplacent, Suivez les étapes ci-dessous ..__ 

1.ViewController.h Fichier 

#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

 enter image description here  enter image description here  enter image description here

Espérons que cette logique aidera quelqu'un.

2
Jaywant Khedkar

Dans mon cas, j'ai défini la constante de la contrainte height sur 0.0f et la propriété hidden sur YES.

Pour afficher à nouveau la vue (avec les sous-vues), j’ai fait l’opposé: j’ai défini la constante de hauteur sur une valeur non nulle et la propriété hidden à NO.

1
testing

Ceci est une autre solution utilisant la contrainte de priorité. L'idée est de définir la largeur à 0.

  1. créer une vue de conteneur (orange) et définir la largeur .  enter image description here

  2. créer une vue du contenu (rouge) et définir un espace de fin 10 points sur superview (orange). Remarquez les contraintes d'espace de fin, il y a 2 contraintes de fin avec une priorité différente. Faible (= 10) et élevé (<= 10). Ceci est important pour éviter toute ambiguïté .  enter image description here

  3. Définissez la largeur de la vue orange sur 0 pour masquer la vue .  enter image description here

1
mnemonic23

Comme no_scene l'a suggéré, vous pouvez certainement le faire en modifiant la priorité de la contrainte lors de l'exécution. Cela a été beaucoup plus facile pour moi car j'avais plus d'une vue bloquante qui devrait être supprimée.

Voici un extrait utilisant 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
skensell

Je pense que c'est la réponse la plus simple s'il vous plaît vérifier s'il fonctionne bien

        StackFullView.layer.isHidden = true
        Task_TopSpaceSections.constant = 0.   //your constrain of top view

vérifier ici https://www.youtube.com/watch?v=EBulMWMoFuw

0
Gowthaman M

Voici comment je réalignerais mes uiviews pour obtenir votre solution:

  1. Faites glisser un UIImageView et placez-le à gauche.
  2. Faites glisser déposer UIView et placez-le à droite de UIImageView. 
  3. Glissez-déposez deux étiquettes UIL à l'intérieur de cette UIView dont les contraintes de début et de fin sont nulles.
  4. Définissez la contrainte principale de UIView contenant 2 étiquettes sur superview au lieu de UIImagView.
  5. SI UIImageView est masqué, définissez la constante de contrainte principale sur 10 px sur superview. ELSE, définissez la constante de contrainte principale sur 10 px + UIImageView.width + 10 px.

J'ai créé ma propre règle du pouce. Chaque fois que vous devez masquer/afficher toute vue uiview dont les contraintes peuvent être affectées, ajoutez toutes les sous-vues concernées/dépendantes dans une vue uiview et mettez à jour sa constante de contrainte de début/fin/haut/bas par programme. 

0
Deepak Thakur

Au cas où cela aiderait quelqu'un, j'ai construit une classe d'assistance pour utiliser les contraintes visual format . Je l'utilise dans mon application actuelle.

AutolayoutHelper

Il est peut-être un peu adapté à mes besoins, mais vous le trouverez peut-être utile ou vous voudrez peut-être le modifier et créer votre propre aide.

Je dois remercier Tim pour sa réponse ci-dessus , cette réponse à propos de UIScrollView et aussi ce tutorial .

0
Ferran Maylinch

la bonne façon de le faire est de désactiver les contraintes avec isActive = false. notez cependant que la désactivation d'une contrainte la supprime et la libère, vous devez donc disposer de points de vente puissants.

0
Hogdotmac

C'est une vieille question mais j'espère que cela aidera. Venant d'Android, cette plate-forme vous offre une méthode pratique isVisible pour la masquer de la vue, mais vous ne devez pas non plus prendre en compte le cadre lorsque la fonctionnalité de retrait automatique dessine la vue.

en utilisant extension et "extend" uiview, vous pourriez faire une fonction similaire dans iOS (vous ne savez pas pourquoi ce n’est pas déjà dans UIKit), ici une implémentation dans Swift 3:

    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
Vincent Choubard

Je vais utiliser stackview horizontal. Il peut supprimer le cadre lorsque la sous-vue est masquée. 

Dans l'image ci-dessous, la vue rouge est le conteneur réel de votre contenu et a un espace de fin de 10 points vers orange superview (ShowHideView). Il vous suffit ensuite de connecter ShowHideView à IBOutlet et d'afficher/masquer/supprimer le programme.

  1. C'est à ce moment que la vue est visible/installée.

 view is visible

  1. C'est lorsque la vue est masquée/non installée.

 view is hidden/removed

0
mnemonic23