web-dev-qa-db-fra.com

Définition de contraintes par programme

J'expérimente comment utiliser UIScrollView. Après bien des ennuis, j'ai finalement compris. Mais maintenant, il me semble que je frappe un autre problème.

Dans cette application simple, j'ai une vue de défilement avec et pour que cela fonctionne, je dois définir l'espace inférieur de la vue pour faire défiler la contrainte de vue à 0 comme décrit ici et cela fonctionne très bien. Je le fais par l'IB.

Maintenant, je suis tombé sur un scénario où je dois faire cette partie par programme. J'ai ajouté le code ci-dessous dans la méthode viewDidLoad.

NSLayoutConstraint *bottomSpaceConstraint = [NSLayoutConstraint constraintWithItem:self.view
                                                                             attribute:NSLayoutAttributeBottom
                                                                             relatedBy:NSLayoutRelationEqual
                                                                                toItem:self.scrollView
                                                                             attribute:NSLayoutAttributeBottom
                                                                            multiplier:1.0
                                                                              constant:0.0];
[self.view addConstraint:bottomSpaceConstraint];

Mais cela ne semble pas fonctionner. Il affiche le message suivant dans la fenêtre de la console et je ne sais pas quoi en faire.

Impossible de satisfaire simultanément les contraintes. Probablement au moins une des contraintes dans la liste suivante est celle que vous ne voulez pas. Essayez ceci: (1) examinez chaque contrainte et essayez de déterminer celle à laquelle vous ne vous attendez pas; (2) recherchez le code qui a ajouté la ou les contraintes indésirables et corrigez-le. (Remarque: si vous voyez NSAutoresizingMaskLayoutConstraints que vous ne comprenez pas, reportez-vous à la documentation de la propriété UIView translatesAutoresizingMaskIntoConstraints) ("", "")

Est-ce que quelqu'un pourrait m'expliquer comment faire cela? J'ai également joint un projet de démonstration ici afin que vous puissiez avoir une meilleure idée du problème.

MISE À JOUR:

Tout d'abord merci pour les réponses. En utilisant les moyens mentionnés dans les réponses, j'ai pu le faire fonctionner. Cependant, dans un scénario légèrement différent, ce n'est pas le cas. Maintenant, j'essaie de charger une vue sur un viewcontroller par programme.

Si je peux m'expliquer davantage. Il y a 2 contrôleurs de vue. Le premier étant un UITableViewController et le second un UIViewController. A l'intérieur, un UIScrollView. Il existe également plusieurs UIView et la hauteur de certaines de ces vues dépasse la hauteur normale de l'écran.

UITableViewController affiche une liste d'options. En fonction de la sélection de l'utilisateur, un UIView particulier du lot sera chargé dans le UIViewController avec le UIScrollView.

Dans ce scénario, la méthode ci-dessus ne fonctionne pas. Le défilement ne se produit pas. Dois-je faire quelque chose de différent puisque je charge la vue séparément?

J'ai téléchargé un projet de démonstration ici afin que vous puissiez le voir en action. Veuillez consulter le Email.xib et sélectionnez E-mail dans la liste de la vue tabulaire.

26
Isuru

