Równomiernie rozmieszczaj wiele widoków w widoku kontenera

279

Auto Layout utrudnia mi życie. Teoretycznie przydałoby się to naprawdę przydać, ale wydaje mi się, że cały czas z tym walczę.

Zrobiłem projekt demonstracyjny, aby spróbować znaleźć pomoc. Czy ktoś wie, jak sprawić, aby odstępy między widokami zwiększały się lub zmniejszały równomiernie przy każdej zmianie rozmiaru widoku?

Oto trzy etykiety (ręcznie rozmieszczone nawet w pionie):

zdjęcie 1

Chcę, aby podczas obrotu obracali się równomiernie zmieniając odstępy (a nie rozmiar widoku). Domyślnie widoki górny i dolny są ściśnięte w kierunku środka:

zdjęcie 2

nothappybob
źródło
może ustawić ograniczenie z jednej etykiety na drugą i ustalić, że relacja „większa niż lub równa” będzie pomocna
BergP,
programowo na przykład przy użyciu tej metody NSLayoutConstraint: + (id) constWaWithItem: (id) atrybut view1: (NSLayoutAttribute) attr1 relatedBy: (NSLayoutRelation) relacja zItem: (id) atrybut view2: (NSLayoutAttribute) mnożnik attr2: (CGFloat) stała: (CGFloat) c
BergP
Otworzyłem źródło ogólnego zastąpienia UITabBar, który uogólnia to podejście za pomocą Auto Layout. Możesz sprawdzić testy, aby zobaczyć, w jaki sposób generuję ograniczenia automatycznego układu. GGTabBar
Goles

Odpowiedzi:

211

Moje podejście pozwala to zrobić w narzędziu do tworzenia interfejsów. To, co robisz, to tworzenie „widoków odstępnych”, które ustawiłeś tak, aby odpowiadały wysokościom jednakowo. Następnie dodaj górne i dolne ograniczenia do etykiet (patrz zrzut ekranu).

wprowadź opis zdjęcia tutaj

Mówiąc dokładniej, mam górne ograniczenie w „Widoku odstępu 1”, aby nadzorować z ograniczeniem wysokości o niższym priorytecie niż 1000 i z wysokością równą wszystkim innym „widokom odstępu”. „Widok Spacer 4” ma dolne ograniczenie miejsca do podglądu. Każda etykieta ma odpowiednie ograniczenia górne i dolne do najbliższych „widoków odstępnych”.

Uwaga: Upewnij się, że NIE masz dodatkowych ograniczeń górnego / dolnego miejsca na etykietach do przeglądania; tylko te do „widoków kosmicznych”. Będzie to satysfakcjonujące, ponieważ ograniczenia górne i dolne dotyczą odpowiednio „Widoku przestrzeni 1” i „Widoku odległości 4”.

Krok 1: Zduplikowałem mój widok i po prostu przestawiłem go w tryb poziomy, abyś mógł zobaczyć, że zadziałał.

Duh 2: „Widoki odstępów” mogły być przezroczyste.

Duh 3: To podejście można zastosować poziomo.

Spencer Hall
źródło
10
To działa. W rzeczywistości widoki elementów dystansowych mogą być ukryte i nadal zapewniają poprawny układ.
paulmelnikow
To było bardzo pomocne. Podobnie jak komentarz dotyczący po prostu oznaczenia widoków spacer jako ukrytych. Następnie nadal możesz je zobaczyć w swoim storyboard / xib, ale nie pojawiają się w Twojej aplikacji. Bardzo dobrze.
shulmey,
Mam to prawie pracy po kilku godzinach wielokrotnych prób. Niestety, Xcode wciąż usuwa moje ograniczenia między górną przestrzenią a przyciskiem i dolną przestrzenią, przerywając łańcuch między przyciskami i przekładkami i zastępując je dowolnymi ograniczeniami między górną przestrzenią a superwisem, które są bezużyteczne.
Desty
To samo podejście działa dla mnie w układzie poziomym - mam widoki o stałej szerokości oddzielone widokami odstępu o równej szerokości. To wspaniałe, że nie muszę mieszać go z żadnym kodem. Dzięki!
Kamil Nomtek.com
2
Nie trzeba używać przekładek. Zobacz moją odpowiedź poniżej.
smileBot
316

OBEJRZYJ, BEZ SPACERÓW!

W oparciu o sugestie w sekcji komentarzy mojej oryginalnej odpowiedzi, a zwłaszcza pomocne sugestie @ Rivera, uprościłem moją oryginalną odpowiedź.

