288 votes

Espacer uniformément plusieurs vues dans une vue conteneur

La mise en page automatique me rend la vie difficile. En théorie, elle devait être très utile lorsque j'ai changé de système, mais il semble que je la combatte tout le temps.

J'ai fait un projet de démonstration pour essayer de trouver de l'aide. Quelqu'un sait-il comment faire en sorte que les espaces entre les vues augmentent ou diminuent de façon régulière lorsque la vue est redimensionnée ?

Voici trois étiquettes (espacées manuellement et à la verticale) :

image1

Ce que je veux, c'est qu'elles redimensionnent leur espacement (et non la taille de la vue) de manière égale lorsque je fais une rotation. Par défaut, les vues du haut et du bas s'écrasent vers le centre :

image2

0 votes

Il serait peut-être utile de définir une contrainte d'une étiquette à l'autre, et de définir la relation "supérieur ou égal".

0 votes

De manière programmatique, par exemple en utilisant cette méthode de NSLayoutConstraint : + (id)constraintWithItem :(id)view1 attribute :(NSLayoutAttribute)attr1 relatedBy :(NSLayoutRelation)relation toItem :(id)view2 attribute :(NSLayoutAttribute)attr2 multiplier :(CGFloat)multiplier constant :(CGFloat)c

0 votes

J'ai créé un remplacement générique de UITabBar qui généralise cette approche en utilisant Auto Layout. Vous pouvez consulter les tests pour voir comment je génère mes contraintes Auto Layout. GGTabBar

317voto

cocoanut Points 2396

REGARDEZ, PAS D'ENTRETOISES !

Sur la base des suggestions faites dans la section des commentaires de ma réponse originale, en particulier les suggestions utiles de @Rivera, j'ai simplifié ma réponse originale.

J'utilise des gifs pour illustrer à quel point c'est simple. J'espère que vous trouverez les gifs utiles. Au cas où vous auriez un problème avec les gifs, j'ai inclus l'ancienne réponse ci-dessous avec des captures d'écran simples.

Instructions :

1) Ajoutez vos boutons ou vos étiquettes. J'utilise 3 boutons.

2) Ajoutez une contrainte x centrale de chaque bouton à la vue supérieure :

enter image description here

3) Ajoutez une contrainte de chaque bouton à la contrainte de disposition inférieure :

enter image description here

4) Ajustez la contrainte ajoutée en #3 ci-dessus comme suit :

a) sélectionnez la contrainte, b) supprimer la constante (mise à 0), c) modifiez le multiplicateur comme suit : prenez le nombre de boutons + 1, et en commençant par le haut, définissez le multiplicateur comme suit buttonCountPlus1:1 et ensuite boutonCountPlus1:2 et enfin boutonCountPlus1:3 . (J'explique d'où je tiens cette formule dans l'ancienne réponse ci-dessous, si cela vous intéresse).

enter image description here

5) Voici une démo en cours !

enter image description here

Remarque : si vos boutons ont une hauteur supérieure, vous devrez compenser cela dans la valeur constante puisque la contrainte part du bas du bouton.


Ancienne réponse


Malgré ce que disent les documents d'Apple et l'excellent livre d'Erica Sadun ( La mise en page automatique démystifiée ) disent, il est possible d'égaliser les vues de l'espace sin entretoises. Cette opération est très simple à réaliser dans l'IB et dans le code pour n'importe quel nombre d'éléments que vous souhaitez espacer de manière égale. Tout ce dont vous avez besoin est une formule mathématique appelée "formule de section". C'est plus simple à faire qu'à expliquer. Je vais faire de mon mieux en la démontrant dans l'IB, mais elle est tout aussi facile à réaliser en code.

Dans l'exemple en question, vous devez

1) commencez par définir une contrainte de centre pour chaque étiquette. C'est très simple à faire. Il suffit de faire un glisser-déposer de chaque étiquette vers le bas.

2) Maintenez la touche shift enfoncée, car vous pourriez tout aussi bien ajouter l'autre contrainte que nous allons utiliser, à savoir le "guide de mise en page de l'espace inférieur à l'espace inférieur".

