La mise en page automatique me rend la vie difficile. En théorie, cela allait être vraiment utile lorsque je changeais de poste, 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 faire en sorte que les espaces entre les vues augmentent ou diminuent de manière uniforme chaque fois que la vue est redimensionnée?
Voici trois étiquettes (espacées manuellement verticalement et même verticalement):
Ce que je veux, c'est qu'ils redimensionnent leur espacement (pas la taille de la vue) de manière uniforme lorsque je fais pivoter. Par défaut, les vues du haut et du bas s’étirent vers le centre:
Donc, mon approche vous permet de faire cela dans le constructeur d'interface. Ce que vous faites est de créer des "vues d'entretoise" que vous avez définies pour faire correspondre les hauteurs de manière égale. Ajoutez ensuite des contraintes en haut et en bas aux étiquettes (voir la capture d'écran).
Plus précisément, j'ai une contrainte maximale sur "Vue d'entretoise 1" à superposer avec une contrainte de hauteur d'une priorité inférieure à 1000 et avec une hauteur égale à toutes les autres "vues d'espacement". 'Spacer View 4' a une contrainte d'espace inférieur à superview. Chaque étiquette a des contraintes supérieure et inférieure respectives par rapport à ses "vues d'espacement" les plus proches.
Remarque: assurez-vous que les étiquettes ne sont pas soumises à des contraintes supplémentaires d'espace en haut/en bas. juste ceux des 'vues de l'espace'. Cela sera satisfaisant puisque les contraintes du haut et du bas 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 afin que vous puissiez voir que cela fonctionnait.
Duh 2: Les "vues d'espacement" auraient pu être transparentes.
Duh 3: Cette approche pourrait être appliquée horizontalement.
LOOK, NO SPACERS!
Sur la base des suggestions de la section commentaires de ma réponse initiale, en particulier des suggestions utiles de @ Rivera, j'ai simplifié ma réponse initiale.
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) Ajoute une contrainte centre x de chaque bouton à la vue d'ensemble:
) Ajoute une contrainte de chaque bouton à la contrainte de présentation inférieure:
4) Ajustez la contrainte ajoutée au n ° 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 commencez par le haut, définissez le multiplicateur comme suit buttonCountPlus1: 1 , puis buttonCountPlus1: 2 et enfin buttonCountPlus1: 3 . (J'explique d'où vient cette formule dans l'ancienne réponse ci-dessous, si cela vous intéresse).
5) Voici une démo en cours d'exécution!
Remarque: Si les hauteurs de vos boutons sont plus grandes, vous devrez alors compenser cette perte avec la valeur constante, car la contrainte provient du bas du bouton.
Ancienne réponse
Malgré les documents d'Apple et l'excellent livre d'Erica Sadun ( Mise en page automatique démystifié ), il est possible d'espacer les vues de manière égale . sans entretoises. C'est très simple à faire dans IB et en code pour le 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 en faisant la démonstration dans IB, mais il est tout aussi facile de le faire en code.
Dans l'exemple en question, vous feriez
1) commencez par définir chaque étiquette pour avoir une contrainte de centre. C'est très simple à faire. Il suffit de contrôler le glisser de chaque étiquette vers le bas.
2) Maintenez la touche shift enfoncée, car vous pourriez aussi bien ajouter l’autre contrainte que nous allons utiliser, à savoir le "guide de mise en forme de l’espace inférieur à inférieur".
3) Sélectionnez "Guide de mise en page" de l'espace du bas au bas "et" Centrez horizontalement dans le conteneur ". Faites cela pour les 3 étiquettes.
Fondamentalement, si nous prenons l'étiquette dont nous voulons déterminer la coordonnée et que nous la divisons par le nombre total d'étiquettes plus 1, 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 les deux verticales et horizontales en même temps. C'est super puissant!
Voici nos multiplicateurs.
Label1 = 1/4 = 0,25,
Label2 = 2/4 = .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 do the math!)
4) Alors, sélectionnons Label1 et sélectionnons la contrainte du bas. Comme ça:
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) Mettez à zéro la constante et la valeur wC hAny. (Vous pouvez ajouter un décalage ici si vous en avez besoin).
8) C’est la partie critique: dans le champ multiplicateur, ajoutez notre premier multiplicateur 0.25.
9) Pendant que vous y êtes, réglez le "Premier élément" sur "CentreY", car nous souhaitons le centrer sur le centre y de l'étiquette. Voici à quoi tout cela devrait ressembler.
10) Répétez cette procédure pour chaque étiquette et insérez le multiplicateur approprié: 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 examiné de nombreuses solutions impliquant des tonnes 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 disposition inférieur n'apparaît que dans les storyboards, pas dans les xibs. Utilisez 'Bottom Space to Container' dans xibs. Bonne prise!
Solution très rapide d'Interface Builder:
Pour que le nombre de vues soit uniformément espacé dans une vue d'ensemble, attribuez simplement à chacune une contrainte "Aligner le centre X à la vue d'ensemble" pour une présentation horizontale ou "Aligner la vue de dessus au centre Y" pour une disposition verticale et définissez le multiplicateur sur N:p
( NOTE: Certains ont eu plus de chance avec p:N
- voir ci-dessous )
où
N = total number of views
, et
p = position of the view including spaces
La première position est 1, puis un espace, ce qui rend 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:
EDIT Remarque: le choix de N:p
ou p:N
dépend de l'ordre de relation de votre contrainte d'alignement. Si "Premier élément" est Superview.Center, vous pouvez utiliser p:N
, tandis que si Superview.Center est "Deuxième élément", vous pouvez utiliser N:p
. En cas de doute, essayez les deux ... :-)
Depuis iOS 9, Apple a rendu cela très facile avec la (tant attendue) UIStackView
. Sélectionnez simplement les vues que vous souhaitez contenir dans Interface Builder et choisissez Editeur -> Intégrer dans -> Vue pile. Définissez les contraintes de largeur/hauteur/marge appropriées pour la vue de 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 plus récente, vous devrez choisir l'une des autres options.
Je suis sur une montagne russe en amour avec l'autolayout et le déteste. La clé pour aimer semble être d'accepter ce qui suit:
Cela dit, ce que vous tentez de faire n'est pas simple et serait difficile à réaliser dans le constructeur d'interface. C'est assez simple à faire dans le 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 le disais, ce code est beaucoup simplifié avec quelques méthodes de catégories dans UIView
, mais par souci de clarté, je l'ai fait depuis longtemps.
La catégorie est ici pour les personnes intéressées, et il existe une méthode pour espacer régulièrement un tableau de vues le long d'un axe particulier.
La plupart de ces solutions dépendent d'un nombre impair d'éléments 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'éléments que vous souhaitez toujours distribuer de manière uniforme? Voici une solution plus générale. Cette catégorie répartira uniformément un nombre quelconque d'éléments sur 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
Découvrez la bibliothèque open source PureLayout . Il propose quelques méthodes d'API pour la distribution de vues, notamment des variantes dans lesquelles l'espacement entre chaque vue est fixe (la taille de la vue varie en fonction des besoins) et la taille de chaque vue est fixe (l'espacement entre les vues varie en fonction des besoins). Notez que tout ceci est accompli sans l’utilisation de "vues d’espacement".
// 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;
// ...
Puisque tout est open source, si vous êtes intéressé à savoir comment cela est réalisé sans vues d'entretoise, jetez un coup d'œil à la mise en œuvre. (Cela dépend d’utiliser à la fois les constant
et multiplier
pour les contraintes.)
Le moyen correct et le plus simple consiste à utiliser Stack Views.
J'ai un problème similaire et découvert ce post. Cependant, aucune des réponses actuellement fournies ne résout le problème de la manière souhaitée. Ils ne font pas l’espacement de manière égale, mais distribuent plutôt le centre des étiquettes de manière égale. Il est important de comprendre que ce n'est pas la même chose. J'ai construit un petit diagramme pour illustrer cela.
Il y a 3 vues, toutes hautes de 20 pt. L’utilisation de l’une des méthodes suggérées distribue également les centres des vues et vous donne la présentation illustrée. Notez que le centre y des vues est espacé de manière égale. Cependant, l’espacement entre la vue de dessus et la vue de dessus est de 15 points, alors que l’espacement entre les vues secondaires n’est que de 5 points. Pour que les vues soient à égale distance, elles doivent toutes les 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 mis au point une 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 égales pour les vues d'espacement.
J'ai pu résoudre cela entièrement dans IB:
Ainsi, si vous avez trois étiquettes, définissez les multiplicateurs sur chacune de ces contraintes sur 0.1666667, 0.5, 0.833333.
J'ai trouvé une méthode parfaite et simple. La mise en page 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. Ajoutez simplement des vues invisibles entre vos champs et indiquez à la disposition automatique de conserver la même taille. Ça fonctionne parfaitement!
Une chose à noter cependant; lorsque je réduisais la taille dans le concepteur d'interface, cela devenait parfois confus et laissait une étiquette à son emplacement, et il y avait un conflit si la taille était modifiée de manière inhabituelle. Sinon cela fonctionnait parfaitement.
edit: J'ai trouvé que le conflit est devenu un problème. À cause de cela, j'ai pris l'une des contraintes d'espacement, je l'ai supprimée et remplacée par deux contraintes, une supérieure ou égale et une inférieure ou égale. Les deux avaient la même taille et avaient une priorité beaucoup plus basse que les autres contraintes. Le résultat n'était plus aucun conflit.
Sur la base de la réponse de Ben Dolman, cela répartit les vues plus uniformément (avec un 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;
}
Swift 3 version
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)
check https://developer.Apple.com/library/ios/documentation/UserExperience/Conceptual/AutolayoutPG/AutoLayoutbyExample/AutoLayoutbyExample.html Avoir une description agréable de Nice concernant la résolution de votre problème.
Avec les étiquettes, cela fonctionne bien au moins:
@"H:|-15-[first(==second)]-[second(==third)]-[third(==first)]-15-|
Si le premier a la même largeur que le second, et le second le troisième et le troisième le premier, ils auront tous la même largeur ... Vous pouvez le faire à la fois horizontalement (H) et verticalement (V).
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 parfaitement fonctionné. Je suis sur Xcode 6.3 FYI.
Ce que j'ai fini par faire était:
1) Tout d’abord, mes boutons sont positionnés sur un écran de 320 pixels de largeur, répartis de la manière que je souhaitais pour un appareil de 320 pixels.
2) Ensuite, j'ai ajouté une contrainte d'espace principale à superview sur tous mes boutons.
3) Ensuite, j'ai modifié les propriétés de l'espace de début pour que la constante soit égale à 0 et le multiplicateur correspond au décalage x divisé par la largeur de l'écran (par exemple, mon premier bouton avait 8px à partir du bord gauche et j'ai donc réglé mon multiplicateur sur 8/320).
4) Ensuite, l’important est de changer le deuxième élément de la relation de contrainte en superview.Trailing au lieu de superview.leading. Ceci est essentiel car superview.Leading vaut 0 et le dernier dans mon cas est 320, 8/320 correspond donc à 8 px sur un périphérique 320px. Lorsque la largeur de la vue supérieure passe à 640 ou peu importe, les vues se déplacent toutes dans un rapport relatif à la largeur. de la taille de l'écran 320px. Le calcul est beaucoup plus simple à comprendre.
Voici encore une autre réponse. Je répondais à une question similaire et j'ai vu le lien référencé à cette question. Je n'ai vu aucune réponse semblable à la mienne. Alors j'ai 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
}
}
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 alignée au centre. Cependant, cette version de UIStackView réduit le code et a l’air agréable.
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 souhaite partager ma solution. Pour les générations à venir ...
Je règle 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 format de langage visuel 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 maths. 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.
Beaucoup de réponses ne sont pas correctes, mais obtiennent beaucoup de comptes. Ici, je viens d'écrire une solution par programme, les trois vues sont horizontalement alignées, sans utiliser de vues d'espacement, mais ça 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]];
Voici une solution qui permet de centrer verticalement un nombre quelconque de vues secondaires, même si elles ont des tailles uniques. Ce que vous voulez faire est de créer un conteneur de niveau intermédiaire, centrez-le dans la vue d'ensemble, puis placez toutes les vues secondaires dans le conteneur et organisez-les les uns par rapport aux autres. Mais surtout, vous devez également les contraindre vers le haut et bas du conteneur, afin que le conteneur puisse être correctement dimensionné et centré dans la vue supérieure. En définissant la hauteur correcte à partir de ses sous-vues, le conteneur peut être centré verticalement.
Dans cet exemple, self
est la vue 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;
}
}
J'ai défini une valeur de largeur uniquement 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, troisième ... élément sur le premier afin d'enchaîner les dépendances entre les éléments.
Un moyen très simple de résoudre ceci 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 (label1 + label2) et ajoutez les contraintes DEUX pour l'espacement vertical. n avec Supérieur ou égal à l'espacement min. n avec Inférieur ou égal à l'espacement maximal.
Idem pour l'étiquette centrée et l'étiquette inférieure (label2 + label3).
De plus, vous pouvez également ajouter deux contraintes à label1 - Top Space To SuperView et deux contraintes à label2 - Bottom To SuperView.
Le résultat sera que les 4 espacements changeront de taille égale.
Une autre approche pourrait consister à faire en sorte que les étiquettes supérieure et inférieure aient des contraintes relatives aux vues supérieure et inférieure, respectivement, et que la vue centrale présente des contraintes supérieure et inférieure relatives aux première et troisième vues, 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 à côté des autres jusqu'à ce que des lignes pointillées apparaissent - elles indiquent les contraintes entre les deux objets qui seront formés plutôt qu'entre l'objet et la vue d'ensemble.
Dans ce cas, vous voudriez alors que les contraintes soient "Supérieur ou égal à" à la valeur souhaitée, au lieu de "égal à" pour leur permettre de redimensionner. Vous ne savez pas si cela fera exactement ce que vous voulez.
Oui, vous pouvez le faire uniquement dans le générateur d’interface et sans écrire de code - le seul inconvénient 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 supérieure afin qu'elle soit fixée au centre. Ensuite, réglez l’espace vertical de l’étiquette 1 sur l’aperçu supérieur et sur l’étiquette 2 sur le standard, répétez cette opération pour l’étiquette 3. Après avoir défini l’étiquette 2, le moyen le plus simple de définir les étiquettes 1 et 3 consiste à les redimensionner jusqu’à ce qu’ils se superposent.
Voici l’affichage horizontal. Notez que l’espace vertical entre les étiquettes 1 et 2 est défini sur Standard:
Et voici la version portrait:
Je me rends compte qu'elles ne sont pas absolument égales à 100% entre les lignes de base en raison de la différence entre l'espace standard entre les étiquettes et l'espace standard dans la vue d'ensemble. Si cela vous dérange, définissez la taille sur 0 au lieu de standard
Je suis en retard pour 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;
}
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]];
va causer que distribution verticale (désolé, ne pas avoir la 10 réputation nécessaire pour incorporer des images). Et si vous modifiez l’axe et certaines valeurs de marge:
alignAxis:@"H"
verticalMargin:120
horizontalMargin:20
innerMargin:10
Vous obtiendrez cela distribution horizontale .
Je suis novice sur 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
Android propose une méthode de chaînage des vues dans son système de mise en forme basé sur des contraintes que je voulais imiter. Les recherches m'ont amené ici mais aucune des réponses n'a fonctionné. Je ne voulais pas utiliser StackViews car ils ont tendance à me causer plus de chagrin qu'ils n'en épargnent d'avance. J'ai fini par créer une solution utilisant UILayoutGuides placé entre les vues. Contrôler leur largeur permet différents types de distributions, styles de chaîne dans le jargon 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 d'être répartie à l'intérieur de la vue parent. Il utilise UILayoutGuide
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
}