Sur la base d'un examen de votre code, quelques commentaires:

  1. Vous n'avez généralement pas besoin d'ajuster les contraintes lorsqu'une vue apparaît. Si vous vous trouvez en train de faire cela, cela signifie souvent que vous n'avez pas configuré correctement votre storyboard (ou du moins, pas efficacement). La seule fois où vous avez vraiment besoin de définir/créer des contraintes est soit (a) vous ajoutez des vues par programme (ce que je suggère est plus de travail que cela vaut); ou (b) vous devez effectuer un ajustement au moment de l'exécution des contraintes (voir troisième puce sous le point 3 ci-dessous).

  2. Je ne veux pas y croire, mais votre projet avait un tas de code redondant. Par exemple.

    • Vous définissiez le cadre pour la vue de défilement, mais cela est régi par des contraintes, de sorte que cela ne fait rien (c'est-à-dire lorsque les contraintes sont appliquées, tous les paramètres frame définis manuellement seront remplacés). En général, dans la disposition automatique, n'essayez pas de changer directement frame: modifiez les contraintes. Mais aucun changement de contraintes n'est nécessaire de toute façon, donc le point est sans objet.

    • Vous définissiez la taille du contenu pour la vue de défilement, mais dans la mise en page automatique, cela aussi est régi par des contraintes (des sous-vues), donc cela n'était pas nécessaire.

    • Vous définissiez des contraintes pour la vue de défilement (qui étaient déjà nulles), mais vous n'avez pas ajouté la vue de la NIB dans la vue de défilement, ce qui a également déjoué toute intention. La question initiale était de savoir comment modifier la contrainte inférieure de la vue de défilement. Mais la contrainte inférieure pour cela est déjà nulle, donc je ne vois aucune raison de la remettre à zéro.

  3. Je suggère une simplification plus radicale de votre projet:

    • Vous vous rendez la vie beaucoup plus difficile en stockant vos opinions dans les NIB. C'est beaucoup plus facile si vous restez dans le monde du storyboard. Nous pouvons vous aider à faire les choses NIB si vous en avez vraiment besoin, mais pourquoi vous compliquer la vie?

    • Utilisez des prototypes de cellules pour faciliter la conception des cellules de votre tableau. Vous pouvez également définir les séquences pour passer des cellules à la scène suivante. Cela élimine le besoin d'écrire n'importe quel code didSelectRowAtIndexPath ou prepareForSegue. De toute évidence, si vous avez quelque chose que vous devez passer à la scène suivante, utilisez certainement prepareForSegue, mais rien de ce que vous avez présenté jusqu'à présent ne l'exige, donc je l'ai commenté dans mes exemples.

    • En supposant que vous cherchiez un exemple pratique de contraintes changeantes par programme, j'ai configuré la scène de sorte que la vue texte change sa hauteur par programme, en fonction du texte dans la vue texte. Comme toujours, plutôt que de parcourir les contraintes pour trouver celle en question, lors de la modification d'une contrainte existante que IB a créée pour moi, je pense qu'il est beaucoup plus efficace de configurer un IBOutlet pour la contrainte et d'éditer le constant propriété de la contrainte directement, c'est donc ce que j'ai fait. J'ai donc configuré le contrôleur de vue pour être le délégué de la vue texte et j'ai écrit un textViewDidChange qui a mis à jour la contrainte de hauteur de la vue texte:

      #pragma mark - UITextViewDelegate
      
      - (void)textViewDidChange:(UITextView *)textView
      {
          self.textViewHeightConstraint.constant = textView.contentSize.height;
          [self.scrollView layoutIfNeeded];
      }
      

      Remarque, ma vue texte a deux contraintes de hauteur, une contrainte de hauteur minimale obligatoire et une contrainte de priorité moyenne que je modifie ci-dessus en fonction de la quantité de texte. Le point principal est qu'il illustre un exemple pratique de changement de contraintes par programme. Vous ne devriez pas du tout avoir à contourner la contrainte inférieure de scrollview, mais cela montre un exemple concret du moment où vous voudrez peut-être ajuster une contrainte.


Lorsque vous ajoutez un scrollview dans IB, il obtiendra automatiquement toutes les contraintes dont vous avez besoin. Vous ne voulez probablement pas ajouter une contrainte par programme (du moins pas sans supprimer la contrainte inférieure existante).

Deux approches pourraient être plus simples:

  1. Créez un IBOutlet pour votre contrainte de fond existante, par exemple scrollViewBottomConstraint. Ensuite, vous pouvez simplement faire

    self.scrollViewBottomConstraint.constant = 0.0;
    
  2. Ou créez votre vue initialement dans IB où la contrainte inférieure est 0.0 et vous n'avez alors rien à faire du tout par programmation. Si vous souhaitez mettre en page une longue vue de défilement et ses sous-vues, sélectionnez le contrôleur, définissez ses métriques simulées de "déduit" à "forme libre". Ensuite, vous pouvez modifier la taille de la vue, définir les contraintes supérieure et inférieure de la vue de défilement sur zéro, mettre en page tout ce que vous voulez à l'intérieur de la vue de défilement, puis lorsque la vue est présentée au moment de l'exécution, la vue sera redimensionnée de manière appropriée et parce que vous 'ai défini les contraintes supérieure et inférieure de scrollview à 0,0, il sera redimensionné correctement. Cela semble un peu étrange dans IB, mais cela fonctionne comme un charme lorsque l'application fonctionne.

  3. Si vous êtes déterminé à ajouter une nouvelle contrainte, vous pouvez soit supprimer par programme l'ancienne contrainte inférieure, soit définir la priorité des anciennes contraintes inférieures aussi bas que possible, et ainsi votre nouvelle contrainte (avec une priorité plus élevée) aura priorité, et l'ancienne contrainte inférieure de faible priorité ne sera gracieusement pas appliquée.

Mais vous ne voulez certainement pas simplement ajouter une nouvelle contrainte.

56
Rob

Il est possible de créer des prises pour représenter les contraintes de disposition dans votre contrôleur de vue. Sélectionnez simplement la contrainte que vous souhaitez dans le générateur d'interface (par exemple via "sélectionner et modifier" dans le volet des mesures de la vue que vous organisez). Accédez ensuite au volet des prises et faites glisser une "Nouvelle prise de référence" vers votre fichier de code (.h ou .m). Cela liera la contrainte à une instance NSLayoutConstraint à laquelle vous pouvez accéder à partir de votre contrôleur et l'ajuster dynamiquement à la volée (généralement via la propriété constant, qui est mal nommée car ce n'est pas du tout une constante ).

enter image description here

(Notez que dans XCode 6, vous pouvez double-cliquer sur la contrainte pour la sélectionner pour l'édition.)

enter image description here

Soyez prudent lorsque vous ajustez la disposition dans le générateur d'interface, car vous risquez de supprimer la contrainte et de la lier à nouveau à la prise.

21
devios1

En regardant les informations de la console, j'ai l'impression que vous créez une ambiguïté lorsque vous ajoutez deux mêmes types de contrainte.

Donc, au lieu de créer et d'ajouter une nouvelle contrainte, essayez de mettre à jour la contrainte précédente qui se trouve déjà dans le tableau des contraintes.

for(NSLayoutConstraint *constraint in self.view.constraints)
    {
        if(constraint.firstAttribute == NSLayoutAttributeBottom && constraint.secondAttribute == NSLayoutAttributeBottom &&
           constraint.firstItem == self.view && constraint.secondItem == self.scrollView)
        {
            constraint.constant = 0.0;
        }
    }

J'espère que cela t'aides

Même la réponse de Rob fonctionnera!

12
Kunal

Vous pouvez utiliser https://github.com/SnapKit/Masonry pour ajouter des contraintes par programme.

C'est la puissance d'AutoLayout NSLayoutConstraints avec une syntaxe simplifiée, chaînable et expressive. Prend en charge la mise en page automatique iOS et OSX.

UIView *superview = self.view;

UIView *view1 = [[UIView alloc] init];
view1.translatesAutoresizingMaskIntoConstraints = NO;
view1.backgroundColor = [UIColor greenColor];
[superview addSubview:view1];

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[superview addConstraints:@[
//view1 constraints
[NSLayoutConstraint constraintWithItem:view1
                             attribute:NSLayoutAttributeTop
                             relatedBy:NSLayoutRelationEqual
                                toItem:superview
                             attribute:NSLayoutAttributeTop
                            multiplier:1.0
                              constant:padding.top],

[NSLayoutConstraint constraintWithItem:view1
                             attribute:NSLayoutAttributeLeft
                             relatedBy:NSLayoutRelationEqual
                                toItem:superview
                             attribute:NSLayoutAttributeLeft
                            multiplier:1.0
                              constant:padding.left],

[NSLayoutConstraint constraintWithItem:view1
                             attribute:NSLayoutAttributeBottom
                             relatedBy:NSLayoutRelationEqual
                                toItem:superview
                             attribute:NSLayoutAttributeBottom
                            multiplier:1.0
                              constant:-padding.bottom],

[NSLayoutConstraint constraintWithItem:view1
                             attribute:NSLayoutAttributeRight
                             relatedBy:NSLayoutRelationEqual
                                toItem:superview
                             attribute:NSLayoutAttributeRight
                            multiplier:1
                              constant:-padding.right],]];

en quelques lignes

Voici les mêmes contraintes créées à l'aide de MASConstraintMaker

UIEdgeInsets padding = UIEdgeInsetsMake(10, 10, 10, 10);

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(superview.mas_top).with.offset(padding.top); //with is an optional semantic filler
    make.left.equalTo(superview.mas_left).with.offset(padding.left);
    make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
    make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];

Ou encore plus court

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(superview).with.insets(padding);
}];

faire de votre mieux, c'est trier;)

2
ERbittuu