Używam gifów, aby zilustrować, jak proste jest to. Mam nadzieję, że gify będą dla ciebie pomocne. Na wypadek, gdybyś miał problem z gifami, zamieściłem starą odpowiedź poniżej ze zwykłymi zrzutami ekranu.

Instrukcje:

1) Dodaj swoje przyciski lub etykiety. Używam 3 przycisków.

2) Dodaj ograniczenie środkowe x z każdego przycisku do podglądu:

wprowadź opis zdjęcia tutaj

3) Dodaj ograniczenie z każdego przycisku do dolnego ograniczenia układu:

wprowadź opis zdjęcia tutaj

4) Dostosuj ograniczenie dodane w punkcie 3 powyżej w następujący sposób:

a) wybierz ograniczenie, b) usuń stałą (ustaw na 0), c) zmień mnożnik w następujący sposób: weź liczbę przycisków + 1 i zaczynając od góry ustaw mnożnik jako buttonCountPlus1: 1 , a następnie buttonCountPlus1 : 2 i wreszcie buttonCountPlus1: 3 . (Wyjaśniam, skąd wziąłem ten wzór w starej odpowiedzi poniżej, jeśli jesteś zainteresowany).

wprowadź opis zdjęcia tutaj

5) Oto uruchomiona wersja demonstracyjna!

wprowadź opis zdjęcia tutaj

Uwaga: Jeśli twoje przyciski mają większe wysokości, musisz to zrekompensować stałą wartością, ponieważ ograniczenie jest od dołu przycisku.


Stara odpowiedź


Pomimo tego, co mówią dokumenty Apple'a i doskonała książka Erici Sadun ( Auto Layout Demystified ), możliwe jest równomierne rozmieszczenie widoków bez przekładek. Jest to bardzo proste w IB i kodzie dla dowolnej liczby elementów, które chcesz rozmieścić równomiernie. Wszystko czego potrzebujesz to formuła matematyczna zwana „formułą sekcji”. Łatwiej to zrobić niż wyjaśnić. Zrobię co w mojej mocy, demonstrując to w IB, ale tak samo łatwo zrobić to w kodzie.

W omawianym przykładzie zrobiłbyś to

1) rozpocznij od ustawienia każdej etykiety na ograniczenie środkowe. To jest bardzo proste do zrobienia. Po prostu kontroluj przeciąganie z każdej etykiety na dół.

2) Przytrzymaj klawisz shift, ponieważ równie dobrze możesz dodać inne ograniczenie, którego będziemy używać, a mianowicie „przewodnik po układzie od dołu do dołu”.

3) Wybierz „przewodnik po układzie od dołu do dołu” i „wyśrodkuj poziomo w pojemniku”. Zrób to dla wszystkich 3 etykiet.

Przytrzymaj klawisz Shift, aby dodać te dwa ograniczenia dla każdej etykiety

Zasadniczo, jeśli weźmiemy etykietę, której współrzędną chcemy ustalić, i podzielimy ją przez całkowitą liczbę etykiet plus 1, wówczas mamy liczbę, którą możemy dodać do IB, aby uzyskać dynamiczną lokalizację. Upraszczam formułę, ale możesz jej użyć do ustawienia odstępów w poziomie lub jednocześnie w pionie i poziomie. Jest super potężny!

Oto nasze mnożniki.

Label1 = 1/4 = 0,25,

Label2 = 2/4 = .5,

Etykieta 3 = 3/4 = 0,75

(Edycja: @Rivera skomentował, że można po prostu użyć współczynników bezpośrednio w polu mnożnika i xCode z matematyki!)

4) Wybierzmy Label1 i wybierz dolne ograniczenie. Lubię to: wprowadź opis zdjęcia tutaj

5) Wybierz „Drugi element” w Inspektorze atrybutów.

6) Z rozwijanego menu wybierz „Odwróć pierwszy i drugi element”.

7) Wyzeruj stałą i wartość hCowolna wC. (W razie potrzeby możesz dodać przesunięcie).

8) Jest to część krytyczna: w polu mnożnika dodaj nasz pierwszy mnożnik 0.25.

9) W tym miejscu ustaw górny „Pierwszy element” na „ŚrodekY”, ponieważ chcemy wyśrodkować go na środku y etykiety. Oto jak to wszystko powinno wyglądać.

wprowadź opis zdjęcia tutaj

10) Powtórz ten proces dla każdej etykiety i podłącz odpowiedni mnożnik: 0,5 dla Label2 i 0,75 dla Label3. Oto końcowy produkt we wszystkich orientacjach ze wszystkimi kompaktowymi urządzeniami! Super proste. Patrzyłem na wiele rozwiązań obejmujących ryzę kodu i przekładki. To zdecydowanie najlepsze rozwiązanie, jakie widziałem w tej sprawie.

