Platzieren Sie mehrere Ansichten gleichmäßig in einer Containeransicht


279

Auto Layout macht mir das Leben schwer. Theoretisch würde es sehr nützlich sein, wenn ich wechselte, aber ich scheine die ganze Zeit dagegen anzukämpfen.

Ich habe ein Demo-Projekt erstellt, um Hilfe zu finden. Weiß jemand, wie die Abstände zwischen Ansichten gleichmäßig vergrößert oder verkleinert werden können, wenn die Größe der Ansicht geändert wird?

Hier sind drei Beschriftungen (manuell vertikal gleichmäßig verteilt):

image1

Ich möchte, dass sie ihren Abstand (nicht die Ansichtsgröße) beim Drehen gleichmäßig ändern. Standardmäßig quetschen sich die Draufsicht und die Unteransicht zur Mitte hin:

image2


Vielleicht ist es hilfreich, die Einschränkung von einem Etikett zum anderen
festzulegen

programmgesteuert zum Beispiel mit dieser Methode von NSLayoutConstraint: + (id) beschränkungWithItem: (id) view1-Attribut: (NSLayoutAttribute) attr1 relatedBy: (NSLayoutRelation) Beziehung zuItem: (id) view2-Attribut: (NSLayoutAttribute) attr2-Multiplikator: (CGFloat) Konstante: (CGFloat) c
BergP

Ich habe einen generischen UITabBar-Ersatz als Open-Source-Lösung bereitgestellt, der diesen Ansatz mithilfe des automatischen Layouts verallgemeinert. Sie können die Tests überprüfen, um zu sehen, wie ich meine Einschränkungen für das automatische Layout generiere. GGTabBar
Goles

Antworten:


211

Mein Ansatz ermöglicht es Ihnen, dies im Interface Builder zu tun. Sie erstellen "Abstandshalteransichten", die Sie so eingestellt haben, dass sie gleich hoch sind. Fügen Sie dann den Beschriftungen obere und untere Einschränkungen hinzu (siehe Screenshot).

Geben Sie hier die Bildbeschreibung ein

Insbesondere habe ich eine oberste Einschränkung für 'Spacer View 1', um eine Ansicht mit einer Höhenbeschränkung von niedrigerer Priorität als 1000 und einer Höhe zu erhalten, die allen anderen 'Spacer Views' entspricht. 'Spacer View 4' hat eine untergeordnete Platzbeschränkung für die Übersicht. Jedes Etikett hat eine entsprechende obere und untere Einschränkung für die nächstgelegenen "Abstandshalteransichten".

Hinweis: Stellen Sie sicher, dass Ihre Beschriftungen keine zusätzlichen Platzbeschränkungen für den oberen / unteren Bereich haben, die angezeigt werden sollen. nur die zu den "Weltraumansichten". Dies ist zufriedenstellend, da die oberen und unteren Einschränkungen für 'Space View 1' bzw. 'Spacer View 4' gelten.

Duh 1: Ich habe meine Ansicht dupliziert und sie lediglich in den Querformatmodus versetzt, damit Sie sehen können, dass sie funktioniert.

Duh 2: Die 'Spacer Views' hätten transparent sein können.

Duh 3: Dieser Ansatz könnte horizontal angewendet werden.


10
Das funktioniert. In der Tat können Abstandshalteransichten ausgeblendet werden, und sie erzeugen immer noch das richtige Layout.
Paulmelnikow

Das war super hilfreich. Wie war der Kommentar über das Markieren der Spacer-Ansichten als versteckt. Dann können Sie sie immer noch in Ihrem Storyboard / xib sehen, aber sie werden nicht in Ihrer App angezeigt. Sehr schön.
Shulmey

Nach mehreren Stunden wiederholter Versuche funktionierte es fast . Leider löscht Xcode immer wieder meine Einschränkungen für den oberen Bereich bis zur Schaltfläche und den unteren Bereich, unterbricht die Kette zwischen Schaltflächen und Abstandshaltern und ersetzt sie durch beliebige Einschränkungen für den oberen Bereich bis zur Übersicht, die unbrauchbar sind.
Desty

Der gleiche Ansatz funktioniert für mich im horizontalen Layout - ich habe Ansichten mit fester Breite, die durch Abstandsansichten mit gleicher Breite getrennt sind. Es ist großartig, dass ich es nicht mit Code mischen muss. Vielen Dank!
Kamil Nomtek.com

2
Es müssen keine Abstandshalter verwendet werden. Siehe meine Antwort unten.
smileBot

316

LOOK, KEINE SPACERS!

