Espacer uniformément plusieurs vues dans une vue de conteneur

279

La mise en page automatique me rend la vie difficile. En théorie, ça allait être vraiment utile quand je suis passé, mais je semble me battre tout le temps.

J'ai fait un projet de démonstration pour essayer de trouver de l'aide. Est-ce que quelqu'un sait comment augmenter ou diminuer uniformément les espaces entre les vues chaque fois que la vue est redimensionnée?

Voici trois étiquettes (espacées manuellement même verticalement):

image1

Ce que je veux, c'est qu'ils redimensionnent leur espacement (pas la taille de la vue) uniformément lorsque je tourne. Par défaut, les vues du haut et du bas s'affichent vers le centre:

image2

nothappybob
la source
peut-être définir une contrainte d'une étiquette à une autre, et définir une relation "supérieure à ou égale" sera utile
BergP
par exemple par programmation avec l'utilisation de cette méthode de NSLayoutConstraint: + (id) constraintWithItem: (id) attribut view1: (NSLayoutAttribute) attr1 relatedBy: (NSLayoutRelation) relation toItem: (id) attribut view2: (NSLayoutAttribute) multiplicateur attr2: (CGFloat) multiplicateur constante: (CGFloat) c
BergP
J'ai ouvert un remplacement générique UITabBar qui généralise cette approche en utilisant la mise en page automatique. Vous pouvez consulter les tests pour voir comment je génère mes contraintes de disposition automatique. GGTabBar
Goles

Réponses:

211

Mon approche vous permet donc de le faire dans le constructeur d'interface. Ce que vous faites est de créer des «vues d'espacement» que vous avez définies pour correspondre également aux hauteurs. Ajoutez ensuite des contraintes supérieure et inférieure aux étiquettes (voir la capture d'écran).

entrez la description de l'image ici

Plus précisément, j'ai une contrainte supérieure sur 'Spacer View 1' à superview avec une contrainte de hauteur de priorité inférieure à 1000 et avec Height Equals à toutes les autres 'spacer vues'. 'Spacer View 4' a une contrainte d'espace inférieur à la vue d'ensemble. Chaque étiquette a des contraintes supérieures et inférieures respectives à ses «vues d'espacement» les plus proches.

Remarque: assurez-vous que vous n'avez PAS de contraintes d'espace supérieur / inférieur supplémentaires sur vos étiquettes pour la vue d'ensemble; juste celles des «vues de l'espace». Cela sera satisfaisant puisque les contraintes supérieures et inférieures se trouvent respectivement sur «Space View 1» et «Spacer View 4».

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

Duh 2: Les «vues d'espacement» auraient pu être transparentes.

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

Spencer Hall
la source
10
Cela marche. En fait, les vues d'espacement peuvent être masquées et elles produiront toujours la disposition correcte.
paulmelnikow
C'était super utile. Tout comme le commentaire sur le simple marquage des vues d'espacement comme masquées. Ensuite, vous pouvez toujours les voir dans votre storyboard / xib, mais ils n'apparaissent pas dans votre application. Très agréable.
shulmey
Je l'ai presque fait 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 la chaîne entre les boutons et les espaceurs et les remplaçant par des contraintes arbitraires d'espace supérieur vers vue d'ensemble qui sont inutiles.
Desty
La même approche fonctionne pour moi dans une disposition horizontale - j'ai des vues à largeur fixe séparées par des vues d'espacement de largeur égale. C'est génial que je n'ai pas besoin de le mélanger avec du code. Merci!
Kamil Nomtek.com
2
Pas besoin d'utiliser des entretoises. Voir ma réponse ci-dessous.
smileBot
316

REGARDEZ, PAS D'ESPACES!

Sur la base des suggestions de la section commentaires de ma réponse d'origine, en particulier des suggestions utiles de @ Rivera, j'ai simplifié ma réponse d'origine.

J'utilise des gifs pour illustrer à quel point c'est simple. J'espère que vous trouverez les gifs utiles. Juste 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 étiquettes. J'utilise 3 boutons.

2) Ajoutez une contrainte centrale x de chaque bouton à la vue d'ensemble:

entrez la description de l'image ici

3) Ajoutez une contrainte de chaque bouton à la contrainte de mise en page inférieure:

entrez la description de l'image ici

4) Ajustez la contrainte ajoutée au point 3 ci-dessus comme suit:

a) sélectionnez la contrainte, b) supprimez la constante (définie sur 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 , puis sur buttonCountPlus1 : 2 , et enfin buttonCountPlus1: 3 . (Si vous êtes intéressé, je vous explique d'où j'ai tiré cette formule dans l'ancienne réponse ci-dessous).

entrez la description de l'image ici

5) Voici une démo en cours d'exécution!

entrez la description de l'image ici

Remarque: Si vos boutons ont des hauteurs plus grandes, vous devrez compenser cela dans la valeur constante car la contrainte vient du bas du bouton.


Ancienne réponse


Malgré les documents d'Apple et l'excellent livre d'Erica Sadun ( disent Auto Layout Demystified ), il est possible d'espacer uniformément les vues sans entretoises. C'est très simple à faire en IB et en code pour n'importe quel nombre d'éléments que vous souhaitez espacer uniformément. Tout ce dont vous avez besoin est une formule mathématique appelée "formule de section". C'est plus simple à faire qu'à expliquer. Je ferai de mon mieux en le démontrant en IB, mais c'est tout aussi facile à faire en code.

Dans l'exemple en question, vous

1) commencez par définir chaque étiquette pour avoir une contrainte centrale. C'est très simple à faire. Contrôlez simplement le glissement de chaque étiquette vers le bas.

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

3) Sélectionnez le "guide de mise en page de l'espace inférieur vers le bas" et "centre horizontalement dans le conteneur". Faites cela pour les 3 étiquettes.

Maintenez la touche Maj enfoncée pour ajouter ces deux contraintes pour chaque étiquette

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

Voici nos multiplicateurs.

Label1 = 1/4 = .25,

Label2 = 2/4 = 0,5,

Label3 = 3/4 = 0,75

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