Aktualizacja: @kraftydevil dodaje, że dolny przewodnik po układzie pojawia się tylko w scenorysach, a nie w XIB-ach. Użyj „Bottom Space to Container” w xib. Dobry chwyt!

wprowadź opis zdjęcia tutaj

smileBot
źródło
2
Nadal mam z tym problem. Myślę, że musisz używać Xcode 6, ponieważ nie ma opcji „WC hAny” w ograniczeniach Xcode 5. Kiedy postępuję zgodnie z instrukcjami, wszystkie widoki podrzędne utkną w górnym centrum.
kraftydevil
4
@kraftydevil AFAIK Przewodnik po układzie dolnym pojawia się tylko w scenorysach, a nie w XIB-ach. Użyj „Bottom Space to Container” w xib.
Timur Kuchkarov
6
Och - warto również zauważyć, że IB w Xcode 6 obsługuje mnożniki współczynników, więc możesz wstawić takie rzeczy jak „1: 4”, „2: 5”, „3: 4” itp.
Benjohn
3
Próbując dowiedzieć się, jak to zrobić w poziomie - jak pierwszy TextView 1/3 drogi od lewej i drugi TextView 2/3 (reszta drogi) ... ... (EDYCJA) dostałem - wykonaj pierwsza 1/3 (lewa) Trailing-Space do Right Margin, wstecz, zero, przypisuj jak wcześniej ...
kfmfe04
3
Aby uzyskać odstępy poziome, po prostu ustaw ograniczenia końcowe dla superview dla każdego widoku częściowego, a następnie ustaw mnożnik współczynnika, np. 5: 1, 5: 2, 5: 3 itd. Poruszanie się od lewej do prawej.
user3344977,
98

Bardzo szybkie rozwiązanie Konstruktora interfejsów:

Aby dowolna liczba widoków była równomiernie rozmieszczona w obrębie podglądu, po prostu nadaj każdemu wiązanie „Wyrównaj środek X do podglądu” dla układu poziomego lub „Wyrównaj środek Y podglądu” dla układu pionowego i ustaw mnożnik na N:p( UWAGA: niektóre mieli więcej szczęścia p:N- patrz poniżej )

gdzie

N = total number of views, i

p = position of the view including spaces

Pierwsza pozycja to 1, następnie spacja, co daje następną pozycję 3, więc p staje się serią [1,3,5,7,9, ...]. Działa dla dowolnej liczby widoków.

Jeśli więc masz 3 widoki do rozmieszczenia, wygląda to tak:

Ilustracja, jak równomiernie rozłożyć poglądy w IB

EDYCJA Uwaga: Wybór N:plub p:Nzależy od kolejności relacji twojego ograniczenia wyrównania. Jeśli „Pierwszy element” to Superview.Center, możesz go użyć p:N, natomiast jeśli „Superview.Center” to „Drugi element”, możesz go użyć N:p. W razie wątpliwości wypróbuj oba ... :-)

Zmierzyć
źródło
To rozwiązanie nie będzie odzwierciedlać wyglądu, jeśli przełączysz się na inny język
wod
@wod Co masz na myśli? Przełączyć na inny język? A co dokładnie się psuje?
Mete
4
co jeśli elementy nie są tego samego rozmiaru? ten wzór nie działa poprawnie, prawda?
ucangetit
2
Nie rozumiem, jak to rozwiązanie jest poprawne? Szczelina między krawędzią ekranu a elementami zewnętrznymi różni się od szczeliny między elementami zewnętrznymi a elementem środkowym?
myles,
6
To nie dla mnie „tak jak jest” musiałem odwrócić mnożniki (np. 3: 1 z pierwszego elementu zamienia się na 1: 3). Wyrzucam to, jeśli to komukolwiek pomoże.
Ces
70

Począwszy od iOS 9, Apple bardzo to ułatwiło (długo oczekiwany) UIStackView. Po prostu wybierz widoki, które chcesz zawrzeć w Konstruktorze interfejsów i wybierz Edytor -> Osadź w -> Widok stosu. Ustaw odpowiednie ograniczenia szerokości / wysokości / marginesu dla widoku stosu i ustaw właściwość Distribution na „Równe odstępy”:

Oczywiście, jeśli chcesz obsługiwać system iOS 8 lub nowszy, musisz wybrać jedną z pozostałych opcji.