Aufgrund der Vorschläge im Kommentarbereich meiner ursprünglichen Antwort, insbesondere der hilfreichen Vorschläge von @ Rivera, habe ich meine ursprüngliche Antwort vereinfacht.

Ich verwende Gifs, um zu veranschaulichen, wie einfach dies ist. Ich hoffe, Sie finden die Gifs hilfreich. Nur für den Fall, dass Sie ein Problem mit Gifs haben, habe ich die alte Antwort unten mit einfachen Screenshots versehen.

Anleitung:

1) Fügen Sie Ihre Schaltflächen oder Beschriftungen hinzu. Ich benutze 3 Tasten.

2) Fügen Sie der Übersicht eine mittlere x-Einschränkung von jeder Schaltfläche hinzu:

Geben Sie hier die Bildbeschreibung ein

3) Fügen Sie der unteren Layouteinschränkung von jeder Schaltfläche eine Einschränkung hinzu:

Geben Sie hier die Bildbeschreibung ein

4) Passen Sie die in Nr. 3 oben hinzugefügte Einschränkung wie folgt an:

a) Wählen Sie die Einschränkung aus, b) entfernen Sie die Konstante (auf 0 gesetzt), c) ändern Sie den Multiplikator wie folgt: Nehmen Sie die Anzahl der Tasten + 1 und setzen Sie den Multiplikator von oben beginnend auf buttonCountPlus1: 1 und dann auf buttonCountPlus1 : 2 und schließlich buttonCountPlus1: 3 . (Ich erkläre, woher ich diese Formel habe, in der alten Antwort unten, wenn Sie interessiert sind).

Geben Sie hier die Bildbeschreibung ein

5) Hier läuft eine Demo!

Geben Sie hier die Bildbeschreibung ein

Hinweis: Wenn Ihre Schaltflächen größere Höhen haben, müssen Sie dies im konstanten Wert ausgleichen, da sich die Einschränkung am unteren Rand der Schaltfläche befindet.


Alte Antwort


Trotz der Aussagen von Apples Dokumenten und Erica Saduns ausgezeichnetem Buch ( Auto Layout Demystified ) ist es möglich, Ansichten ohne Abstandshalter gleichmäßig zu platzieren . Dies ist in IB und im Code für eine beliebige Anzahl von Elementen, die Sie gleichmäßig platzieren möchten, sehr einfach. Alles, was Sie brauchen, ist eine mathematische Formel namens "Abschnittsformel". Es ist einfacher zu tun als zu erklären. Ich werde mein Bestes geben, indem ich es in IB demonstriere, aber es ist genauso einfach, es in Code zu tun.

In dem fraglichen Beispiel würden Sie

1) Beginnen Sie, indem Sie für jedes Etikett eine Mittenbeschränkung festlegen. Dies ist sehr einfach zu tun. Steuern Sie einfach das Ziehen von jedem Etikett nach unten.

2) Halten Sie die Umschalttaste gedrückt, da Sie genauso gut die andere Einschränkung hinzufügen können, die wir verwenden werden, nämlich die "Layoutanleitung von unten nach unten".

3) Wählen Sie die "Layoutführung von unten nach unten" und "Horizontal im Container zentrieren". Tun Sie dies für alle 3 Etiketten.

Halten Sie die Umschalttaste gedrückt, um diese beiden Einschränkungen für jedes Etikett hinzuzufügen

Wenn wir das Etikett nehmen, dessen Koordinate wir bestimmen möchten, und es durch die Gesamtzahl der Etiketten plus 1 dividieren, können wir IB eine Zahl hinzufügen, um den dynamischen Ort zu erhalten. Ich vereinfache die Formel, aber Sie können sie verwenden, um den horizontalen Abstand oder sowohl den vertikalen als auch den horizontalen Abstand gleichzeitig festzulegen. Es ist super mächtig!

Hier sind unsere Multiplikatoren.

Label1 = 1/4 = 0,25,

Label2 = 2/4 = .5,

Label3 = 3/4 = 0,75

(Bearbeiten: @Rivera kommentierte, dass Sie die Verhältnisse einfach direkt im Multiplikatorfeld verwenden können und xCode mit rechnen!)

4) Wählen wir also Label1 und die unterste Einschränkung. So was: Geben Sie hier die Bildbeschreibung ein

5) Wählen Sie im Attributinspektor das "Zweite Element" aus.

6) Wählen Sie in der Dropdown-Liste "Erstes und zweites Element umkehren".

7) Nullen Sie die Konstante und den wC hAny-Wert auf Null. (Sie können hier einen Offset hinzufügen, wenn Sie ihn benötigen).