4) Alors, sélectionnons Label1 et sélectionnons la contrainte inférieure. Comme ça: entrez la description de l'image ici

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

6) Dans le menu déroulant, 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 décalage ici si vous en avez besoin).

8) Voici la partie critique: dans le champ multiplicateur, ajoutez notre premier multiplicateur 0,25.

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

entrez la description de l'image ici

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

Mise à jour: @kraftydevil ajoute que le guide de disposition inférieur n'apparaît que dans les storyboards, pas dans xibs. Utilisez 'Bottom Space to Container' dans les xibs. Bonne prise!

entrez la description de l'image ici

smileBot
la source
2
Je suis toujours aux prises avec ça. Je pense que vous devez utiliser Xcode 6 car il n'y a pas d'option 'wC hAny' dans les contraintes Xcode 5. Lorsque je suis vos instructions, toutes les sous-vues restent bloquées en haut au centre.
kraftydevil
4
@kraftydevil AFAIK Le guide de disposition inférieur n'apparaît que dans les storyboards, pas dans xibs. Utilisez 'Bottom Space to Container' dans les xibs.
Timur Kuchkarov
6
Oh - il convient également de noter que IB dans Xcode 6 prend en charge les multiplicateurs de rapport, de sorte que vous pouvez mettre des choses comme "1: 4", "2: 5", "3: 4", etc.
Benjohn
3
Essayer de comprendre comment le faire horizontalement - comme le premier TextView 1/3 de la gauche, et un deuxième TextView 2/3 (le reste du chemin) ... ... (EDIT) l'a compris - faites le premier 1/3 (à gauche) Trailing-Space to Right Margin, reverse, zero, assign like like before ...
kfmfe04
3
Pour l'espacement horizontal, définissez simplement les contraintes de fin sur la vue d'ensemble pour chaque sous-vue, puis définissez le multiplicateur de rapport, par exemple. 5: 1, 5: 2, 5: 3, etc. se déplaçant de gauche à droite.
user3344977
98

Solution Interface Builder très rapide:

Pour qu'un nombre illimité de vues soit uniformément espacé dans une superview, donnez simplement à chacune une contrainte "Aligner le centre X sur la superview" pour la disposition horizontale, ou "Aligner le centre Y superview" pour la disposition verticale, et définissez le multiplicateur sur N:p( REMARQUE: certains ont eu plus de chance avec p:N- voir ci-dessous )

N = total number of views, et

p = position of the view including spaces

La première position est 1, puis un espace, faisant la position suivante 3, donc p devient une série [1,3,5,7,9, ...]. Fonctionne pour n'importe quel nombre de vues.

Donc, si vous avez 3 vues à espacer, cela ressemble à ceci:

Illustration de la répartition uniforme des vues dans IB

EDIT Remarque: Le choix de N:pou p:Ndépend de l'ordre des relations de votre contrainte d'alignement. Si "First Item" est Superview.Center, vous pouvez utiliser p:N, alors que si Superview.Center est "Second Item", vous pouvez utiliser N:p. En cas de doute, essayez les deux ... :-)

Infliger
la source
Cette solution ne reflétera pas la conception si vous passez à une autre langue
wod
@wod Que voulez-vous dire? Changer quoi dans une autre langue? Et qu'est-ce qui se casse exactement?
Mete
4
que faire si les éléments ne sont pas de la même taille? ce modèle ne fonctionnera pas correctement non?
ucangetit
2
Je ne comprends pas comment cette solution est correcte? L'écart entre le bord de l'écran et les éléments extérieurs est différent de l'écart entre les éléments extérieurs et l'élément central?
myles
6
Cela n'a pas été le cas pour moi "tel quel". J'ai dû inverser les multiplicateurs (par exemple, le 3: 1 du premier élément est converti en 1: 3). Il suffit de jeter cela si cela aide quelqu'un.
Ces
70

Depuis iOS 9, Apple a rendu cela très facile avec le (très attendu) UIStackView. Sélectionnez simplement les vues que vous souhaitez contenir dans Interface Builder et choisissez Éditeur -> Incorporer dans -> Vue de la pile. Définissez les contraintes de largeur / hauteur / marge appropriées pour la vue de la pile et assurez-vous de définir la propriété Distribution sur 'Espacement égal':

Bien sûr, si vous devez prendre en charge iOS 8 ou une version antérieure, vous devrez choisir l'une des autres options.

Glorfindel
la source
Oui, c'est triste que cela n'ait pas de compatibilité rétro, donc jusqu'à ce que votre application ait besoin du support de la réponse IOS8 @Mete est la meilleure pour l'instant.
ZiggyST
51

J'ai fait des montagnes russes en aimant la mise en page automatique et en la détestant. La clé pour l'aimer semble être d'accepter ce qui suit:

  1. L'édition du constructeur d'interface et la création automatique "utile" de contraintes sont presque inutiles pour tous sauf le cas le plus trivial
  2. La création de catégories pour simplifier les opérations courantes est une bouée de sauvetage puisque le code est tellement répétitif et verbeux.

Cela dit, ce que vous tentez n'est pas simple et serait difficile à réaliser dans le générateur d'interface. C'est assez simple à faire en code. Ce code, dans viewDidLoad, crée et positionne trois étiquettes comme vous les 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 très simplifié avec quelques méthodes de catégorie dans UIView , mais pour plus de clarté, je l'ai fait longtemps ici.

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

jrturton
la source
Vous monsieur, êtes une bouée de sauvetage. Je n'ai pas compris le n ° 1 jusqu'à présent. 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 de toute façon utiliser du code, donc c'est parfait, et je vais passer directement aux méthodes de catégorie.
nothappybob
Bien que très proche, cela ne résout pas tout à fait le problème exact posé (garder la taille de la vue la même avec un espacement régulier entre les sous-vues). Cette solution est la réponse raffinée au cas général.
smileyborg
21

La plupart de ces solutions dépendent de la présence d'un nombre impair d'articles afin que vous puissiez prendre l'élément du milieu et le centrer. Que se passe-t-il si vous avez un nombre pair d'articles que vous souhaitez tout de même répartir? Voici une solution plus générale. Cette catégorie répartira 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 superview:

[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
Ben Dolman
la source
1
Cela a fonctionné en supprimant toutes les contraintes et immédiatement après, en ajoutant les contraintes de largeur et de hauteur à ces objets, puis en appelant la méthode contraintesForEvenDistributionOfItems: relativeToCenterOfItem: verticalement:
carlos_ms
@carlos_ms J'adorerais voir votre code, car lorsque j'essaie avec un nombre pair d'éléments, ce code fonctionne parfaitement lors du changement d'orientation des appareils. Êtes-vous sûr de ne pas avoir par inadvertance d'autres contraintes (telles que les contraintes de redimensionnement automatique) qui provoquent un conflit?
Ben Dolman
Voici un exemple de création de 4 étiquettes et d'espacement uniforme le long de l'axe vertical: gist.github.com/bdolman/5379465
Ben Dolman
20

La manière correcte et la plus simple consiste à utiliser les vues de pile.

  1. Ajoutez vos étiquettes / vues à la vue de la pile :

entrez la description de l'image ici

  1. Sélectionnez la vue de la pile et définissez la distribution sur Espacement égal :

entrez la description de l'image ici

  1. Ajoutez des contraintes d' espacement aux voisins les plus proches dans la vue de la pile et mettez à jour les cadres:

entrez la description de l'image ici

  1. Ajoutez des contraintes de hauteur à toutes les étiquettes (facultatif). Nécessaire uniquement pour les vues qui n'ont pas de taille intrinsèque). Par exemple, les étiquettes n'ont pas besoin de contraintes de hauteur et doivent uniquement définir numberOfLines = 3 ou 0.

entrez la description de l'image ici

  1. Profitez de l'aperçu:

entrez la description de l'image ici

Oleh Kudinov
la source
2
Ceci est> = iOS 9.0 uniquement, vérifiez donc votre cible de déploiement avant de commencer à l'implémenter :) C'est bien de voir que cela a été ajouté!
DivideByZer0
1
2018 maintenant, utilisez la vue de pile
Jingshao Chen
17

Découvrez la bibliothèque open source PureLayout . Il propose 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 tout cela est accompli sans l'utilisation de "vues d'espacement".

De 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 tout est open source, si vous êtes intéressé de voir comment cela est possible sans les vues d'espacement, jetez un œil à la mise en œuvre. (Cela dépend de la mise à profit des contraintes constant et multiplier des contraintes.)

smileyborg
la source
Bon travail! Bien que vous deviez probablement renommer le dépôt afin de pouvoir le rendre disponible via des cocoapods.
jrturton
9

J'ai un problème similaire et j'ai découvert ce post. Cependant, aucune des réponses actuellement fournies ne résout le problème comme vous le souhaitez. Ils ne font pas l'espacement également, mais distribuent plutôt le centre des étiquettes de manière égale. Il est important de comprendre que ce n'est pas pareil. J'ai construit un petit diagramme pour illustrer cela.

Voir l'illustration de l'espacement

Il y a 3 vues, toutes hautes de 20 points. L'utilisation de l'une des méthodes suggérées répartit également les centres des vues et vous donne la disposition illustrée. Notez que le centre y des vues est espacé de manière égale. Cependant, l'espacement entre la vue d'ensemble et la vue de dessus est de 15 points, tandis que l'espacement entre les vues secondaires n'est que de 5 points. Pour que les vues soient également espacées, elles doivent toutes deux être de 10 pt, c'est-à-dire que toutes les flèches bleues doivent être de 10 pt.

Néanmoins, je n'ai pas encore trouvé de bonne solution générique. Actuellement, ma meilleure idée est d'insérer des "vues d'espacement" entre les sous-vues et de définir des hauteurs des vues d'espacement égales.

Florian
la source
1
J'ai utilisé la solution des vues d'espacement cachées, qui fonctionne bien.
paulmelnikow
8

J'ai pu résoudre ce problème entièrement dans IB:

  1. Créez des contraintes pour aligner le centre Y de chacune de vos sous-vues sur le bord inférieur de la vue d'ensemble.
  2. Définissez le multiplicateur de chacune de ces contraintes sur 1 / 2n, 3 / 2n, 5 / 2n,…, n-1 / 2n où n est le nombre de sous-vues que vous distribuez.

Donc, si vous avez trois étiquettes, définissez les multiplicateurs de chacune de ces contraintes sur 0,1666667, 0,5, 0,833333.

Hayesk
la source
1
En fait, je devais faire 1 / n 3 / n 5 / n 7 / n jusqu'à (2n-1) / n
Hamzah Malik
5

J'ai trouvé une méthode parfaite et simple. La disposition automatique ne vous permet pas de redimensionner les espaces de manière égale, mais elle vous permet de redimensionner les vues de manière égale. Mettez simplement des vues invisibles entre vos champs et dites à la mise en page automatique de les garder à la même taille. Ça marche parfaitement!

XIB initial

XIB étiré

Une chose à noter cependant; lorsque je réduisais la taille dans le concepteur d'interface, parfois cela devenait confus et laissait une étiquette où il se trouvait, et il y avait un conflit si la taille était modifiée de façon étrange. Sinon, cela a parfaitement fonctionné.

edit: J'ai trouvé que le conflit est devenu un problème. Pour cette raison, j'ai pris l'une des contraintes d'espacement, je l'ai supprimée et je l'ai remplacée par deux contraintes, une supérieure ou égale et une inférieure ou égale. Les deux étaient de la même taille et avaient une priorité beaucoup plus faible que les autres contraintes. Le résultat n'est plus un conflit.

Owen Godfrey
la source
4

En s'appuyant sur la réponse de Ben Dolman, cela répartit les vues de manière plus égale (avec rembourrage, etc.):

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

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

    return constraints;
}
Ray W
la source
3

Avec des étiquettes, cela fonctionne très bien au moins:

@"H:|-15-[first(==second)]-[second(==third)]-[third(==first)]-15-|