3) Sélectionnez le guide de mise en page "espace inférieur à inférieur", et "centrer horizontalement dans le conteneur". Faites cela pour les 3 étiquettes.

Hold down shift to add these two constraints for each label

En gros, si nous prenons l'étiquette dont nous voulons déterminer les coordonnées et que nous la divisons par le nombre total d'étiquettes plus 1, nous obtenons un nombre que nous pouvons ajouter à IB pour obtenir l'emplacement dynamique. Je simplifie la formule, mais vous pourriez l'utiliser pour définir l'espacement horizontal ou les deux en même temps. C'est super puissant !

Voici nos multiplicateurs.

Étiquette1 = 1/4 = .25,

Étiquette2 = 2/4 = 0,5,

Étiquette3 = 3/4 = .75

(Edit : @Rivera a commenté que vous pouvez simplement utiliser les ratios directement dans le champ du multiplicateur, et xCode avec faire le calcul !)

4) Donc, sélectionnons Label1 et sélectionnons la contrainte inférieure. Comme ceci : enter image description here

5) Sélectionnez le "Deuxième élément" dans l'inspecteur d'attributs.

6) Dans la liste déroulante, sélectionnez "Inverser le premier et le deuxième élément".

7) Remettre à zéro la constante et la valeur wC hAny. (Vous pouvez ajouter un offset ici si vous en avez besoin).

8) C'est la partie critique : Dans le champ du multiplicateur, ajoutez notre premier multiplicateur 0,25.

9) Pendant que vous y êtes, réglez le "Premier élément" supérieur sur "CenterY" puisque nous voulons le centrer sur le centre y de l'étiquette. Voici à quoi tout cela devrait ressembler.

enter image description here

10) Répétez ce processus pour chaque étiquette et ajoutez le multiplicateur correspondant : 0,5 pour l'étiquette 2, et 0,75 pour l'étiquette 3. Voici le produit final dans toutes les orientations avec tous les appareils compacts ! Super simple. J'ai examiné de nombreuses solutions impliquant des rames de code et des espaceurs. C'est de loin la meilleure solution que j'ai vue sur le sujet.

Mise à jour : @kraftydevil ajoute que le guide de mise en page Bottom n'apparaît que dans les storyboards, pas dans les xibs. Utilisez 'Bottom Space to Container' dans les xibs. Bien vu !

enter image description here

0 votes

Je pose la question parce que je n'arrive pas à trouver le menu des contraintes dans la section 4 (Premier élément, Deuxième élément, Relation, etc.) après avoir suivi les étapes précédentes. Après avoir choisi l'inspecteur de taille, tout ce que je vois sont ces sous-menus : Vue, Priorité d'étreinte du contenu, Priorité de résistance à la compression du contenu, et Contraintes.

0 votes

Mon erreur est que je n'ai pas réalisé que je pouvais trouver la contrainte dans le plan du document. #Le numéro 5 devrait être l'inspecteur "Attributs". Elle est initialement nommée "Espace vertical".

0 votes

@kraftydevil Je l'ai changé en "Attributes Inspector". C'est ma faute. Cela a-t-il fonctionné pour vous ? Je trouve cela très puissant. J'ai résolu une mise en page assez complexe en l'utilisant. Pas besoin d'espaceurs.

212voto

Spencer Hall Points 894

Mon approche vous permet donc de le faire dans Interface Builder. Il s'agit de créer des "vues d'espacement" que vous avez réglées pour que les hauteurs soient égales. Ensuite, vous ajoutez des contraintes supérieures et inférieures aux étiquettes (voir la capture d'écran).

enter image description here

Plus précisément, j'ai une contrainte supérieure sur la "vue d'espacement 1" pour superviser avec une contrainte de hauteur de priorité inférieure à 1000 et avec une hauteur égale à toutes les autres "vues d'espacement". La vue d'espacement 4 a une contrainte d'espace inférieure par rapport à la vue supérieure. Chaque étiquette a des contraintes respectives de haut et de bas par rapport aux "vues d'espacement" les plus proches.

Note : Assurez-vous que vous n'avez pas de contraintes d'espace supplémentaires en haut et en bas sur vos étiquettes pour la vue supérieure ; seulement celles pour les 'vues spatiales'. Ceci sera satisfaisant puisque les contraintes supérieures et inférieures sont respectivement sur 'Space View 1' et 'Spacer View 4'.