8) Dies ist der kritische Teil: Addieren Sie im Multiplikatorfeld unseren ersten Multiplikator 0,25.

9) Wenn Sie gerade dabei sind, setzen Sie das obere "Erste Element" auf "CenterY", da wir es auf die y-Mitte des Etiketts zentrieren möchten. So sollte das alles aussehen.

Geben Sie hier die Bildbeschreibung ein

10) Wiederholen Sie diesen Vorgang für jedes Etikett und stecken Sie den entsprechenden Multiplikator ein: 0,5 für Etikett2 und 0,75 für Etikett3. Hier ist das Endprodukt in allen Ausrichtungen mit allen kompakten Geräten! Super einfach. Ich habe mir viele Lösungen angesehen, die Unmengen von Code und Abstandshalter beinhalten. Dies ist bei weitem die beste Lösung, die ich zu diesem Thema gesehen habe.

Update: @kraftydevil fügt hinzu, dass die Anleitung für das untere Layout nur in Storyboards und nicht in xibs angezeigt wird. Verwenden Sie in xibs 'Bottom Space to Container'. Guter Fang!

Geben Sie hier die Bildbeschreibung ein


2
Ich kämpfe immer noch damit. Ich denke, Sie müssen Xcode 6 verwenden, da es in Xcode 5-Einschränkungen keine Option 'wC hAny' gibt. Wenn ich Ihren Anweisungen folge, bleiben alle Unteransichten in der oberen Mitte hängen.
Kraftydevil

4
@kraftydevil AFAIK Die untere Layoutanleitung wird nur in Storyboards angezeigt, nicht in xibs. Verwenden Sie in xibs 'Bottom Space to Container'.
Timur Kuchkarov

6
Oh - auch erwähnenswert, dass IB in Xcode 6 Verhältnismultiplikatoren unterstützt, so dass Sie Dinge wie "1: 4", "2: 5", "3: 4" usw.
eingeben können

3
Versuchen Sie herauszufinden, wie dies horizontal zu tun ist - wie das erste TextView 1/3 des Weges von links und ein zweites TextView 2/3 (der Rest des Weges) ... ... (BEARBEITEN) erstes 1/3 (links eins) Trailing-Space zum rechten Rand, umgekehrt, null, wie zuvor zuweisen ...
kfmfe04

3
Für horizontale Abstände legen Sie einfach für jede Unteransicht nachfolgende Einschränkungen für die Übersicht fest und legen dann den Verhältnismultiplikator fest, z. 5: 1, 5: 2, 5: 3 usw. von links nach rechts bewegen.
user3344977

98

Sehr schnelle Interface Builder-Lösung:

Wenn eine beliebige Anzahl von Ansichten innerhalb einer Übersicht gleichmäßig verteilt sein soll, geben Sie für das horizontale Layout einfach die Einschränkung "Mitte X an Übersicht anzeigen" oder für das vertikale Layout die Option "Mitte Y-Ansicht ausrichten" an und stellen Sie den Multiplikator auf N:p( HINWEIS: einige hatte besseres Glück mit p:N- siehe unten )

wo

N = total number of views, und

p = position of the view including spaces

Die erste Position ist 1, dann ein Leerzeichen und die nächste Position 3, sodass p zu einer Reihe wird [1,3,5,7,9, ...]. Funktioniert für eine beliebige Anzahl von Ansichten.

Wenn Sie also 3 Ansichten zum Platzieren haben, sieht es so aus:

Illustration, wie Ansichten in IB gleichmäßig verteilt werden

BEARBEITEN Hinweis: Die Auswahl N:poder p:Nhängt von der Beziehungsreihenfolge Ihrer Ausrichtungsbeschränkung ab. Wenn "Erstes Element" Superview.Center ist, können Sie verwenden p:N, während Sie verwenden können , wenn Superview.Center "Zweites Element" ist N:p. Im Zweifelsfall einfach beides ausprobieren ... :-)


Diese Lösung spiegelt nicht das Design wider, wenn Sie zu einer anderen Sprache wechseln
am

@wod Was meinst du? Was in eine andere Sprache wechseln? Und was bricht genau?
Mete

4
Was ist, wenn Elemente nicht gleich groß sind? Dieses Muster funktioniert nicht richtig, oder?
ucangetit

2
Ich verstehe nicht, wie diese Lösung richtig ist? Der Abstand zwischen dem Rand des Bildschirms und den äußeren Elementen unterscheidet sich von dem Abstand zwischen den äußeren Elementen und dem Mittelelement.
Myles