Si le premier a la même largeur que le second, le deuxième le troisième et le troisième le premier, alors ils auront tous la même largeur ... Vous pouvez le faire à la fois horizontalement (H) et verticalement (V).

Johannes
la source
3

version swift 3

let _redView = UIView()
        _redView.backgroundColor = UIColor.red
        _redView.translatesAutoresizingMaskIntoConstraints = false

        let _yellowView = UIView()
        _yellowView.backgroundColor = UIColor.yellow
        _yellowView.translatesAutoresizingMaskIntoConstraints = false

        let _blueView = UIView()
        _blueView.backgroundColor = UIColor.blue
        _blueView.translatesAutoresizingMaskIntoConstraints = false

        self.view.addSubview(_redView)
        self.view.addSubview(_yellowView)
        self.view.addSubview(_blueView)

        var views = ["_redView": _redView, "_yellowView": _yellowView, "_blueView":_blueView]

        var nslayoutConstraint_H = NSLayoutConstraint.constraints(withVisualFormat: "|->=0-[_redView(40)]->=0-[_yellowView(40)]->=0-[_blueView(40)]->=0-|", options: [.alignAllTop, .alignAllBottom], metrics: nil, views: views)
        self.view.addConstraints(nslayoutConstraint_H)

        var nslayoutConstraint_V = NSLayoutConstraint.constraints(withVisualFormat: "V:[_redView(60)]", options: NSLayoutFormatOptions.init(rawValue: 0), metrics: nil, views: views)
        self.view.addConstraints(nslayoutConstraint_V)


        let constraint_red = NSLayoutConstraint.init(item: self.view, attribute: .centerY, relatedBy: .equal, toItem: _redView, attribute: .centerY, multiplier: 1, constant: 0)
        self.view.addConstraint(constraint_red)

        let constraint_yellow = NSLayoutConstraint.init(item: self.view, attribute: .centerX, relatedBy: .equal, toItem: _yellowView, attribute: .centerX, multiplier: 1, constant: 0)
        self.view.addConstraint(constraint_yellow)

        let constraint_yellow1 = NSLayoutConstraint.init(item: _redView, attribute: .centerX, relatedBy: .equal, toItem: _yellowView, attribute: .leading, multiplier: 0.5, constant: 0)
        self.view.addConstraint(constraint_yellow1)

        let constraint_yellow2 = NSLayoutConstraint.init(item: _blueView, attribute: .centerX, relatedBy: .equal, toItem: _yellowView, attribute: .leading, multiplier: 1.5, constant: 40)
        self.view.addConstraint(constraint_yellow2)
Sarath Raveendran
la source
1
L'élaboration de la façon dont ce code répond aux questions aiderait les futurs visiteurs.
JAL
2

Je sais que cela fait un moment depuis la première réponse, mais je viens de rencontrer le même problème et je veux partager ma solution. Pour les générations à venir ...

J'ai défini mes vues sur viewDidLoad:

- (void)viewDidLoad {

    [super viewDidLoad];

    cancelButton = [UIButton buttonWithType: UIButtonTypeRoundedRect];
    cancelButton.translatesAutoresizingMaskIntoConstraints = NO;
    [cancelButton setTitle:@"Cancel" forState:UIControlStateNormal];
    [self.view addSubview:cancelButton];

    middleButton = [UIButton buttonWithType: UIButtonTypeRoundedRect];
    middleButton.translatesAutoresizingMaskIntoConstraints = NO;
    [middleButton setTitle:@"Middle" forState:UIControlStateNormal];
    [self.view addSubview:middleButton];

    nextButton = [UIButton buttonWithType: UIButtonTypeRoundedRect];
    nextButton.translatesAutoresizingMaskIntoConstraints = NO;
    [nextButton setTitle:@"Next" forState:UIControlStateNormal];
    [self.view addSubview:nextButton];


    [self.view setNeedsUpdateConstraints];

}

Et puis, sur updateViewConstrains, je supprime d'abord toutes les contraintes, puis je crée le dictionnaire des vues, puis je calcule l'espace à utiliser entre les vues. Après cela, j'utilise simplement le Visual Language Format pour définir les contraintes:

- (void)updateViewConstraints {


    [super updateViewConstraints];

    [self.view removeConstraints:self.view.constraints];

    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(cancelButton, nextButton, middleButton);

    float distance=(self.view.bounds.size.width-cancelButton.intrinsicContentSize.width-nextButton.intrinsicContentSize.width-middleButton.intrinsicContentSize.width-20-20)/  ([viewsDictionary count]-1);  // 2 times 20 counts for the left & rigth margins
    NSNumber *distancies=[NSNumber numberWithFloat:distance];

//    NSLog(@"Distancies: %@", distancies);
//    
//    NSLog(@"View Width: %f", self.view.bounds.size.width);
//    NSLog(@"Cancel Width: %f", cancelButton.intrinsicContentSize.width);
//    NSLog(@"Middle Width: %f", middleButton.intrinsicContentSize.width);
//    NSLog(@"Next Width: %f", nextButton.intrinsicContentSize.width);



    NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"|-[cancelButton]-dis-[middleButton]-dis-[nextButton]-|"
                                                                   options:NSLayoutFormatAlignAllBaseline
                                                                   metrics:@{@"dis":distancies}
                                                                     views:viewsDictionary];


    [self.view addConstraints:constraints];



    constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:[nextButton]-|"
                                                          options:0
                                                          metrics:nil
                                                            views:viewsDictionary];
    [self.view addConstraints:constraints];



}

La bonne chose à propos de cette méthode est que vous devez faire très peu de mathématiques. Je ne dis pas que c'est la solution parfaite, mais je travaille pour la mise en page que j'essayais de réaliser.

J'espère que ça aide.

Marcal
la source
2

Voici une solution qui centrera verticalement n'importe quel nombre de sous-vues, même si elles ont des tailles uniques. Ce que vous voulez faire est de créer un conteneur de niveau intermédiaire, de le centrer dans la vue d'ensemble, puis de placer toutes les sous-vues dans le conteneur et de les organiser les unes par rapport aux autres. Mais surtout, vous devez également les contraindre en haut et en bas du conteneur, afin que le conteneur puisse être correctement dimensionné et centré dans la vue d'ensemble. En calculant la hauteur correcte à partir de ses vues secondaires, le conteneur peut être centré verticalement.