Glorfindel
źródło
Tak, to smutne, że nie ma to kompatybilności retro, więc dopóki twoja aplikacja nie potrzebuje wsparcia dla IOS8 @Mete, odpowiedź jest na razie najlepsza.
ZiggyST
51

Byłem na rollercoasterze, kochając autolayout i nienawidząc tego. Kluczem do miłości wydaje się akceptacja następujących rzeczy:

  1. Edycja konstruktora interfejsów i „pomocne” automatyczne tworzenie wiązań jest prawie bezużyteczne dla wszystkich prócz najbardziej trywialnych przypadków
  2. Tworzenie kategorii w celu uproszczenia typowych operacji oszczędza życie, ponieważ kod jest tak powtarzalny i pełny.

To powiedziawszy, to, co próbujesz, nie jest proste i byłoby trudne do osiągnięcia w kreatorze interfejsów. Jest to dość proste do zrobienia w kodzie. Ten kod w viewDidLoadtworzy i ustawia trzy etykiety, w jaki sposób o nie prosisz:

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

Jak mówię, ten kod jest znacznie uproszczony dzięki kilku metodom kategorii w UIView , ale dla jasności zrobiłem to tutaj.

Kategoria jest tutaj dla zainteresowanych i ma metodę równomiernego rozmieszczania tablicy widoków wzdłuż określonej osi.

jrturton
źródło
Panie, jesteś ratownikiem. Do tej pory nie rozumiałem # 1. Konstruktor interfejsów doprowadza mnie do szału, ale myślałem, że był w stanie zrobić te same rzeczy. W każdym razie wolę używać kodu, więc jest to idealne i przejdę do metod kategorii.
nothappybob
Chociaż zdecydowanie dość blisko, to nie do końca rozwiązuje dokładnie zadany problem (utrzymanie tego samego rozmiaru widoku przy równomiernych odstępach między widokami podrzędnymi). To rozwiązanie jest dopracowaną ogólną odpowiedzią na przypadek.
smileyborg,
21

Większość tych rozwiązań zależy od nieparzystej liczby przedmiotów, dzięki czemu można wziąć środkowy przedmiot i wyśrodkować go. Co zrobić, jeśli masz parzystą liczbę przedmiotów, które nadal chcesz równomiernie rozdzielić? Oto bardziej ogólne rozwiązanie. Ta kategoria równomiernie rozłoży dowolną liczbę elementów wzdłuż osi pionowej lub poziomej.

Przykład użycia do pionowej dystrybucji 4 etykiet w ramach ich podglądu:

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

NSLayoutConstraint + EvenDistribution.h

@interface NSLayoutConstraint (EvenDistribution)

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

@end

NSLayoutConstraint + EvenDistribution.m

@implementation NSLayoutConstraint (EvenDistribution)

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

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

    return constraints;
}

@end
Ben Dolman
źródło
1
Działa poprzez usunięcie wszystkich wiązań i natychmiast po tym, dodanie do tych obiektów ograniczeń Szerokość i Wysokość, a następnie wywołanie metody constraintsForEvenDistributionOfItems: relativeToCenterOfItem: pionowo:
carlos_ms
@carlos_ms Chciałbym zobaczyć twój kod, ponieważ gdy próbuję z parzystą liczbą elementów, kod ten działa idealnie przy zmianie orientacji urządzenia. Czy na pewno nie masz przypadkowo innych ograniczeń (takich jak ograniczenia autoresize), które powodują konflikt?
Ben Dolman
Oto przykład tworzenia 4 etykiet i równomiernego rozmieszczania ich wzdłuż osi pionowej: gist.github.com/bdolman/5379465
Ben Dolman
20

Prawidłowym i najłatwiejszym sposobem jest użycie widoków stosu.

  1. Dodaj swoje etykiety / widoki do widoku stosu :

wprowadź opis zdjęcia tutaj

  1. Wybierz widok stosu i ustaw opcję Dystrybucja na równe odstępy :

wprowadź opis zdjęcia tutaj

  1. Dodaj odstępy do ograniczeń najbliższego sąsiada do widoku stosu i zaktualizuj ramki:

wprowadź opis zdjęcia tutaj

  1. Dodaj ograniczenia wysokości do wszystkich etykiet (opcjonalnie). Potrzebne tylko w widokach, które nie mają rozmiaru wewnętrznego). Etykiety na przykład nie potrzebują tutaj ograniczeń wysokości i muszą jedynie ustawić na przykład numberOfLines = 3 lub 0.

wprowadź opis zdjęcia tutaj

  1. Ciesz się podglądem:

wprowadź opis zdjęcia tutaj