6
Dies war für mich nicht "so wie es ist", ich musste die Multiplikatoren umkehren (ej. Das 3: 1 vom ersten Element wird in 1: 3 umgewandelt). Wirf das einfach raus, wenn es jemandem hilft.
Ces

70

Ab iOS 9 hat Apple dies mit dem (lang erwarteten) sehr einfach gemacht UIStackView. Wählen Sie einfach die Ansichten aus, die Sie im Interface Builder enthalten möchten, und wählen Sie Editor -> Einbetten -> Stapelansicht. Legen Sie die entsprechenden Einschränkungen für Breite, Höhe und Rand für die Stapelansicht fest und stellen Sie sicher, dass die Verteilungseigenschaft auf "Gleicher Abstand" festgelegt ist:

Wenn Sie iOS 8 oder niedriger unterstützen möchten, müssen Sie natürlich eine der anderen Optionen auswählen.


Ja, es ist traurig, dass dies keine Retro-Kompatibilität hat. Bis Ihre App Unterstützung für IOS8 @Mete benötigt, ist die Antwort vorerst die beste.
ZiggyST

51

Ich war auf einer Achterbahnfahrt, um Autolayout zu lieben und es zu hassen. Der Schlüssel zum Lieben scheint darin zu liegen, Folgendes zu akzeptieren:

  1. Die Bearbeitung des Interface Builders und die "hilfreiche" automatische Erstellung von Einschränkungen sind für alle außer dem trivialsten Fall nahezu nutzlos
  2. Das Erstellen von Kategorien zur Vereinfachung gängiger Vorgänge ist ein Lebensretter, da sich der Code so wiederholt und ausführlich ist.

Das, was Sie versuchen, ist jedoch nicht einfach und im Interface Builder schwer zu erreichen. Es ist ziemlich einfach, in Code zu tun. Dieser Code viewDidLoaderstellt und positioniert drei Beschriftungen, wie Sie sie anfordern:

// 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]];

Wie gesagt, dieser Code ist mit ein paar Kategoriemethoden stark vereinfacht UIView, aber der Klarheit halber habe ich es hier auf dem langen Weg gemacht.

Die Kategorie ist hier für die interessiert, und sie hat ein Verfahren zum gleichmäßigen Verteilen eine Reihe von Ansichten entlang einer bestimmten Achsabstand.


Sie, mein Herr, sind ein Lebensretter. Ich habe # 1 bis jetzt nicht verstanden. Der Interface Builder macht mich verrückt, aber ich dachte, er könnte alle die gleichen Dinge tun. Ich bevorzuge es sowieso, Code zu verwenden, daher ist dies perfekt und ich werde gleich auf die Kategoriemethoden eingehen.
nothappybob

Dies ist zwar definitiv ziemlich nahe beieinander, löst jedoch nicht genau das gestellte Problem (wobei die Ansichtsgröße bei gleichem Abstand zwischen den Unteransichten gleich bleibt). Diese Lösung ist die verfeinerte allgemeine Fallantwort.
Smileyborg

21

Die meisten dieser Lösungen hängen davon ab, dass eine ungerade Anzahl von Elementen vorhanden ist, sodass Sie das mittlere Element nehmen und zentrieren können. Was ist, wenn Sie eine gerade Anzahl von Artikeln haben, die Sie dennoch gleichmäßig verteilen möchten? Hier ist eine allgemeinere Lösung. Diese Kategorie verteilt eine beliebige Anzahl von Elementen gleichmäßig entlang der vertikalen oder horizontalen Achse.

Beispiel für die vertikale Verteilung von 4 Beschriftungen in ihrer Übersicht:

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

NSLayoutConstraint + EvenDistribution.h

@interface NSLayoutConstraint (EvenDistribution)

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

@end

NSLayoutConstraint + EvenDistribution.m

@implementation NSLayoutConstraint (EvenDistribution)

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

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

    return constraints;
}

@end

1
Es funktionierte, indem alle Einschränkungen entfernt wurden und unmittelbar danach die Width- und Height-Einschränkungen zu diesen Objekten hinzugefügt und dann die Methode ConstraintsForEvenDistributionOfItems: relativeToCenterOfItem: vertikal
aufgerufen wurden

@carlos_ms Ich würde gerne Ihren Code sehen, denn wenn ich es mit einer geraden Anzahl von Elementen versuche, funktioniert dieser Code perfekt, wenn Sie die Geräteorientierung wechseln. Sind Sie sicher, dass Sie nicht versehentlich andere Einschränkungen (z. B. Einschränkungen bei der automatischen Größenänderung) haben, die einen Konflikt verursachen?
Ben Dolman