Dans cet exemple, selfest la vue d'ensemble dans laquelle vous centrez toutes les sous-vues.

NSArray *subviews = @[ (your subviews in top-to-bottom order) ];

UIView *container = [[UIView alloc] initWithFrame:CGRectZero];
container.translatesAutoresizingMaskIntoConstraints = NO;
for (UIView *subview in subviews) {
    subview.translatesAutoresizingMaskIntoConstraints = NO;
    [container addSubview:subview];
}
[self addSubview:container];

[self addConstraint:[NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual
                                                    toItem:self attribute:NSLayoutAttributeLeft multiplier:1.0f constant:0.0f]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual
                                                    toItem:self attribute:NSLayoutAttributeRight multiplier:1.0f constant:0.0f]];
[self addConstraint:[NSLayoutConstraint constraintWithItem:container attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual
                                                    toItem:self attribute:NSLayoutAttributeCenterY multiplier:1.0f constant:0.0f]];

if (0 < subviews.count) {
    UIView *firstSubview = subviews[0];
    [container addConstraint:[NSLayoutConstraint constraintWithItem:firstSubview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
                                                             toItem:container attribute:NSLayoutAttributeTop multiplier:1.0f constant:0.0f]];
    UIView *lastSubview = subviews.lastObject;
    [container addConstraint:[NSLayoutConstraint constraintWithItem:lastSubview attribute:NSLayoutAttributeBottom relatedBy:NSLayoutRelationEqual
                                                             toItem:container attribute:NSLayoutAttributeBottom multiplier:1.0f constant:0.0f]];

    UIView *subviewAbove = nil;
    for (UIView *subview in subviews) {
        [container addConstraint:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual
                                                                 toItem:container attribute:NSLayoutAttributeCenterX multiplier:1.0f constant:0.0f]];
        if (subviewAbove) {
            [container addConstraint:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
                                                                     toItem:subviewAbove attribute:NSLayoutAttributeBottom multiplier:1.0f constant:10.0f]];
        }
        subviewAbove = subview;
    }
}
Kevin Conner
la source
C'est la meilleure solution lorsque vous utilisez des vues de tailles différentes qui doivent être centrées.
noobsmcgoobs
2

Je viens de résoudre mon problème en utilisant la fonction multiplicateur. Je ne suis pas sûr que cela fonctionne dans tous les cas, mais pour moi, cela a fonctionné parfaitement. Je suis sur Xcode 6.3 FYI.

J'ai fini par faire:

1) Premièrement, le positionnement de mes boutons sur un écran d'une largeur de 320 pixels était réparti de la façon dont je voulais qu'il apparaisse sur un périphérique de 320 pixels.

étape 1: positionnement des boutons

2) Ensuite, j'ai ajouté une contrainte d'espace principale à la vue d'ensemble sur tous mes boutons.

étape 2: ajouter des contraintes d'espace principales

3) Ensuite, j'ai modifié les propriétés de l'espace de tête pour que la constante soit 0 et que le multiplicateur soit le décalage x divisé par la largeur de l'écran (par exemple, mon premier bouton était à 8 pixels du bord gauche, j'ai donc réglé mon multiplicateur sur 8/320)

4) Ensuite, l'étape importante ici consiste à modifier le deuxième élément de la relation de contrainte pour qu'il soit superview.Trailing au lieu de superview.leading. Ceci est essentiel parce que superview.Leading est 0 et à la fin dans mon cas est 320, donc 8/320 est 8 px sur un périphérique 320px, puis lorsque la largeur de la superview passe à 640 ou autre, les vues se déplacent toutes dans un rapport par rapport à la largeur de la taille de l'écran 320px. Les mathématiques ici sont beaucoup plus simples à comprendre.

étapes 3 et 4: changez le multiplicateur en xPos / screenWidth et définissez le deuxième élément sur .Trailing

ucangetit
la source
2

De nombreuses réponses ne sont pas correctes, mais obtiennent de nombreux décomptes. Ici, je viens d'écrire une solution par programme, les trois vues sont alignées horizontalement, sans utiliser de vues d'espacement , mais cela ne fonctionne que lorsque les largeurs des étiquettes sont connues lorsqu'elles sont utilisées dans le storyboard .

NSDictionary *views = NSDictionaryOfVariableBindings(_redView, _yellowView, _blueView);

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"|->=0-[_redView(40)]->=0-[_yellowView(40)]->=0-[_blueView(40)]->=0-|" options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom metrics:nil views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_redView(60)]" options:0 metrics:nil views:views]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:_redView attribute:NSLayoutAttributeCenterY multiplier:1 constant:0]];

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yellowView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:_redView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yellowView attribute:NSLayoutAttributeLeading multiplier:0.5 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:_blueView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yellowView attribute:NSLayoutAttributeLeading multiplier:1.5 constant:40]];
zgjie
la source
2

