Spazio uniformemente più viste all'interno di una vista contenitore


279

Il layout automatico mi sta rendendo la vita difficile. In teoria, sarebbe stato molto utile quando sono passato, ma mi sembra di combatterlo tutto il tempo.

Ho realizzato un progetto demo per cercare aiuto. Qualcuno sa come aumentare o diminuire uniformemente gli spazi tra le viste ogni volta che la vista viene ridimensionata?

Ecco tre etichette (spaziate manualmente in senso verticale):

image1

Quello che voglio è che ridimensionino uniformemente la loro spaziatura (non la dimensione della vista) quando giro. Per impostazione predefinita, le viste superiore e inferiore si inclinano verso il centro:

image2


forse impostare un vincolo da un'etichetta all'altra, e impostare una relazione "maggiore o uguale" sarà utile
BergP

a livello di codice, ad esempio, utilizzando questo metodo di NSLayoutConstraint: + (id) limitintWithItem: (id) attributo view1: (NSLayoutAttribute) attr1 relatedBy: (NSLayoutRelation) relazione a Oggetto: (id) view2 attributo: (NSLayoutAttribute) attr2 moltiplicatore: (CGFloat) moltiplicatore costante: (CGFloat) c
BergP

Ho aperto una sostituzione UITabBar generica che generalizza questo approccio utilizzando Auto Layout. È possibile controllare i test per vedere come posso generare i miei vincoli di layout automatico. GGTabBar
Goles,

Risposte:


211

Quindi il mio approccio ti consente di farlo nel generatore di interfacce. Quello che fai è creare "viste spaziatori" che hai impostato per abbinare le altezze allo stesso modo. Quindi aggiungi i vincoli superiore e inferiore alle etichette (vedi screenshot).

inserisci qui la descrizione dell'immagine

Più specificamente, ho un vincolo superiore su "Vista spaziatore 1" per la supervisione con un vincolo di altezza con priorità inferiore a 1000 e con Altezza uguale a tutte le altre "viste spaziatore". 'Spacer View 4' ha un vincolo di spazio inferiore per la superview. Ogni etichetta ha i rispettivi vincoli superiore e inferiore alle "viste spaziatore" più vicine.

Nota: assicurarsi di NON avere vincoli di spazio superiore / inferiore aggiuntivi sulle etichette per la supervisione; solo quelli delle "viste spaziali". Ciò sarà soddisfacente poiché i vincoli superiore e inferiore sono rispettivamente su 'Space View 1' e 'Spacer View 4'.

Duh 1: ho duplicato la mia vista e l'ho semplicemente messa in modalità orizzontale in modo da poter vedere che funzionava.

Duh 2: Le "viste spaziatore" avrebbero potuto essere trasparenti.

Duh 3: questo approccio potrebbe essere applicato in senso orizzontale.


10
Questo funziona In effetti le viste spaziatore possono essere nascoste e produrranno comunque il layout corretto.
paulmelnikow,

Questo è stato super utile. Come è stato il commento su come contrassegnare le viste del distanziatore come nascoste. Quindi puoi ancora vederli nello storyboard / xib, ma non vengono visualizzati nella tua app. Molto bella.
shulmey,

L'ho quasi funzionato dopo diverse ore di ripetuti tentativi. Sfortunatamente Xcode continua a cancellare i miei vincoli spazio-pulsante-spazio-inferiore, spezzando la catena tra pulsanti e distanziatori e sostituendoli con vincoli arbitrari spazio-super-superview che sono inutili.
Desty,

Lo stesso approccio funziona per me nel layout orizzontale: ho viste a larghezza fissa separate da viste distanziali di uguale larghezza. È fantastico che non ho bisogno di mescolarlo con nessun codice. Grazie!
Kamil Nomtek.com,

2
Non è necessario utilizzare distanziatori. Vedi la mia risposta qui sotto.
smileBot,

316

GUARDA, NESSUN DISTANZIALE!