Hier ist ein Beispiel für das Erstellen von 4 Etiketten und deren gleichmäßigen Abstand entlang der vertikalen Achse: gist.github.com/bdolman/5379465
Ben Dolman

20

Der richtige und einfachste Weg ist die Verwendung von Stapelansichten.

  1. Fügen Sie Ihre Etiketten / Blick auf die Stapelansicht :

Geben Sie hier die Bildbeschreibung ein

  1. Wählen Sie die Stapelansicht aus und stellen Sie die Verteilung auf Gleicher Abstand ein :

Geben Sie hier die Bildbeschreibung ein

  1. Fügen Sie der Stapelansicht Abstand zu den Einschränkungen des nächsten Nachbarn hinzu und aktualisieren Sie die Frames:

Geben Sie hier die Bildbeschreibung ein

  1. In Höhe Einschränkungen auf alle Etiketten (optional). Wird nur für Ansichten benötigt, die keine intrinsische Größe haben. Beschriftungen benötigen hier beispielsweise keine Höhenbeschränkungen und müssen beispielsweise nur numberOfLines = 3 oder 0 setzen.

Geben Sie hier die Bildbeschreibung ein

  1. Vorschau genießen:

Geben Sie hier die Bildbeschreibung ein


2
Dies ist nur> = iOS 9.0, überprüfen Sie also Ihr Bereitstellungsziel, bevor Sie mit der Implementierung beginnen :) Schön zu sehen, dass dies hinzugefügt wurde!
DivideByZer0

1
2018 jetzt
Stapelansicht

17

Schauen Sie sich die Open Source-Bibliothek an PureLayout an . Es bietet einige API-Methoden zum Verteilen von Ansichten, einschließlich Varianten, bei denen der Abstand zwischen den einzelnen Ansichten festgelegt ist (die Ansichtsgröße variiert je nach Bedarf) und bei denen die Größe jeder Ansicht festgelegt ist (der Abstand zwischen den Ansichten variiert je nach Bedarf). Beachten Sie, dass all dies ohne die Verwendung von "Spacer-Ansichten" erreicht wird.

Von 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;

// ...

Da alles Open Source ist, werfen Sie einen Blick auf die Implementierung, wenn Sie sehen möchten, wie dies ohne Spacer-Ansichten erreicht wird. (Es hängt davon ab, ob sowohl die constant als auch multiplier die Einschränkungen genutzt werden.)


Gute Arbeit! Obwohl Sie das Repo wahrscheinlich umbenennen sollten, damit Sie es über Cocoapods verfügbar machen können.
Jrturton

9

Ich habe ein ähnliches Problem und habe diesen Beitrag entdeckt. Keine der derzeit bereitgestellten Antworten löst das Problem jedoch so, wie Sie es möchten. Sie machen den Abstand nicht gleichmäßig, sondern verteilen die Mitte der Etiketten gleichmäßig. Es ist wichtig zu verstehen, dass dies nicht dasselbe ist. Ich habe ein kleines Diagramm erstellt, um dies zu veranschaulichen.

Abstandsabbildung anzeigen

Es gibt 3 Ansichten, alle 20pt groß. Wenn Sie eine der vorgeschlagenen Methoden verwenden, werden die Zentren der Ansichten gleichmäßig verteilt und Sie erhalten das abgebildete Layout. Beachten Sie, dass die y-Mitte der Ansichten gleichmäßig verteilt ist. Der Abstand zwischen Übersicht und Draufsicht beträgt jedoch 15pt, während der Abstand zwischen den Unteransichten nur 5pt beträgt. Damit die Ansichten gleichmäßig verteilt sind, sollten beide 10pt sein, dh alle blauen Pfeile sollten 10pt sein.

Trotzdem habe ich noch keine gute generische Lösung gefunden. Derzeit ist es meine beste Idee, "Abstandsansichten" zwischen die Unteransichten einzufügen und die Höhen der Abstandsansichten gleich einzustellen.


1
Ich habe die Lösung von versteckten Abstandshalteransichten verwendet, die gut funktioniert.
Paulmelnikow

8

Ich konnte dies vollständig in IB lösen:

  1. Nehmen Sie Einschränkungen vor, um die Mitte Y jeder Ihrer Unteransichten am unteren Rand der Übersicht auszurichten.
  2. Setzen Sie den Multiplikator jeder dieser Einschränkungen auf 1 / 2n, 3 / 2n, 5 / 2n,…, n-1 / 2n, wobei n die Anzahl der von Ihnen verteilten Unteransichten ist.

Wenn Sie also drei Beschriftungen haben, setzen Sie die Multiplikatoren für jede dieser Einschränkungen auf 0,1666667, 0,5, 0,833333.