Oleh Kudinov
źródło
2
To tylko> = iOS 9.0, więc sprawdź cel wdrożenia przed rozpoczęciem implementacji :) Świetnie, że to zostało dodane!
DivideByZer0,
1
Teraz 2018, użyj widoku stosu
Jingshao Chen,
17

Sprawdź bibliotekę Open Source PureLayout . Oferuje kilka metod API do dystrybucji widoków, w tym warianty, w których odstępy między każdym widokiem są ustalone (rozmiar widoku zmienia się w zależności od potrzeb) i gdzie rozmiar każdego widoku jest stały (odstępy między widokami zmieniają się w zależności od potrzeb). Zauważ, że wszystkie te są realizowane bez użycia „widoków odstępnych”.

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

// ...

Ponieważ jest to wszystko oprogramowanie typu open source, jeśli chcesz zobaczyć, jak to osiągnąć bez widoków spacerowych, spójrz na implementację. (To zależy od wykorzystania zarówno constant i multiplier dla ograniczeń.)

Smileyborg
źródło
Dobra robota! Chociaż prawdopodobnie powinieneś zmienić nazwę repozytorium, abyś mógł go udostępnić za pośrednictwem cocoapods.
jrturton,
9

Mam podobny problem i odkryłem ten post. Jednak żadna z obecnie dostępnych odpowiedzi nie rozwiązuje problemu w pożądany sposób. Nie wyrównują odstępów, lecz równomiernie rozkładają środek etykiet. Ważne jest, aby zrozumieć, że to nie to samo. Zilustrowałem ten schemat, aby to zilustrować.

Zobacz ilustrację odstępów

Istnieją 3 widoki, wszystkie o wysokości 20 punktów. Zastosowanie dowolnej z sugerowanych metod równomiernie rozprowadza środki widoków i daje zilustrowany układ. Zauważ, że środek y widoków jest równomiernie rozmieszczony. Jednak odstęp między widokiem z góry a widokiem z góry wynosi 15 punktów, podczas gdy odstęp między widokami podrzędnymi wynosi zaledwie 5 punktów. Aby widoki były równomiernie rozmieszczone, oba powinny mieć 10 punktów, tzn. Wszystkie niebieskie strzałki powinny mieć 10 punktów.

Niemniej jednak nie znalazłem jeszcze dobrego ogólnego rozwiązania. Obecnie moim najlepszym pomysłem jest wstawienie „widoków odstępów” między widokami podrzędnymi i ustawienie równych wysokości widoków odstępów.

Florian
źródło
1
Użyłem rozwiązania ukrytych widoków odstępnika, które działa dobrze.
paulmelnikow
8

Byłem w stanie rozwiązać to całkowicie w IB:

  1. Wprowadź ograniczenia, aby wyrównać środek Y każdego z widoków podrzędnych do dolnej krawędzi widoku.
  2. Ustaw mnożnik każdego z tych ograniczeń na 1 / 2n, 3 / 2n, 5 / 2n,…, n-1 / 2n, gdzie n jest liczbą rozpowszechnianych widoków podrzędnych.

Więc jeśli masz trzy etykiety, ustaw mnożniki dla każdego z tych ograniczeń na 0.1666667, 0.5, 0.833333.

Hayesk
źródło
1
Właściwie musiałem zrobić 1 / n 3 / n 5 / n 7 / n upto (2n-1) / n
Hamzah Malik
5

Znalazłem idealną i prostą metodę. Automatyczny układ nie pozwala równomiernie zmieniać rozmiaru przestrzeni, ale pozwala równomiernie zmieniać rozmiar widoków. Po prostu umieść kilka niewidocznych widoków między polami i powiedz automatycznemu układowi, aby zachował ten sam rozmiar. Działa idealnie!

Początkowy XIB

Rozciągnięty XIB

Jedna rzecz godna uwagi; kiedy zmniejszyłem rozmiar w projektancie interfejsu, czasami się mylił i zostawił etykietę tam, gdzie był, i miał konflikt, jeśli rozmiar został zmieniony o dziwną wartość. W przeciwnym razie działało idealnie.

edycja: Odkryłem, że konflikt stał się problemem. Z tego powodu wziąłem jedno z ograniczeń odległości, usunąłem je i zastąpiłem dwoma ograniczeniami, większym lub równym i mniejszym lub równym. Oba były tego samego rozmiaru i miały znacznie niższy priorytet niż inne ograniczenia. Rezultatem nie był dalszy konflikt.

Owen Godfrey
źródło
4