Sulla base dei suggerimenti nella sezione commenti della mia risposta originale, in particolare i suggerimenti utili di @ Rivera, ho semplificato la mia risposta originale.

Sto usando gif per illustrare quanto sia semplice. Spero che le gif ti siano utili. Nel caso in cui tu abbia un problema con le gif, ho incluso la vecchia risposta qui sotto con semplici schermate.

Istruzioni:

1) Aggiungi i tuoi pulsanti o etichette. Sto usando 3 pulsanti.

2) Aggiungi un vincolo centrale x da ciascun pulsante alla superview:

inserisci qui la descrizione dell'immagine

3) Aggiungi un vincolo da ciascun pulsante al vincolo di layout inferiore:

inserisci qui la descrizione dell'immagine

4) Regola il vincolo aggiunto nel precedente punto 3 come segue:

a) selezionare il vincolo, b) rimuovere la costante (impostare su 0), c) modificare il moltiplicatore come segue: prendere il numero di pulsanti + 1, e partendo dall'alto, impostare il moltiplicatore come buttonCountPlus1: 1 , quindi buttonCountPlus1 : 2 e infine buttonCountPlus1: 3 . (Spiego da dove ho preso questa formula nella vecchia risposta qui sotto, se sei interessato).

inserisci qui la descrizione dell'immagine

5) Ecco una demo in esecuzione!

inserisci qui la descrizione dell'immagine

Nota: se i pulsanti hanno altezze maggiori, sarà necessario compensare questo valore costante poiché il vincolo si trova nella parte inferiore del pulsante.


Vecchia risposta


Nonostante ciò che dicono i documenti di Apple e l'eccellente libro di Erica Sadun ( Disposizione automatica demistificata ), è possibile spaziare uniformemente le viste senza distanziatori. Questo è molto semplice da eseguire in IB e nel codice per qualsiasi numero di elementi che si desidera spaziare uniformemente. Tutto ciò che serve è una formula matematica chiamata "formula di sezione". È più semplice da fare che da spiegare. Farò del mio meglio dimostrandolo in IB, ma è altrettanto facile da fare nel codice.

Nell'esempio in questione, lo faresti

1) iniziare impostando ciascuna etichetta per avere un vincolo centrale. Questo è molto semplice da fare. Basta controllare il trascinamento da ciascuna etichetta verso il basso.

2) Tieni premuto il tasto MAIUSC, dato che potresti anche aggiungere l'altro vincolo che useremo, vale a dire la "guida dal basso allo spazio inferiore".

3) Selezionare la "guida del layout dallo spazio inferiore al fondo" e "centrare orizzontalmente nel contenitore". Fallo per tutte e 3 le etichette.

Tenere premuto MAIUSC per aggiungere questi due vincoli per ciascuna etichetta

Fondamentalmente, se prendiamo l'etichetta di cui desideriamo determinare le coordinate e dividerla per il numero totale di etichette più 1, allora abbiamo un numero che possiamo aggiungere a IB per ottenere la posizione dinamica. Sto semplificando la formula, ma potresti usarla per impostare la spaziatura orizzontale o sia verticale che orizzontale allo stesso tempo. È super potente!

Ecco i nostri moltiplicatori.

Etichetta1 = 1/4 = .25,

Etichetta2 = 2/4 = .5,

Etichetta3 = 3/4 = .75

(Modifica: @Rivera ha commentato che puoi semplicemente usare i rapporti direttamente nel campo del moltiplicatore e xCode con fare i calcoli!)

4) Quindi, selezioniamo Label1 e selezioniamo il vincolo inferiore. Come questo: inserisci qui la descrizione dell'immagine

5) Seleziona il "Secondo oggetto" nella finestra di ispezione degli attributi.

6) Dal menu a discesa selezionare "Inverti primo e secondo elemento".

7) Azzerare la costante e il valore hC di qualsiasi valore. (Puoi aggiungere un offset qui se ne hai bisogno).