1
Ich musste tatsächlich 1 / n 3 / n 5 / n 7 / n bis (2n-1) / n machen
Hamzah Malik

5

Ich habe eine perfekte und einfache Methode gefunden. Mit dem automatischen Layout können Sie die Größe der Leerzeichen nicht gleichmäßig ändern, aber Sie können die Größe der Ansichten gleichmäßig ändern. Fügen Sie einfach einige unsichtbare Ansichten zwischen Ihre Felder ein und teilen Sie dem automatischen Layout mit, dass sie dieselbe Größe haben sollen. Es funktioniert perfekt!

Anfängliches XIB

Gestrecktes XIB

Eines ist jedoch zu beachten; Wenn ich die Größe im Interface-Designer reduzierte, wurde es manchmal verwirrt und hinterließ ein Etikett, wo es war, und es gab einen Konflikt, wenn die Größe um einen ungeraden Betrag geändert wurde. Ansonsten hat es perfekt funktioniert.

edit: Ich habe festgestellt, dass der Konflikt zu einem Problem wurde. Aus diesem Grund habe ich eine der Abstandsbeschränkungen genommen, sie gelöscht und durch zwei Einschränkungen ersetzt, eine größer als oder gleich und eine kleiner als oder gleich. Beide waren gleich groß und hatten eine viel niedrigere Priorität als die anderen Einschränkungen. Das Ergebnis war kein weiterer Konflikt.


4

Aufbauend auf der Antwort von Ben Dolman werden die Ansichten gleichmäßiger verteilt (mit Polsterung usw.):

+(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;
}


3

Bei Etiketten funktioniert dies zumindest einwandfrei:

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

Wenn die erste die gleiche Breite wie die zweite und die zweite die dritte und die dritte die erste hat, erhalten alle die gleiche Breite ... Sie können dies sowohl horizontal (H) als auch vertikal (V) tun.


3

schnelle 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)

1
Die Ausarbeitung, wie dieser Code die Fragen beantwortet, würde zukünftigen Besuchern helfen.
JAL

2

Ich weiß, dass seit der ersten Antwort eine Weile vergangen ist, aber ich bin gerade auf das gleiche Problem gestoßen und möchte meine Lösung teilen. Für kommende Generationen ...

Ich habe meine Ansichten auf viewDidLoad festgelegt:

- (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];

}

Und dann lösche ich bei updateViewConstrains zuerst alle Einschränkungen, erstelle dann das Ansichtswörterbuch und berechne dann den Abstand, der zwischen den Ansichten verwendet werden soll. Danach benutze ich einfach das Visual Language Format, um die Einschränkungen festzulegen:

- (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];



}

Das Gute an dieser Methode ist, dass Sie nur sehr wenig rechnen müssen. Ich sage nicht, dass dies die perfekte Lösung ist, aber ich arbeite für das Layout, das ich erreichen wollte.

Ich hoffe, es hilft.


2

Hier ist eine Lösung, die eine beliebige Anzahl von Unteransichten vertikal zentriert, auch wenn sie eindeutige Größen haben. Was Sie tun möchten, ist, einen Container auf mittlerer Ebene zu erstellen, diesen in der Übersicht zu zentrieren, dann alle Unteransichten in den Container einzufügen und sie in Bezug zueinander anzuordnen. Entscheidend ist aber auch, dass Sie sie nach oben und unten beschränken Unterseite des Containers beschränken müssen, damit der Container in der Übersicht richtig dimensioniert und zentriert werden kann. Durch Ermitteln der richtigen Höhe aus den Unteransichten kann der Container vertikal zentriert werden.

In diesem Beispiel selfist dies die Übersicht, in der Sie alle Unteransichten zentrieren.

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

Dies ist die beste Lösung, wenn Sie Ansichten unterschiedlicher Größe verwenden, die zentriert werden müssen.
Noobsmcgoobs

2

Ich habe mein Problem gerade mit der Multiplikatorfunktion gelöst. Ich bin nicht sicher, ob es in allen Fällen funktioniert, aber für mich hat es perfekt funktioniert. Ich bin auf Xcode 6.3 FYI.

Am Ende habe ich Folgendes getan:

1) Zuerst wurden meine Schaltflächen auf einem Bildschirm mit einer Breite von 320 Pixel so positioniert, wie ich es auf einem Gerät mit 320 Pixel sehen wollte.

Schritt 1: Positionieren der Tasten

2) Dann habe ich eine führende Space-Einschränkung hinzugefügt, um alle meine Schaltflächen zu überwachen.

Schritt 2: Fügen Sie führende Platzbeschränkungen hinzu