Opierając się na odpowiedzi Bena Dolmana, rozkłada to widoki bardziej równomiernie (z wypełnieniem itp.):

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

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

    return constraints;
}
Ray W.
źródło
3

W przypadku etykiet działa to co najmniej dobrze:

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

Jeśli pierwsza ma taką samą szerokość jak druga, a druga trzecia i trzecia pierwsza, wówczas wszystkie uzyskają tę samą szerokość ... Możesz to zrobić zarówno poziomo (H), jak i pionowo (V).

Johannes
źródło
3

szybka wersja 3

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

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

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

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

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

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

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


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

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

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

        let constraint_yellow2 = NSLayoutConstraint.init(item: _blueView, attribute: .centerX, relatedBy: .equal, toItem: _yellowView, attribute: .leading, multiplier: 1.5, constant: 40)
        self.view.addConstraint(constraint_yellow2)
Sarath Raveendran
źródło
1
Opracowanie, w jaki sposób ten kod odpowiada na pytania, pomoże przyszłym użytkownikom.
JAL,
2

Wiem, że minęło trochę czasu od pierwszej odpowiedzi, ale właśnie natknąłem się na ten sam problem i chcę podzielić się moim rozwiązaniem. Dla przyszłych pokoleń ...

Ustawiam swoje widoki na 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];

}

Następnie w updateViewConstrains najpierw usuwam wszystkie ograniczenia, następnie tworzę słownik widoków, a następnie obliczam przestrzeń, która będzie używana między widokami. Następnie po prostu używam Visual Language Format, aby ustawić ograniczenia:

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



}

Zaletą tej metody jest to, że musisz robić bardzo mało matematyki. Nie twierdzę, że jest to idealne rozwiązanie, ale pracuję dla układu, który starałem się osiągnąć.

Mam nadzieję, że to pomoże.

Marcal
źródło
2

Oto rozwiązanie, które pionowo wyśrodkuje dowolną liczbę widoków podrzędnych, nawet jeśli mają one unikalne rozmiary. To, co chcesz zrobić, to zrobić kontener średniego poziomu, wyśrodkować go w superwizji, a następnie umieścić wszystkie widoki cząstkowe w kontenerze i ułożyć je względem siebie. Ale co najważniejsze, musisz również ograniczyć je do szczytu i dolnej części kontenera, aby kontener mógł być odpowiednio zwymiarowany i wyśrodkowany w widoku podglądu. Określając odpowiednią wysokość na podstawie widoków podrzędnych, pojemnik można wyśrodkować w pionie.

W tym przykładzie selfjest superview, w którym centrujesz wszystkie podviewy.

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

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

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

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

    UIView *subviewAbove = nil;
    for (UIView *subview in subviews) {
        [container addConstraint:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual
                                                                 toItem:container attribute:NSLayoutAttributeCenterX multiplier:1.0f constant:0.0f]];
        if (subviewAbove) {
            [container addConstraint:[NSLayoutConstraint constraintWithItem:subview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
                                                                     toItem:subviewAbove attribute:NSLayoutAttributeBottom multiplier:1.0f constant:10.0f]];
        }
        subviewAbove = subview;
    }
}
Kevin Conner
źródło
Jest to najlepsze rozwiązanie w przypadku korzystania z widoków o różnych rozmiarach, które należy wyśrodkować.
noobsmcgoobs
2

Właśnie rozwiązałem swój problem za pomocą funkcji mnożnika. Nie jestem pewien, czy to działa we wszystkich przypadkach, ale dla mnie działało idealnie. Jestem na Xcode 6.3 FYI.

Skończyło się na tym, że:

1) Najpierw ustawiam przyciski na ekranie o szerokości 320 pikseli, tak jak chciałem, aby wyglądało na urządzeniu o rozdzielczości 320 pikseli.

krok 1: ustawianie przycisków

2) Następnie dodałem wiodące ograniczenie Spacji do podglądu wszystkich moich przycisków.

krok 2: dodaj wiodące ograniczenia przestrzeni

3) Następnie zmodyfikowałem właściwości wiodącej przestrzeni, tak aby stała wynosiła 0, a mnożnik jest przesunięciem x podzielonym przez szerokość ekranu (np. Mój pierwszy przycisk był 8px od lewej krawędzi, więc ustawiłem mnożnik na 8/320)

4) Zatem ważnym krokiem tutaj jest zmiana drugiego elementu w relacji ograniczenia na superview.Trailing zamiast superview.leading. Jest to kluczowe, ponieważ superview. Odczyt wynosi 0, a w moim przypadku końcowy wynosi 320, więc 8/320 ma 8 pikseli na urządzeniu 320px, a następnie, gdy szerokość superview zmieni się na 640 lub cokolwiek innego, widoki poruszają się w stosunku względem szerokości o wielkości ekranu 320 pikseli. Tutaj matematyka jest znacznie prostsza do zrozumienia.