8) Questa è la parte critica: nel campo moltiplicatore aggiungi il nostro primo moltiplicatore 0,25.

9) Mentre ci sei, imposta il primo "Primo elemento" su "CentraY" poiché vogliamo centrarlo sul centro y dell'etichetta. Ecco come dovrebbe apparire tutto ciò.

inserisci qui la descrizione dell'immagine

10) Ripetere questo processo per ciascuna etichetta e collegare il moltiplicatore pertinente: 0,5 per Label2 e 0,75 per Label3. Ecco il prodotto finale in tutti gli orientamenti con tutti i dispositivi compatti! Super semplice. Ho cercato molte soluzioni che coinvolgono risme di codice e distanziatori. Questa è di gran lunga la migliore soluzione che ho visto sul problema.

Aggiornamento: @kraftydevil aggiunge che la guida al layout inferiore appare solo negli storyboard, non negli xibs. Usa 'Bottom Space to Container' in xibs. Buona pesca!

inserisci qui la descrizione dell'immagine


2
Ci sto ancora lottando. Penso che tu debba usare Xcode 6 perché non c'è alcuna opzione 'wC hAny' nei vincoli Xcode 5. Quando seguo le tue istruzioni, tutte le visualizzazioni secondarie rimangono bloccate in alto al centro.
kraftydevil,

4
@kraftydevil AFAIK La guida al layout inferiore viene visualizzata solo negli storyboard, non negli xib. Usa 'Bottom Space to Container' in xibs.
Timur Kuchkarov,

6
Oh, vale anche la pena notare che IB in Xcode 6 supporta i moltiplicatori di rapporto, quindi puoi inserire cose come "1: 4", "2: 5", "3: 4", ecc.
Benjohn,

3
Cercando di capire come farlo in orizzontale - come il primo TextView 1/3 del modo da sinistra, e un secondo TextView 2/3 (resto del percorso) ... ... (EDIT) capito - fai il primo 1/3 (uno a sinistra) Trailing-Space to Right Margin, reverse, zero, assegnato come prima ...
kfmfe04

3
Per la spaziatura orizzontale, è sufficiente impostare i vincoli finali sulla superview per ogni sottoview, quindi impostare il moltiplicatore del rapporto, ad es. 5: 1, 5: 2, 5: 3, ecc., Spostandosi da sinistra a destra.
user3344977

98

Soluzione molto rapida per Interface Builder:

Per un numero qualsiasi di viste da distribuire uniformemente all'interno di una superview, è sufficiente assegnare a ciascuna un vincolo "Allinea Center X alla superview" per il layout orizzontale o "Allinea Superview Center Y" per il layout verticale e impostare il Moltiplicatore su N:p( NOTA: alcuni ho avuto più fortuna con p:N- vedi sotto )

dove

N = total number of views, e

p = position of the view including spaces

La prima posizione è 1, quindi uno spazio, rendendo la posizione successiva 3, quindi p diventa una serie [1,3,5,7,9, ...]. Funziona con qualsiasi numero di visualizzazioni.

Quindi, se hai 3 viste per spaziare, sembra così:

Illustrazione di come distribuire uniformemente le visualizzazioni in IB

EDIT Nota: la scelta N:po p:Ndipende dall'ordine di relazione del vincolo di allineamento. Se "Primo elemento" è Superview.Center, è possibile utilizzare p:N, mentre se Superview.Center è "Secondo elemento", è possibile utilizzare N:p. In caso di dubbi, prova entrambi ... :-)


Questa soluzione non rifletterà il design se passi a un'altra lingua
wod

@wod Cosa intendi? Passa a un'altra lingua? E cosa si rompe esattamente?
Mete,

4
cosa succede se gli elementi non hanno le stesse dimensioni? questo modello non funzionerà correttamente, giusto?
ucangetit,

2
Non capisco come questa soluzione sia corretta? Lo spazio tra il bordo dello schermo e gli elementi esterni è diverso dallo spazio tra gli elementi esterni e l'elemento centrale?
myles,