3) Dann habe ich die Eigenschaften des führenden Bereichs so geändert, dass die Konstante 0 war und der Multiplikator der x-Versatz geteilt durch die Breite des Bildschirms ist (z. B. war meine erste Schaltfläche 8 Pixel vom linken Rand entfernt, also habe ich meinen Multiplikator auf 8/320 gesetzt).

4) Dann ist der wichtige Schritt hier, das zweite Element in der Einschränkungsrelation so zu ändern, dass es das Superview.Trailing anstelle von Superview.Leading ist. Dies ist wichtig, da Superview.Leading 0 und Trailing in meinem Fall 320 ist. 8/320 ist also 8 px auf einem 320px-Gerät. Wenn sich die Breite der Superview auf 640 oder was auch immer ändert, bewegen sich alle Ansichten in einem Verhältnis zur Breite der 320px Bildschirmgröße. Die Mathematik hier ist viel einfacher zu verstehen.

Schritt 3 & 4: Ändern Sie den Multiplikator in xPos / screenWidth und setzen Sie das zweite Element auf .Trailing


2

Viele Antworten sind nicht richtig, aber es gibt viele Zählungen. Hier schreibe ich nur programmgesteuert eine Lösung. Die drei Ansichten sind horizontal ausgerichtet, ohne Abstandsansichten zu verwenden. Dies funktioniert jedoch nur, wenn die Breite der Beschriftungen bekannt ist, wenn sie im Storyboard verwendet werden .

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]];

2

Hier ist noch eine andere Antwort. Ich beantwortete eine ähnliche Frage und sah einen Link, der auf diese Frage verwies. Ich habe keine ähnliche Antwort wie meine gesehen. Also dachte ich daran, es hier zu schreiben.

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

Geben Sie hier die Bildbeschreibung ein

Auch dies kann mit iOS9 UIStackViews ganz einfach durchgeführt werden.

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

Beachten Sie, dass es genau der gleiche Ansatz wie oben ist. Es werden vier Containeransichten hinzugefügt, die gleichmäßig gefüllt sind, und jeder Stapelansicht, die in der Mitte ausgerichtet ist, wird eine Ansicht hinzugefügt. Diese Version von UIStackView reduziert jedoch den Code und sieht gut aus.


1

Ein anderer Ansatz könnte darin bestehen, dass die oberen und unteren Beschriftungen Einschränkungen in Bezug auf die Ansicht oben bzw. unten und die mittlere Ansicht Einschränkungen in Bezug auf die obere und untere Ansicht in Bezug auf die erste bzw. dritte Ansicht aufweisen.

Beachten Sie, dass Sie mehr Kontrolle über Einschränkungen haben, als es den Anschein hat, als würden Sie Ansichten nahe beieinander ziehen, bis gestrichelte Linien angezeigt werden. Diese zeigen Einschränkungen zwischen den beiden Objekten an, die anstelle zwischen dem Objekt und der Übersicht erstellt werden.

In diesem Fall möchten Sie dann die Einschränkungen so ändern, dass sie größer als oder gleich dem gewünschten Wert sind, anstatt "gleich", damit sie die Größe ändern können. Ich bin mir nicht sicher, ob dies genau das tut, was Sie wollen.


1

Sehr einfache Möglichkeit, dies in InterfaceBuilder zu lösen:

Setzen Sie die zentrierte Beschriftung (label2) auf "Horizontale Mitte im Container" und "Vertikale Mitte im Container".

Wählen Sie die zentrierte Beschriftung und die obere Beschriftung (Beschriftung1 + Beschriftung2) aus und fügen Sie ZWEI Einschränkungen für den vertikalen Abstand hinzu. Eins mit größer als oder gleich dem Mindestabstand. Eins mit weniger als oder gleich dem maximalen Abstand.

Gleiches gilt für das zentrierte Etikett und das untere Etikett (Etikett2 + Etikett3).

Darüber hinaus können Sie label1 zwei Einschränkungen hinzufügen - den oberen Bereich für SuperView und zwei Einschränkungen für label2 - den unteren Bereich für SuperView.

Das Ergebnis ist, dass alle 4 Abstände ihre Größe gleichermaßen ändern.


2
Ich habe festgestellt, dass dies nur funktioniert, wenn Sie die Größe der Übersicht ändern, sodass der Abstand genau die Min- oder Max-Werte annimmt. Wenn Sie die Größe auf einen Wert irgendwo dazwischen ändern, werden die Unteransichten nicht gleichmäßig verteilt.
Dorian Roy

1

Ich habe eine Funktion erstellt, die helfen könnte. Dieses Anwendungsbeispiel:

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