Duh 1 : J'ai dupliqué ma vue et l'ai simplement mise en mode paysage pour que vous puissiez voir que cela fonctionne.

Duh 2 : Les "vues intercalaires" auraient pu être transparentes.

Duh 3 : Cette approche pourrait être appliquée horizontalement.

10 votes

Ça marche. En fait, les vues d'espacement peuvent être caché et ils produiront toujours la mise en page correcte.

0 votes

C'était super utile. Tout comme le commentaire sur le fait de marquer les vues d'espacement comme cachées. Ainsi, vous pouvez toujours les voir dans votre storyboard/xib, mais elles n'apparaissent pas dans votre application. Très bien.

0 votes

Je l'ai eu. presque fonctionner après plusieurs heures de tentatives répétées. Malheureusement, Xcode continue de supprimer mes contraintes d'espace supérieur vers bouton et d'espace inférieur, brisant ainsi la chaîne entre les boutons et les espaceurs, et les remplace par des contraintes arbitraires d'espace supérieur vers vue supérieure qui sont inutiles.

51voto

jrturton Points 64875

J'ai fait des montagnes russes en adorant autolayout et en le détestant. La clé pour l'aimer semble être d'accepter ce qui suit :

  1. L'édition et l'autocréation "utile" de contraintes par le constructeur d'interface sont pratiquement inutiles, sauf dans les cas les plus triviaux.
  2. La création de catégories pour simplifier les opérations courantes est un sauveteur de vie puisque le code est si répétitif et verbeux.

Cela dit, ce que vous tentez n'est pas simple et serait difficile à réaliser dans le constructeur d'interface. C'est assez simple à réaliser en code. Ce code, dans viewDidLoad crée et positionne trois étiquettes comme vous le demandez :

// Create three labels, turning off the default constraints applied to views created in code
UILabel *label1 = [UILabel new];
label1.translatesAutoresizingMaskIntoConstraints = NO;
label1.text = @"Label 1";

UILabel *label2 = [UILabel new];
label2.translatesAutoresizingMaskIntoConstraints = NO;
label2.text = @"Label 2";

UILabel *label3 = [UILabel new];
label3.translatesAutoresizingMaskIntoConstraints = NO;
label3.text = @"Label 3";

// Add them all to the view
[self.view addSubview:label1];
[self.view addSubview:label2];
[self.view addSubview:label3];

// Center them all horizontally
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label1 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label3 attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]];

// Center the middle one vertically
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label2 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]];

// Position the top one half way up
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label1 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:label2 attribute:NSLayoutAttributeCenterY multiplier:0.5 constant:0]];

// Position the bottom one half way down
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:label3 attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:label2 attribute:NSLayoutAttributeCenterY multiplier:1.5 constant:0]];

Comme je l'ai dit, ce code est beaucoup plus simple avec un couple de méthodes de catégorie dans UIView mais pour plus de clarté, je l'ai fait à la manière longue ici.

La catégorie est aquí pour ceux qui sont intéressés, et il a une méthode pour espacer uniformément un ensemble de vues le long d'un axe particulier.

0 votes

Vous, monsieur, êtes un sauveur de vies. Je n'avais pas compris le numéro 1 jusqu'à maintenant. Le constructeur d'interface me rend fou, mais je pensais qu'il était capable de faire toutes les mêmes choses. Je préfère utiliser du code de toute façon, donc c'est parfait, et je vais m'atteler aux méthodes de catégorie.

0 votes

Bien qu'elle soit très proche, cette solution ne résout pas exactement le problème posé (conserver la même taille de vue avec un espacement régulier entre les sous-vues). Cette solution est la réponse raffinée du cas général.

21voto

Ben Dolman Points 1355

La plupart de ces solutions dépendent du fait qu'il y ait un nombre impair d'articles pour que vous puissiez prendre l'article du milieu et le centrer. Que se passe-t-il si vous avez un nombre pair d'articles et que vous voulez quand même qu'ils soient répartis uniformément ? Voici une solution plus générale. Elle permet de répartir uniformément n'importe quel nombre d'éléments le long de l'axe vertical ou horizontal.