6
Questo non è stato per me "così com'è" Ho dovuto invertire i moltiplicatori (ad esempio il 3: 1 dal primo elemento convertito in 1: 3). Basta buttarlo là fuori se aiuta qualcuno.
Ces

70

A partire da iOS 9, Apple ha reso tutto molto semplice con (tanto atteso) UIStackView. Basta selezionare le viste che si desidera contenere in Interface Builder e scegliere Editor -> Incorpora in -> Vista pila. Imposta i vincoli larghezza / altezza / margine appropriati per la vista pila e assicurati di impostare la proprietà Distribuzione su "Spaziatura uguale":

Naturalmente, se devi supportare iOS 8 o versioni precedenti, dovrai scegliere una delle altre opzioni.


Sì, è triste che questo non abbia retro compatibilità, quindi fino a quando l'app non avrà bisogno del supporto per IOS8 la risposta @Mete è la migliore per ora.
ZiggyST,

51

Sono stato su un giro sulle montagne russe di amorevole autolayout e odio. La chiave per amarlo sembra essere accettare quanto segue:

  1. La modifica di Interface Builder e la "utile" creazione automatica di vincoli sono quasi inutili per tutti, tranne il caso più banale
  2. La creazione di categorie per semplificare le operazioni comuni è un salvavita in quanto il codice è così ripetitivo e dettagliato.

Detto questo, ciò che stai tentando non è semplice e sarebbe difficile da realizzare nel generatore di interfacce. È abbastanza semplice da fare nel codice. Questo codice, in viewDidLoad, crea e posiziona tre etichette come le stai chiedendo:

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

Come ho già detto, questo codice è molto semplificato con un paio di metodi di categoria UIView, ma per chiarezza l'ho fatto molto tempo qui.

La categoria è qui per coloro che sono interessati e ha un metodo per spaziare uniformemente una matrice di viste lungo un asse particolare.


Signore, salvavita. Fino ad ora non ho capito il primo. Interface Builder mi sta facendo impazzire, ma ho pensato che fosse in grado di fare tutte le stesse cose. Preferisco usare il codice comunque, quindi è perfetto, e passerò direttamente ai metodi di categoria.
nothappybob,

Sebbene decisamente piuttosto vicino, questo non risolve del tutto il problema esatto (mantenendo le dimensioni della vista uguali con una spaziatura uniforme tra le sottoview). Questa soluzione è la raffinata risposta del caso generale.
smileyborg,

21

La maggior parte di queste soluzioni dipende dal fatto che esiste un numero dispari di elementi in modo da poter prendere l'elemento centrale e centrarlo. Cosa succede se si dispone di un numero pari di articoli che si desidera distribuire uniformemente? Ecco una soluzione più generale. Questa categoria distribuirà uniformemente un numero qualsiasi di elementi lungo l'asse verticale o orizzontale.

Esempio di utilizzo per distribuire verticalmente 4 etichette all'interno della loro superview:

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

NSLayoutConstraint + EvenDistribution.h

@interface NSLayoutConstraint (EvenDistribution)

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

@end

NSLayoutConstraint + EvenDistribution.m

@implementation NSLayoutConstraint (EvenDistribution)

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

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

    return constraints;
}

@end

1
Ha funzionato rimuovendo tutti i vincoli e subito dopo, aggiungendo i vincoli Larghezza e Altezza a quegli oggetti, quindi chiamando i vincoli del metodo
For EvenDistributionOfItems

@carlos_ms Mi piacerebbe vedere il tuo codice, perché quando provo con un numero pari di elementi questo codice funziona perfettamente quando cambio orientamento del dispositivo. Sei sicuro di non avere inavvertitamente altri vincoli (come i vincoli di ridimensionamento automatico) che causano un conflitto?
Ben Dolman,

Ecco un esempio della creazione di 4 etichette e della loro spaziatura uniforme lungo l'asse verticale: gist.github.com/bdolman/5379465
Ben Dolman