krok 3 i 4: zmień mnożnik na xPos / screenWidth i ustaw drugi element na .Trailing

ucangetit
źródło
2

Wiele odpowiedzi jest niepoprawnych, ale wiele się liczy. Tutaj po prostu piszę programowo rozwiązanie, trzy widoki są wyrównane w poziomie, bez użycia widoków odstępu , ale działa to tylko wtedy, gdy szerokości etykiet są znane, gdy są używane w serii ujęć .

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

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

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

[self.view addConstraint:[NSLayoutConstraint constraintWithItem:self.view attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yellowView attribute:NSLayoutAttributeCenterX multiplier:1 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:_redView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yellowView attribute:NSLayoutAttributeLeading multiplier:0.5 constant:0]];
[self.view addConstraint:[NSLayoutConstraint constraintWithItem:_blueView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:_yellowView attribute:NSLayoutAttributeLeading multiplier:1.5 constant:40]];
zgjie
źródło
2

Oto kolejna odpowiedź. Odpowiedziałem na podobne pytanie i zobaczyłem link do tego pytania. Nie widziałem żadnej odpowiedzi podobnej do mojej. Pomyślałem więc o napisaniu tego tutaj.

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

wprowadź opis zdjęcia tutaj

I znowu można to zrobić dość łatwo również za pomocą UIStackViews na iOS9.

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

Zauważ, że jest to dokładnie to samo podejście, co powyżej. Dodaje cztery widoki kontenerów, które są równo wypełnione, a widok jest dodawany do każdego widoku stosu, który jest wyrównany do środka. Ale ta wersja UIStackView zmniejsza część kodu i wygląda ładnie.

Sandeep
źródło
1

Inne podejście może polegać na tym, że górne i dolne etykiety mają ograniczenia względem widoku odpowiednio górnego i dolnego, a środkowy widok ma ograniczenia górne i dolne odpowiednio względem pierwszego i trzeciego widoku.

Zauważ, że masz większą kontrolę nad ograniczeniami, niż mogłoby się to wydawać, przeciągając widoki blisko siebie, aż pojawią się linie przerywane - wskazują one ograniczenia między dwoma obiektami, które zostaną utworzone zamiast między obiektem a widokiem.

W takim przypadku należy zmienić ograniczenia na „Większe lub równe” pożądanej wartości, zamiast „równe”, aby umożliwić zmianę rozmiaru. Nie jestem pewien, czy to zrobi dokładnie to, co chcesz.

Colin
źródło
1

Bardzo prosty sposób na rozwiązanie tego w InterfaceBuilder:

Ustaw wyśrodkowaną etykietę (label2) na „Poziomy środek w pojemniku” i „Pionowy środek w pojemniku”

Wybierz wyśrodkowaną etykietę i górną etykietę (etykieta1 + etykieta2) i dodaj DWIE wiązanie dla Odstępu pionowego. Jeden z większym niż lub równy minimalnemu odstępowi. Jeden z mniejszym lub równym maksymalnym odstępem.

To samo dotyczy środkowej etykiety i dolnej etykiety (label2 + label3).

Dodatkowo możesz dodać dwa ograniczenia do etykiety 1 - Górna przestrzeń do SuperView i dwa ograniczenia do etykiety 2 - Dolna przestrzeń do SuperView.

W rezultacie wszystkie 4 odstępy zmienią jednakowo swoje rozmiary.

sust86
źródło
2
Przekonałem się, że działa to tylko podczas zmiany rozmiaru superwizji, dzięki czemu odstępy przyjmują dokładnie wartości minimalną lub maksymalną. Kiedy zmienisz rozmiar na wartość gdzieś pomiędzy, widoki podrzędne nie rozkładają się równomiernie.
Dorian Roy
1

Zrobiłem funkcję, która może pomóc. Ten przykład użycia:

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

spowoduje, że dystrybucja pionowa (przepraszam, nie mam 10 reputacji do osadzania zdjęć). A jeśli zmienisz oś i niektóre wartości marginesów:

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

Otrzymasz ten rozkład poziomy .

Jestem początkującym w iOS, ale voila!

EvenDistribution.h

@interface NSLayoutConstraint (EvenDistribution)

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

EvenDistribution.m

#import "EvenDistribution.h"