Voici encore une autre réponse. Je répondais à une question similaire et j'ai vu le lien renvoyé à cette question. Je n'ai pas vu de réponse similaire à la mienne. J'ai donc pensé à l'écrire ici.

  class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.whiteColor()
        setupViews()
    }

    var constraints: [NSLayoutConstraint] = []

    func setupViews() {

        let container1 = createButtonContainer(withButtonTitle: "Button 1")
        let container2 = createButtonContainer(withButtonTitle: "Button 2")
        let container3 = createButtonContainer(withButtonTitle: "Button 3")
        let container4 = createButtonContainer(withButtonTitle: "Button 4")

        view.addSubview(container1)
        view.addSubview(container2)
        view.addSubview(container3)
        view.addSubview(container4)

        [

            // left right alignment
            container1.leftAnchor.constraintEqualToAnchor(view.leftAnchor, constant: 20),
            container1.rightAnchor.constraintEqualToAnchor(view.rightAnchor, constant: -20),
            container2.leftAnchor.constraintEqualToAnchor(container1.leftAnchor),
            container2.rightAnchor.constraintEqualToAnchor(container1.rightAnchor),
            container3.leftAnchor.constraintEqualToAnchor(container1.leftAnchor),
            container3.rightAnchor.constraintEqualToAnchor(container1.rightAnchor),
            container4.leftAnchor.constraintEqualToAnchor(container1.leftAnchor),
            container4.rightAnchor.constraintEqualToAnchor(container1.rightAnchor),


            // place containers one after another vertically
            container1.topAnchor.constraintEqualToAnchor(view.topAnchor),
            container2.topAnchor.constraintEqualToAnchor(container1.bottomAnchor),
            container3.topAnchor.constraintEqualToAnchor(container2.bottomAnchor),
            container4.topAnchor.constraintEqualToAnchor(container3.bottomAnchor),
            container4.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor),


            // container height constraints
            container2.heightAnchor.constraintEqualToAnchor(container1.heightAnchor),
            container3.heightAnchor.constraintEqualToAnchor(container1.heightAnchor),
            container4.heightAnchor.constraintEqualToAnchor(container1.heightAnchor)
            ]
            .forEach { $0.active = true }
    }


    func createButtonContainer(withButtonTitle title: String) -> UIView {
        let view = UIView(frame: .zero)
        view.translatesAutoresizingMaskIntoConstraints = false

        let button = UIButton(type: .System)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.setTitle(title, forState: .Normal)
        view.addSubview(button)

        [button.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor),
            button.leftAnchor.constraintEqualToAnchor(view.leftAnchor),
            button.rightAnchor.constraintEqualToAnchor(view.rightAnchor)].forEach { $0.active = true }

        return view
    }
}

entrez la description de l'image ici

Et encore une fois, cela peut être fait assez facilement avec iOS9 UIStackViews.

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.greenColor()
        setupViews()
    }

    var constraints: [NSLayoutConstraint] = []

    func setupViews() {

        let container1 = createButtonContainer(withButtonTitle: "Button 1")
        let container2 = createButtonContainer(withButtonTitle: "Button 2")
        let container3 = createButtonContainer(withButtonTitle: "Button 3")
        let container4 = createButtonContainer(withButtonTitle: "Button 4")

        let stackView = UIStackView(arrangedSubviews: [container1, container2, container3, container4])
        stackView.translatesAutoresizingMaskIntoConstraints = false
        stackView.axis = .Vertical
        stackView.distribution = .FillEqually
        view.addSubview(stackView)

        [stackView.topAnchor.constraintEqualToAnchor(view.topAnchor),
            stackView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor),
            stackView.leftAnchor.constraintEqualToAnchor(view.leftAnchor, constant: 20),
            stackView.rightAnchor.constraintEqualToAnchor(view.rightAnchor, constant: -20)].forEach { $0.active = true }
    }


    func createButtonContainer(withButtonTitle title: String) -> UIView {
        let button = UIButton(type: .Custom)
        button.translatesAutoresizingMaskIntoConstraints = false
        button.backgroundColor = UIColor.redColor()
        button.setTitleColor(UIColor.whiteColor(), forState: .Normal)
        button.setTitle(title, forState: .Normal)
        let buttonContainer = UIStackView(arrangedSubviews: [button])
        buttonContainer.distribution = .EqualCentering
        buttonContainer.alignment = .Center
        buttonContainer.translatesAutoresizingMaskIntoConstraints = false
        return buttonContainer
    }
}

Notez que c'est exactement la même approche que ci-dessus. Il ajoute quatre vues de conteneur qui sont remplies de manière égale et une vue est ajoutée à chaque vue de pile qui est alignée au centre. Mais, cette version de UIStackView réduit le code et semble agréable.

Sandeep
la source
1

Une autre approche pourrait consister à faire en sorte que les étiquettes supérieure et inférieure aient des contraintes par rapport à la vue supérieure et inférieure, respectivement, et que la vue du milieu ait des contraintes supérieure et inférieure par rapport à la première et à la troisième vue, respectivement.

Notez que vous avez plus de contrôle sur les contraintes qu'il n'y paraît en faisant glisser les vues les unes vers les autres jusqu'à ce que des lignes pointillées de guidage apparaissent - elles indiquent les contraintes entre les deux objets qui seront formées plutôt qu'entre l'objet et la vue d'ensemble.

Dans ce cas, vous souhaiterez alors modifier les contraintes pour qu'elles soient "supérieures ou égales à" la valeur souhaitée, au lieu de "égales à" pour leur permettre de se redimensionner. Je ne sais pas si cela fera exactement ce que vous voulez.

Colin
la source
1

Un moyen très simple de résoudre ce problème dans InterfaceBuilder:

Définissez l'étiquette centrée (label2) sur "Centre horizontal dans le conteneur" et "Centre vertical dans le conteneur"

Sélectionnez l'étiquette centrée et l'étiquette supérieure (étiquette1 + étiquette2) et ajoutez DEUX contraintes pour l'espacement vertical. Un avec un espacement supérieur ou égal à min. Un avec moins que ou égal à l'espacement maximum.

De même pour l'étiquette centrée et l'étiquette inférieure (étiquette2 + étiquette3).

De plus, vous pouvez également ajouter deux contraintes à label1 - Espace supérieur vers SuperView et deux contraintes à label2 - Espace inférieur vers SuperView.

Le résultat sera que les 4 espacements changeront leurs tailles également.

sust86
la source
2
J'ai trouvé que cela ne fonctionne que lorsque vous redimensionnez la vue d'ensemble afin que l'espacement prenne exactement les valeurs min ou max. Lorsque vous le redimensionnez à une valeur située entre les deux, les sous-vues ne sont pas réparties uniformément.
Dorian Roy
1

J'ai créé une fonction qui pourrait aider. Cet exemple d'utilisation:

 [self.view addConstraints: [NSLayoutConstraint fluidConstraintWithItems:NSDictionaryOfVariableBindings(button1, button2, button3)
                                                                asString:@[@"button1", @"button2", @"button3"]
                                                               alignAxis:@"V"
                                                          verticalMargin:100
                                                        horizontalMargin:50
                                                             innerMargin:25]];