20

Il modo più semplice e corretto è utilizzare Stack Views.

  1. Aggiungi le tue etichette / viste alla vista Stack :

inserisci qui la descrizione dell'immagine

  1. Seleziona la vista Stack e imposta la distribuzione su Spaziatura uguale :

inserisci qui la descrizione dell'immagine

  1. Aggiungi la spaziatura ai vincoli vicini più vicini alla vista Stack e aggiorna i frame:

inserisci qui la descrizione dell'immagine

  1. Aggiungi vincoli di altezza a tutte le etichette (opzionale). Necessario solo per le viste che non hanno dimensioni intrinseche). Le etichette per esempio non hanno bisogno qui di vincoli di altezza e devono solo impostare numberOfLines = 3 o 0 per esempio.

inserisci qui la descrizione dell'immagine

  1. Goditi l'anteprima:

inserisci qui la descrizione dell'immagine


2
Questo è> = iOS 9.0 solo quindi controlla la tua destinazione di distribuzione prima di iniziare l'implementazione :) È bello vedere che questo è stato aggiunto in!
DivideByZer0

1
2018 ora, usa la vista pila
Jingshao Chen,

17

Scopri la libreria open source PureLayout . Offre alcuni metodi API per la distribuzione di viste, comprese le varianti in cui è fissata la spaziatura tra ciascuna vista (le dimensioni della vista variano in base alle esigenze) e in cui è fissata la dimensione di ciascuna vista (la spaziatura tra le viste varia in base alle esigenze). Si noti che tutti questi sono realizzati senza l'uso di "viste spaziatore".

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

// ...

Dal momento che è tutto open source, se sei interessato a vedere come questo è ottenuto senza viste spaziali, dai un'occhiata all'implementazione. (Dipende dall'utilizzo di entrambi i vincoli constant e multiplier per i vincoli.)


Bel lavoro! Anche se probabilmente dovresti rinominare il repository in modo da renderlo disponibile tramite i cocoapodi.
jrturton,

9

Sto riscontrando un problema simile e ho scoperto questo post. Tuttavia, nessuna delle risposte attualmente fornite risolve il problema nel modo desiderato. Non fanno la spaziatura allo stesso modo, ma piuttosto distribuiscono il centro delle etichette allo stesso modo. È importante capire che questo non è lo stesso. Ho costruito un piccolo diagramma per illustrare questo.

Visualizza illustrazione di spaziatura

Ci sono 3 viste, tutte alte 20pt. L'uso di uno qualsiasi dei metodi suggeriti distribuisce equamente i centri delle viste e fornisce il layout illustrato. Si noti che il centro y delle viste è spaziato equamente. Tuttavia, la spaziatura tra superview e top view è 15pt, mentre la spaziatura tra le subview è solo 5pt. Per avere una vista equidistante, entrambe dovrebbero essere 10pt, cioè tutte le frecce blu dovrebbero essere 10pt.

Tuttavia, non ho ancora trovato una buona soluzione generica. Attualmente la mia idea migliore è quella di inserire "viste di spaziatura" tra le viste secondarie e impostare le altezze delle viste di spaziatura in modo che siano uguali.


1
Ho usato la soluzione di viste distanziate nascoste, che funziona bene.
paulmelnikow,

8

Sono stato in grado di risolverlo interamente in IB:

  1. Restringi vincoli per allineare il centro Y di ciascuna delle tue sottoview al bordo inferiore della superview.
  2. Impostare il moltiplicatore di ciascuno di questi vincoli su 1 / 2n, 3 / 2n, 5 / 2n, ..., n-1 / 2n dove n è il numero di visualizzazioni secondarie che si stanno distribuendo.

Pertanto, se si dispone di tre etichette, impostare i moltiplicatori su ciascuno di questi vincoli su 0,1666667, 0,5, 0,833333.


1
In realtà ho dovuto fare 1 / n 3 / n 5 / n 7 / n fino a (2n-1) / n
Hamzah Malik,

5

Ho trovato un metodo perfetto e semplice. Il layout automatico non ti consente di ridimensionare gli spazi allo stesso modo, ma ti consente di ridimensionare le viste allo stesso modo. Basta inserire alcune viste invisibili tra i campi e dire al layout automatico di mantenerli delle stesse dimensioni. Funziona perfettamente!

XIB iniziale

XIB allungato

Una cosa degna di nota però; quando ho ridotto le dimensioni nel designer dell'interfaccia, a volte mi sono confuso e ho lasciato un'etichetta dov'era, e si è verificato un conflitto se le dimensioni sono state modificate di un importo dispari. Altrimenti ha funzionato perfettamente.

modifica: ho scoperto che il conflitto è diventato un problema. Per questo motivo, ho preso uno dei vincoli di spaziatura, l'ho eliminato e lo ho sostituito con due vincoli, uno maggiore di o uguale e uno di minore o uguale. Entrambi avevano le stesse dimensioni e avevano una priorità molto più bassa rispetto agli altri vincoli. Il risultato non fu ulteriore conflitto.


4

Basandosi sulla risposta di Ben Dolman, questo distribuisce le viste in modo più uniforme (con imbottitura, ecc.):

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

Con le etichette funziona bene almeno:

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

Se il primo ha la stessa larghezza del secondo, il secondo il terzo e il terzo il primo, avranno tutti la stessa larghezza ... Puoi farlo sia in orizzontale (H) che in verticale (V).


3

versione rapida 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)