@implementation NSLayoutConstraint (EvenDistribution)

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

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



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

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

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

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


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

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

    return constraints;

}

@end
półcień
źródło
1

Tak, można to zrobić wyłącznie w kreatorze interfejsów i bez pisania kodu - jedynym zastrzeżeniem jest zmiana rozmiaru etykiety zamiast dystrybucji białych znaków. W takim przypadku wyrównaj X i Y etykiety 2 z widokiem superwizyjnym, aby był on ustawiony na środku. Następnie ustaw pionową przestrzeń etykiety 1 w widoku podglądu i etykietę 2 zgodnie ze standardem, powtórz dla etykiety 3. Po ustawieniu etykiety 2 najłatwiejszym sposobem ustawienia etykiety 1 i 3 jest zmiana jej rozmiaru, dopóki się nie zatrzasną.

Oto wyświetlacz poziomy, zauważ, że pionowa przestrzeń między etykietą 1 a 2 jest ustawiona na standard:wyświetlacz poziomy

A oto wersja portretowa:wprowadź opis zdjęcia tutaj

Zdaję sobie sprawę, że nie są absolutnie w 100% równe odstępy między liniami bazowymi ze względu na różnicę między standardową przestrzenią między etykietami a standardową przestrzenią do podglądu. Jeśli Ci to przeszkadza, ustaw rozmiar na 0 zamiast standardowego

James
źródło
1

Ustawiam wartość szerokości tylko dla pierwszego elementu (> = szerokość) i minimalną odległość między każdym elementem (> = odległość). Następnie używam Ctrl, aby przeciągnąć drugi, trzeci ... element na pierwszy, aby połączyć zależności między elementami.

wprowadź opis zdjęcia tutaj

Vladimír Slavík
źródło
1

Późno na imprezę, ale mam działające rozwiązanie do tworzenia menu poziomo z odstępami. Można to łatwo zrobić za pomocą ==NSLayoutConstraint

const float MENU_HEIGHT = 40;

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

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

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

    [format appendString: @"|"];

    NSArray * constraints = [NSLayoutConstraint constraintsWithVisualFormat: (NSString *) format
                                                                               options: 0
                                                                               metrics: nil
                                                                                 views: (NSDictionary *) views];
    [backgroundView addConstraints: constraints];
    return backgroundView;
}
Tsuz
źródło
0

Android ma metodę łączenia ze sobą widoków w układzie opartym na ograniczeniach, który chciałem naśladować. Wyszukiwania mnie tu przywiodły, ale żadna z odpowiedzi nie zadziałała. Nie chciałem używać StackViews, ponieważ powodują one więcej żalu w dół niż oszczędzają z góry. W końcu stworzyłem rozwiązanie, które korzystało z UILayoutGuides umieszczonych między widokami. Kontrolowanie ich szerokości pozwala na różne typy dystrybucji, style łańcucha w języku Android. Funkcja przyjmuje wiodącą i końcową kotwicę zamiast widoku rodzica. Pozwala to na umieszczenie łańcucha między dwoma dowolnymi widokami zamiast rozprowadzania go w widoku nadrzędnym. Wykorzystuje to, UILayoutGuideco jest dostępne tylko w iOS 9+, ale nie powinno to już stanowić problemu.

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

public extension NSLayoutConstraint {

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

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

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

    return constraints
}
Sam Corder
źródło
0

Chciałem wyrównać w poziomie 5 zdjęć, więc skończyłem na odpowiedzi Mete z niewielką różnicą.

Pierwszy obraz zostanie wyśrodkowany poziomo w pojemniku równym 0 i mnożnikowi 1: 5:

pierwszy obraz

Drugi obraz zostanie wyśrodkowany poziomo w pojemniku równym 0 i mnożnikowi 3: 5: drugi obraz

I tak w przypadku pozostałych zdjęć. Na przykład piąty (i ostatni) obraz zostanie wyśrodkowany poziomo w kontenerze równym 0 i mnożnikowi 9: 5: ostatni obraz

Jak wyjaśnił Mete, kolejność wynosi 1, 3, 5, 7, 9 itd. Pozycje są zgodne z tą samą logiką: pierwsza pozycja to 1, następnie spacja, następnie następna pozycja 3 i tak dalej.

Adriana Pineda
źródło
-1

Dlaczego po prostu nie stworzysz tableView i dokonasz isScrollEnabled = false

Josh O'Connor
źródło
Nie rozumiem opinii negatywnej, moje rozwiązanie myśli nieszablonowo. Po co męczyć się z tworzeniem listy z automatycznym układem, gdy kakao daje listę w UITableView?
Josh O'Connor,