provoquera cette distribution verticale (désolé de ne pas avoir la réputation d'intégrer des images). Et si vous modifiez l'axe et certaines valeurs de marge:

alignAxis:@"H"
verticalMargin:120
horizontalMargin:20
innerMargin:10

Vous obtiendrez cette distribution horizontale .

Je suis novice dans iOS mais voilà!

EvenDistribution.h

@interface NSLayoutConstraint (EvenDistribution)

/**
 * Returns constraints that will cause a set of subviews
 * to be evenly distributed along an axis.
 */
+ (NSArray *)  fluidConstraintWithItems:(NSDictionary *) views
                               asString:(NSArray *) stringViews
                              alignAxis:(NSString *) axis
                         verticalMargin:(NSUInteger) vMargin
                       horizontalMargin:(NSUInteger) hMargin
                            innerMargin:(NSUInteger) inner;
@end

EvenDistribution.m

#import "EvenDistribution.h"

@implementation NSLayoutConstraint (EvenDistribution)

+ (NSArray *) fluidConstraintWithItems:(NSDictionary *) dictViews
                              asString:(NSArray *) stringViews
                             alignAxis:(NSString *) axis
                        verticalMargin:(NSUInteger) vMargin
                      horizontalMargin:(NSUInteger) hMargin
                           innerMargin:(NSUInteger) iMargin

{
    NSMutableArray *constraints = [NSMutableArray arrayWithCapacity: dictViews.count];
    NSMutableString *globalFormat = [NSMutableString stringWithFormat:@"%@:|-%d-",
                                     axis,
                                     [axis isEqualToString:@"V"] ? vMargin : hMargin
                                     ];



        for (NSUInteger i = 0; i < dictViews.count; i++) {

            if (i == 0)
                [globalFormat appendString:[NSString stringWithFormat: @"[%@]-%d-", stringViews[i], iMargin]];
            else if(i == dictViews.count - 1)
                [globalFormat appendString:[NSString stringWithFormat: @"[%@(==%@)]-", stringViews[i], stringViews[i-1]]];
            else
               [globalFormat appendString:[NSString stringWithFormat: @"[%@(==%@)]-%d-", stringViews[i], stringViews[i-1], iMargin]];

            NSString *localFormat = [NSString stringWithFormat: @"%@:|-%d-[%@]-%d-|",
                                     [axis isEqualToString:@"V"] ? @"H" : @"V",
                                     [axis isEqualToString:@"V"] ? hMargin : vMargin,
                                     stringViews[i],
                                     [axis isEqualToString:@"V"] ? hMargin : vMargin];

            [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:localFormat
                                                                                     options:0
                                                                                     metrics:nil
                                                                                       views:dictViews]];


    }
    [globalFormat appendString:[NSString stringWithFormat:@"%d-|",
                                [axis isEqualToString:@"V"] ? vMargin : hMargin
                                ]];

    [constraints addObjectsFromArray:[NSLayoutConstraint constraintsWithVisualFormat:globalFormat
                                                                             options:0
                                                                             metrics:nil
                                                                               views:dictViews]];

    return constraints;

}

@end
pénombre
la source
1

Oui, vous pouvez le faire uniquement dans le générateur d'interface et sans écrire de code - la seule mise en garde est que vous redimensionnez l'étiquette au lieu de distribuer des espaces. Dans ce cas, alignez les X et Y de l'étiquette 2 sur la vue d'ensemble afin qu'elle soit fixée au centre. Ensuite, définissez l'espace vertical de l'étiquette 1 sur la vue d'ensemble et pour étiqueter 2 sur la norme, répétez pour l'étiquette 3. Après avoir défini l'étiquette 2, la façon la plus simple de définir les étiquettes 1 et 3 est de les redimensionner jusqu'à ce qu'elles s'enclenchent.

Voici l'affichage horizontal, notez que l'espace vertical entre les étiquettes 1 et 2 est réglé sur standard:affichage horizontal

Et voici la version portrait:entrez la description de l'image ici

Je me rends compte qu'ils ne sont pas absolument à 100% également espacés entre les lignes de base en raison de la différence entre l'espace standard entre les étiquettes et l'espace standard à la vue d'ensemble. Si cela vous dérange, définissez la taille sur 0 au lieu de standard

James
la source
1

J'ai défini une valeur de largeur juste pour le premier élément (> = une largeur) et une distance minimale entre chaque élément (> = une distance). Ensuite, j'utilise Ctrl pour faire glisser le deuxième, le troisième ... élément sur le premier pour enchaîner les dépendances entre les éléments.

entrez la description de l'image ici

Vladimír Slavík
la source
1

Tard dans la fête mais j'ai une solution de travail pour créer un menu horizontalement avec un espacement. Cela peut être facilement fait en utilisant ==dans NSLayoutConstraint

const float MENU_HEIGHT = 40;

- (UIView*) createMenuWithLabels: (NSArray *) labels
    // labels is NSArray of NSString
    UIView * backgroundView = [[UIView alloc]init];
    backgroundView.translatesAutoresizingMaskIntoConstraints = false;

    NSMutableDictionary * views = [[NSMutableDictionary alloc] init];
    NSMutableString * format = [[NSMutableString alloc] initWithString: @"H:|"];
    NSString * firstLabelKey;

    for(NSString * str in labels)
    {
        UILabel * label = [[UILabel alloc] init];
        label.translatesAutoresizingMaskIntoConstraints = false;
        label.text = str;
        label.textAlignment = NSTextAlignmentCenter;
        label.textColor = [UIColor whiteColor];
        [backgroundView addSubview: label];
        [label fixHeightToTopBounds: MENU_HEIGHT-2];
        [backgroundView addConstraints: [label fixHeightToTopBounds: MENU_HEIGHT]];
        NSString * key = [self camelCaseFromString: str];
        [views setObject: label forKey: key];
        if(firstLabelKey == nil)
        {
            [format appendString: [NSString stringWithFormat: @"[%@]", key]];
            firstLabelKey = key;
        }
        else
        {
            [format appendString: [NSString stringWithFormat: @"[%@(==%@)]", key, firstLabelKey]];
        }
    }

    [format appendString: @"|"];

    NSArray * constraints = [NSLayoutConstraint constraintsWithVisualFormat: (NSString *) format
                                                                               options: 0
                                                                               metrics: nil
                                                                                 views: (NSDictionary *) views];
    [backgroundView addConstraints: constraints];
    return backgroundView;
}
tsuz
la source
0