1
Elaborare come questo codice risponde alle domande aiuterebbe i futuri visitatori.
JAL

2

So che è passato un po 'di tempo dalla prima risposta, ma ho riscontrato lo stesso problema e voglio condividere la mia soluzione. Per le generazioni a venire ...

Ho impostato le mie visualizzazioni su 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];

}

Quindi, su updateViewConstrains, prima elimino tutti i vincoli, quindi creo il dizionario delle viste e quindi calcolo lo spazio da utilizzare tra le viste. Dopodiché, utilizzo il formato linguaggio visivo per impostare i vincoli:

- (void)updateViewConstraints {


    [super updateViewConstraints];

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

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

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

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



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


    [self.view addConstraints:constraints];



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



}

La cosa buona di questo metodo è che devi fare pochissima matematica. Non sto dicendo che questa sia la soluzione perfetta, ma lavoro per il layout che stavo cercando di ottenere.

Spero possa essere d'aiuto.


2

Ecco una soluzione che centrerà verticalmente qualsiasi numero di visualizzazioni secondarie, anche se hanno dimensioni uniche. Quello che vuoi fare è creare un container di livello intermedio, centrarlo nella superview, quindi mettere tutte le subview nel container e disporle una rispetto all'altra. Ma soprattutto devi anche vincolarli in alto e inferiore del contenitore, in modo che il contenitore possa essere correttamente dimensionato e centrato nella superview. Immaginando l'altezza corretta dalle sue viste secondarie, il contenitore può essere centrato verticalmente.

In questo esempio, selfè la superview in cui si stanno centrando tutte le subview.

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

Questa è la soluzione migliore quando si utilizzano viste di dimensioni diverse che devono essere centrate.
noobsmcgoobs,

2

Ho appena risolto il mio problema utilizzando la funzione moltiplicatore. Non sono sicuro che funzioni per tutti i casi, ma per me ha funzionato perfettamente. Sono su Xcode 6.3 FYI.

Quello che ho finito per fare è stato:

1) Innanzitutto posizionando i miei pulsanti su uno schermo con larghezza di 320 px distribuito nel modo in cui volevo che fosse visualizzato su un dispositivo da 320 px.

passaggio 1: posizionamento dei pulsanti

2) Quindi ho aggiunto un vincolo Space principale alla superview su tutti i miei pulsanti.