Exemple d'utilisation pour distribuer verticalement 4 étiquettes dans leur vue supérieure :

[self.view addConstraints:
     [NSLayoutConstraint constraintsForEvenDistributionOfItems:@[label1, label2, label3, label4]
                                        relativeToCenterOfItem:self.view
                                                    vertically:YES]];

NSLayoutConstraint+EvenDistribution.h

@interface NSLayoutConstraint (EvenDistribution)

/**
 * Returns constraints that will cause a set of views to be evenly distributed horizontally
 * or vertically relative to the center of another item. This is used to maintain an even
 * distribution of subviews even when the superview is resized.
 */
+ (NSArray *) constraintsForEvenDistributionOfItems:(NSArray *)views
                             relativeToCenterOfItem:(id)toView
                                         vertically:(BOOL)vertically;

@end

NSLayoutConstraint+EvenDistribution.m

@implementation NSLayoutConstraint (EvenDistribution)

+(NSArray *)constraintsForEvenDistributionOfItems:(NSArray *)views
                           relativeToCenterOfItem:(id)toView vertically:(BOOL)vertically
{
    NSMutableArray *constraints = [NSMutableArray new];
    NSLayoutAttribute attr = vertically ? NSLayoutAttributeCenterY : NSLayoutAttributeCenterX;

    for (NSUInteger i = 0; i < [views count]; i++) {
        id view = views[i];
        CGFloat multiplier = (2*i + 2) / (CGFloat)([views count] + 1);
        NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem:view
                                                                      attribute:attr
                                                                      relatedBy:NSLayoutRelationEqual
                                                                         toItem:toView
                                                                      attribute:attr
                                                                     multiplier:multiplier
                                                                       constant:0];
        [constraints addObject:constraint];
    }

    return constraints;
}

@end

1 votes

Cela a fonctionné en supprimant toutes les contraintes et, immédiatement après, en ajoutant les contraintes Width et Height à ces objets, puis en appelant la méthode constraintsForEvenDistributionOfItems:relativeToCenterOfItem:vertically :

0 votes

@carlos_ms J'aimerais bien voir votre code, car lorsque j'essaie avec un nombre pair d'éléments, ce code fonctionne parfaitement lors du changement d'orientation de l'appareil. Êtes-vous sûr que vous n'avez pas par inadvertance d'autres contraintes (comme les contraintes de redimensionnement automatique) qui causent un conflit ?

0 votes

Voici un exemple de création de 4 étiquettes et de leur espacement régulier le long de l'axe vertical : gist.github.com/bdolman/5379465

17voto

smileyborg Points 4427

Consultez la bibliothèque open source PureLayout . Il offre quelques méthodes API pour distribuer les vues, y compris des variantes où l'espacement entre chaque vue est fixe (la taille de la vue varie selon les besoins), et où la taille de chaque vue est fixe (l'espacement entre les vues varie selon les besoins). Notez que toutes ces méthodes sont réalisées sin l'utilisation de toute "vue intercalaire".

Desde NSArray+PureLayout.h :

// NSArray+PureLayout.h

// ...

/** Distributes the views in this array equally along the selected axis in their superview. Views will be the same size (variable) in the dimension along the axis and will have spacing (fixed) between them. */
- (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
                                alignedTo:(ALAttribute)alignment
                         withFixedSpacing:(CGFloat)spacing;

/** Distributes the views in this array equally along the selected axis in their superview. Views will be the same size (fixed) in the dimension along the axis and will have spacing (variable) between them. */
- (NSArray *)autoDistributeViewsAlongAxis:(ALAxis)axis
                                alignedTo:(ALAttribute)alignment
                            withFixedSize:(CGFloat)size;

// ...

Comme il s'agit d'une source ouverte, si vous souhaitez voir comment cela est réalisé sans vues d'espacement, jetez un coup d'œil à l'implémentation. (Elle dépend de l'utilisation de l'outil constant y multiplier pour les contraintes).

0 votes

Beau travail ! Bien que vous devriez probablement renommer le repo afin de le rendre disponible via cocoapods.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X