wird diese vertikale Verteilung verursachen (leider haben Sie nicht die 10 Reputation, um Bilder einzubetten). Und wenn Sie die Achse und einige Randwerte ändern:

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

Sie erhalten diese horizontale Verteilung .

Ich bin Neuling in iOS, aber 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

1

Ja, Sie können dies ausschließlich im Interface Builder und ohne Code schreiben. Die einzige Einschränkung besteht darin, dass Sie die Größe des Etiketts ändern, anstatt Leerzeichen zu verteilen. Richten Sie in diesem Fall das X und Y von Label 2 so an der Übersicht aus, dass es in der Mitte fixiert ist. Setzen Sie dann den vertikalen Raum von Etikett 1 auf die Übersicht und auf Etikett 2 auf den Standard. Wiederholen Sie diesen Vorgang für Etikett 3. Nachdem Sie Etikett 2 eingestellt haben, können Sie Etiketten 1 und 3 am einfachsten ändern, indem Sie die Größe ändern, bis sie einrasten.

Hier ist die horizontale Anzeige. Beachten Sie, dass der vertikale Abstand zwischen Etikett 1 und 2 auf Standard eingestellt ist:horizontale Anzeige

Und hier ist die Porträtversion:Geben Sie hier die Bildbeschreibung ein

Mir ist klar, dass sie aufgrund des Unterschieds zwischen dem Standardabstand zwischen Beschriftungen und dem Standardabstand zur Übersicht nicht zu 100% gleichmäßig zwischen den Basislinien verteilt sind. Wenn Sie das stört, setzen Sie die Größe auf 0 anstelle von Standard


1

Ich habe einen Breitenwert nur für das erste Element (> = eine Breite) und einen Mindestabstand zwischen jedem Element (> = eine Entfernung) festgelegt. Dann benutze ich Strg, um das zweite, dritte ... Element auf das erste zu ziehen, um Abhängigkeiten zwischen den Elementen zu verketten.

Geben Sie hier die Bildbeschreibung ein


1

Spät zur Party, aber ich habe eine funktionierende Lösung, um ein Menü horizontal mit Abstand zu erstellen. ==Dies kann problemlos mit NSLayoutConstraint durchgeführt werden

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

0

Android hat eine Methode zum Verketten von Ansichten in seinem auf Einschränkungen basierenden Layoutsystem, das ich nachahmen wollte. Suchen haben mich hierher gebracht, aber keine der Antworten hat ganz funktioniert. Ich wollte StackViews nicht verwenden, weil sie mir auf der ganzen Linie mehr Kummer bereiten, als sie im Voraus sparen. Am Ende habe ich eine Lösung erstellt, die UILayoutGuides verwendet, die zwischen den Ansichten platziert sind. Das Steuern ihrer Breite ermöglicht verschiedene Arten von Distributionen und Kettenstilen im Android-Sprachgebrauch. Die Funktion akzeptiert einen führenden und einen nachfolgenden Anker anstelle einer übergeordneten Ansicht. Auf diese Weise kann die Kette zwischen zwei beliebigen Ansichten platziert und nicht innerhalb der übergeordneten Ansicht verteilt werden. Es wird verwendet, UILayoutGuidewas nur in iOS 9+ verfügbar ist, aber das sollte kein Problem mehr sein.

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
}

0

Ich wollte 5 Bilder horizontal ausrichten, also folgte ich Metes Antwort mit einem kleinen Unterschied.

Das erste Bild wird horizontal in einem Container mit 0 und einem Multiplikator von 1: 5 zentriert:

erstes Bild

Das zweite Bild wird horizontal in einem Container mit 0 und einem Multiplikator von 3: 5 zentriert: zweites Bild

Und so für den Rest der Bilder. Zum Beispiel wird das fünfte (und letzte) Bild horizontal in einem Container zentriert, der gleich 0 und einem Multiplikator von 9: 5 ist: letztes Bild

Wie Mete erklärte, lautet die Reihenfolge 1, 3, 5, 7, 9 usw. Die Positionen folgen der gleichen Logik: Die erste Position ist 1, dann das Leerzeichen, dann die nächste Position 3 und so weiter.


-1

Warum erstellen Sie nicht einfach eine Tabellenansicht und machen isScrollEnabled = false


Verstehe die Ablehnung nicht, meine Lösung ist, über den Tellerrand hinaus zu denken. Warum sollten Sie sich die Mühe machen, eine Liste mit Autolayout zu erstellen, wenn Cocoa Ihnen eine Liste in UITableView gibt?
Josh O'Connor
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.