Android a une méthode de chaînage des vues dans son système de mise en page basé sur des contraintes que je voulais imiter. Les recherches m'ont amené ici mais aucune des réponses n'a tout à fait fonctionné. Je ne voulais pas utiliser StackViews car ils ont tendance à me causer plus de chagrin qu'ils n'en économisent d'avance. J'ai fini par créer une solution qui utilisait des UILayoutGuides placés entre les vues. Le contrôle de leur largeur permet différents types de distributions, des styles de chaîne dans le langage Android. La fonction accepte une ancre de début et de fin au lieu d'une vue parent. Cela permet à la chaîne d'être placée entre deux vues arbitraires plutôt que distribuée à l'intérieur de la vue parent. Il utilise UILayoutGuidece qui n'est disponible que dans iOS 9+ mais cela ne devrait plus être un problème.

public enum LayoutConstraintChainStyle {
    case spread //Evenly distribute between the anchors
    case spreadInside //Pin the first & last views to the sides and then evenly distribute
    case packed //The views have a set space but are centered between the anchors.
}

public extension NSLayoutConstraint {

    static func chainHorizontally(views: [UIView],
                                  leadingAnchor: NSLayoutXAxisAnchor,
                                  trailingAnchor: NSLayoutXAxisAnchor,
                                  spacing: CGFloat = 0.0,
                                  style: LayoutConstraintChainStyle = .spread) -> [NSLayoutConstraint] {
    var constraints = [NSLayoutConstraint]()
    guard views.count > 1 else { return constraints }
    guard let first = views.first, let last = views.last, let superview = first.superview else { return constraints }

    //Setup the chain of views
    var distributionGuides = [UILayoutGuide]()
    var previous = first
    let firstGuide = UILayoutGuide()
    superview.addLayoutGuide(firstGuide)
    distributionGuides.append(firstGuide)
    firstGuide.identifier = "ChainDistribution\(distributionGuides.count)"
    constraints.append(firstGuide.leadingAnchor.constraint(equalTo: leadingAnchor))
    constraints.append(first.leadingAnchor.constraint(equalTo: firstGuide.trailingAnchor, constant: spacing))
    views.dropFirst().forEach { view in
        let g = UILayoutGuide()
        superview.addLayoutGuide(g)
        distributionGuides.append(g)
        g.identifier = "ChainDistribution\(distributionGuides.count)"
        constraints.append(contentsOf: [
            g.leadingAnchor.constraint(equalTo: previous.trailingAnchor),
            view.leadingAnchor.constraint(equalTo: g.trailingAnchor)
        ])
        previous = view
    }
    let lastGuide = UILayoutGuide()
    superview.addLayoutGuide(lastGuide)
    constraints.append(contentsOf: [lastGuide.leadingAnchor.constraint(equalTo: last.trailingAnchor),
                                    lastGuide.trailingAnchor.constraint(equalTo: trailingAnchor)])
    distributionGuides.append(lastGuide)

    //Space the according to the style.
    switch style {
    case .packed:
        if let first = distributionGuides.first, let last = distributionGuides.last {
            constraints.append(first.widthAnchor.constraint(greaterThanOrEqualToConstant: spacing))
            constraints.append(last.widthAnchor.constraint(greaterThanOrEqualToConstant: spacing))
            constraints.append(last.widthAnchor.constraint(equalTo: first.widthAnchor))
            constraints.append(contentsOf:
                distributionGuides.dropFirst().dropLast()
                    .map { $0.widthAnchor.constraint(equalToConstant: spacing) }
                )
        }
    case .spread:
        if let first = distributionGuides.first {
            constraints.append(contentsOf:
                distributionGuides.dropFirst().map { $0.widthAnchor.constraint(equalTo: first.widthAnchor) })
        }
    case .spreadInside:
        if let first = distributionGuides.first, let last = distributionGuides.last {
            constraints.append(first.widthAnchor.constraint(equalToConstant: spacing))
            constraints.append(last.widthAnchor.constraint(equalToConstant: spacing))
            let innerGuides = distributionGuides.dropFirst().dropLast()
            if let key = innerGuides.first {
                constraints.append(contentsOf:
                    innerGuides.dropFirst().map { $0.widthAnchor.constraint(equalTo: key.widthAnchor) }
                )
            }
        }
    }

    return constraints
}
Sam Corder
la source
0

Je voulais aligner horizontalement 5 images, donc j'ai fini par suivre la réponse de Mete avec une petite différence.

La première image sera centrée horizontalement dans un conteneur égal à 0 et un multiplicateur de 1: 5:

première image

La deuxième image sera centrée horizontalement dans un conteneur égal à 0 et un multiplicateur de 3: 5: deuxième image

Et comme ça pour le reste des images. Par exemple, la cinquième (et dernière) image sera centrée horizontalement dans un conteneur égal à 0 et un multiplicateur de 9: 5: dernière image

Comme Mete l'a expliqué, l'ordre passe à 1, 3, 5, 7, 9, etc. Les positions suivent la même logique: la première position est 1, puis l'espace, puis la position suivante 3, et ainsi de suite.

Adriana Pineda
la source
-1

Pourquoi ne créez-vous pas simplement une table isScrollEnabled = false

Josh O'Connor
la source
Je ne comprends pas le downvote, ma solution est de sortir des sentiers battus. Pourquoi passer par le casse-tête de créer une liste avec mise en page automatique lorsque cacao vous donne une liste dans UITableView?
Josh O'Connor