passaggio 2: aggiungere vincoli di spazio iniziali

3) Quindi ho modificato le proprietà dello spazio iniziale in modo che la costante fosse 0 e il moltiplicatore sia l'offset x diviso per la larghezza dello schermo (ad esempio il mio primo pulsante era 8 pixel dal bordo sinistro, quindi ho impostato il mio moltiplicatore su 8/320)

4) Quindi il passo importante qui è cambiare il secondo elemento nella relazione di vincolo in modo che sia la superview. Trailing invece di superview.leading. Questo è fondamentale perché superview.Leading è 0 e il trailing nel mio caso è 320, quindi 8/320 è 8 px su un dispositivo 320px, quindi quando la larghezza della superview cambia in 640 o altro, le viste si muovono tutte in un rapporto rispetto alla larghezza delle dimensioni dello schermo di 320 px. La matematica qui è molto più semplice da capire.

passaggi 3 e 4: cambia il moltiplicatore in xPos / screenWidth e imposta il secondo elemento su .Trailing


2

Molte risposte non sono corrette, ma ottengono molti conteggi. Qui scrivo solo una soluzione a livello di codice, le tre viste sono allineate orizzontalmente, senza usare le viste spaziatore , ma funzionano solo quando si conoscono le larghezze delle etichette quando vengono utilizzate nello storyboard .

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

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

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

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

2

Ecco un'altra risposta. Stavo rispondendo a una domanda simile e ho visto il link riferito a questa domanda. Non ho visto nessuna risposta simile alla mia. Quindi, ho pensato di scriverlo qui.

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

inserisci qui la descrizione dell'immagine

E ancora, questo può essere fatto abbastanza facilmente anche con iOS9 UIStackViews.

class ViewController: UIViewController {

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

    var constraints: [NSLayoutConstraint] = []

    func setupViews() {

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

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

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


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

Si noti che è esattamente lo stesso approccio di cui sopra. Aggiunge quattro viste contenitore riempite equamente e una vista viene aggiunta a ciascuna vista pila allineata al centro. Ma questa versione di UIStackView riduce un po 'di codice e sembra carina.


1

Un altro approccio potrebbe essere quello di avere rispettivamente le etichette superiore e inferiore con vincoli relativi alla vista superiore e inferiore e che la vista centrale abbia vincoli superiore e inferiore rispettivamente rispetto alla prima e alla terza vista.

Tieni presente che hai un maggiore controllo sui vincoli di quanto possa sembrare trascinando le viste vicine l'una all'altra fino a quando vengono visualizzate le linee tratteggiate guida: queste indicano vincoli tra i due oggetti che si formeranno anziché tra l'oggetto e la superview.

In questo caso, si desidera modificare i vincoli in modo che il valore desiderato sia "Maggiore o uguale a", anziché "uguale a" per consentire il ridimensionamento. Non sono sicuro se questo farà esattamente quello che vuoi.


1

Modo molto semplice per risolvere questo problema in InterfaceBuilder:

Impostare l'etichetta centrata (etichetta2) su "Centro orizzontale nel contenitore" e "Centro verticale nel contenitore"

Seleziona l'etichetta centrata e l'etichetta superiore (etichetta1 + etichetta2) e aggiungi DUE vincoli per la spaziatura verticale. Uno con maggiore o uguale alla spaziatura minima. Uno con minore o uguale alla spaziatura massima.

Lo stesso per l'etichetta centrata e l'etichetta inferiore (etichetta2 + etichetta3).

Inoltre, è possibile aggiungere anche due vincoli a label1 - Spazio superiore a SuperView e due vincoli a label2 - Spazio inferiore a SuperView.

Il risultato sarà che tutte e 4 le spaziature cambieranno le loro dimensioni allo stesso modo.


2
Ho scoperto che questo funziona solo quando ridimensioni la superview in modo che la spaziatura prenda esattamente i valori minimo o massimo. Quando lo ridimensioni a un valore intermedio, le visualizzazioni secondarie non vengono distribuite uniformemente.
Dorian Roy,

1

Ho creato una funzione che potrebbe aiutare. Questo esempio di utilizzo:

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

causerà quella distribuzione verticale (scusate non ho la reputazione 10 di incorporare immagini). E se modifichi l'asse e alcuni valori di margine:

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

Otterrai quella distribuzione orizzontale .

Sono un principiante in iOS ma 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

Sì, puoi farlo solo nel builder di interfacce e senza scrivere codice: l'unico avvertimento è che stai ridimensionando l'etichetta invece di distribuire spazi bianchi. In questo caso, allinea X e Y di Label 2 alla superview in modo che sia fissata al centro. Quindi imposta lo spazio verticale dell'etichetta 1 sulla superview e sull'etichetta 2 sullo standard, ripeti per l'etichetta 3. Dopo aver impostato l'etichetta 2, il modo più semplice per impostare l'etichetta 1 e 3 è ridimensionarli fino allo scatto.

Ecco il display orizzontale, si noti che lo spazio verticale tra l'etichetta 1 e 2 è impostato su standard:display orizzontale

Ed ecco la versione verticale:inserisci qui la descrizione dell'immagine

Mi rendo conto che non sono assolutamente equamente distanziati al 100% tra le linee di base a causa della differenza tra lo spazio standard tra le etichette e lo spazio standard rispetto alla superview. Se questo ti dà fastidio, imposta la dimensione su 0 anziché su standard


1

Ho impostato un valore di larghezza solo per il primo elemento (> = una larghezza) e una distanza minima tra ciascun elemento (> = una distanza). Quindi uso Ctrl per trascinare il secondo, il terzo ... elemento sul primo per concatenare le dipendenze tra gli elementi.

inserisci qui la descrizione dell'immagine


1

In ritardo alla festa, ma ho una soluzione funzionante per la creazione di un menu in orizzontale con spaziatura. Può essere fatto facilmente usando ==in 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;
}

0

Android ha un metodo per concatenare le viste insieme nel suo sistema di layout basato sui vincoli che volevo imitare. Le ricerche mi hanno portato qui, ma nessuna delle risposte ha funzionato. Non volevo usare StackViews perché tendono a causarmi più dolore di quanto non risparmino in anticipo. Ho finito per creare una soluzione che utilizzava UILayoutGuides posizionati tra le viste. Il controllo della loro larghezza consente diversi tipi di distribuzioni, stili di catena nel linguaggio Android. La funzione accetta un ancoraggio iniziale e finale anziché una vista principale. Ciò consente di posizionare la catena tra due viste arbitrarie anziché distribuirle all'interno della vista padre. Usa UILayoutGuideche è disponibile solo in iOS 9+ ma che non dovrebbe più essere un problema.

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

Volevo allineare orizzontalmente 5 immagini, quindi ho finito per seguire la risposta di Mete con una piccola differenza.

La prima immagine verrà centrata orizzontalmente in un contenitore uguale a 0 e un moltiplicatore di 1: 5:

prima immagine

La seconda immagine verrà centrata orizzontalmente in un contenitore uguale a 0 e un moltiplicatore di 3: 5: seconda immagine

E così per il resto delle immagini. Ad esempio, la quinta (e ultima) immagine sarà centrata orizzontalmente in un contenitore uguale a 0 e un moltiplicatore di 9: 5: ultima immagine

Come spiegato Mete, l'ordine va 1, 3, 5, 7, 9, ecc. Le posizioni seguono la stessa logica: la prima posizione è 1, quindi lo spazio, quindi la posizione successiva 3 e così via.


-1

Perché non basta creare un tableView e creare isScrollEnabled = false


Non capisco il downvote, la mia soluzione è pensare fuori dagli schemi. Perché passare attraverso il mal di testa della creazione di un elenco con autolayout quando cacao ti dà un elenco in UITableView?
Josh O'Connor,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.