Espace uniformemente várias visualizações em uma exibição de contêiner

279

O Auto Layout está dificultando minha vida. Em teoria, seria realmente útil quando eu trocasse, mas pareço lutar contra isso o tempo todo.

Eu fiz um projeto de demonstração para tentar encontrar ajuda. Alguém sabe como aumentar ou diminuir uniformemente os espaços entre as visualizações sempre que a visualização é redimensionada?

Aqui estão três etiquetas (espaçadas manualmente na vertical, mesmo):

image1

O que eu quero é que eles redimensionem seu espaçamento (não o tamanho da visualização) uniformemente quando eu giro. Por padrão, as vistas superior e inferior se comprimem em direção ao centro:

image2

nothappybob
fonte
talvez definir restrições de um rótulo para outro e definir a relação "maior que ou igual" seja útil
BergP
programaticamente, por exemplo, com o uso deste método de NSLayoutConstraint: atributo + (id) constraintWithItem: (id) view1: (NSLayoutAttribute) attr1 relatedBy: (NSLayoutRelation) relação ao item: constante: (CGFloat) c
BergP
Abro uma substituição genérica de UITabBar que generaliza essa abordagem usando o Layout Automático. Você pode verificar os testes para ver como eu gero minhas restrições de layout automático. GGTabBar
Goles

Respostas:

211

Portanto, minha abordagem permite que você faça isso no construtor de interfaces. O que você faz é criar 'vistas espaçadoras' que você definiu para igualar as alturas igualmente. Em seguida, adicione restrições superiores e inferiores aos rótulos (veja a captura de tela).

insira a descrição da imagem aqui

Mais especificamente, tenho uma restrição superior em 'Spacer View 1' para supervisionar com uma restrição de altura de prioridade mais baixa que 1000 e com Height Equals para todas as outras 'visualizações de espaçador'. 'Spacer View 4' tem uma restrição de espaço inferior à superview. Cada etiqueta possui uma respectiva restrição superior e inferior às 'vistas espaçadoras' mais próximas.

Nota: verifique se NÃO há restrições adicionais de espaço superior / inferior em seus rótulos para a visualização. apenas aqueles para as 'vistas do espaço'. Isso será satisfatório, pois as restrições superior e inferior estão em 'Space View 1' e 'Spacer View 4', respectivamente.

Duh 1: dupliquei minha visualização e apenas a coloquei no modo paisagem para que você pudesse ver que funcionava.

Duh 2: As 'vistas espaçadoras' poderiam ter sido transparentes.

Duh 3: Essa abordagem pode ser aplicada horizontalmente.

Spencer Hall
fonte
10
Isso funciona. De fato, as vistas espaçadoras podem ser ocultadas e ainda produzirão o layout correto.
paulmelnikow
Isso foi super útil. Como foi o comentário sobre apenas marcar as vistas do espaçador como ocultas. Em seguida, você ainda pode vê-los no seu storyboard / xib, mas eles não aparecem no seu aplicativo. Muito agradável.
Shulmey
Eu consegui trabalhar quase depois de várias horas de tentativas repetidas. Infelizmente, o Xcode continua excluindo minhas restrições de espaço superior a botão e espaço inferior, quebrando a cadeia entre botões e espaçadores e substituindo-as por restrições arbitrárias de espaço superior a superview que são inúteis.
Destino 24/10
A mesma abordagem funciona para mim no layout horizontal - tenho vistas de largura fixa separadas por vistas de espaçador de largura igual. É ótimo que eu não precise misturá-lo com nenhum código. Obrigado!
Kamil Nomtek.com 15/02/14
2
Não há necessidade de usar espaçadores. Veja minha resposta abaixo.
precisa saber é o seguinte
316

OLHE, SEM ESPAÇADORES!

Com base nas sugestões na seção de comentários da minha resposta original, especialmente nas sugestões úteis do @ Rivera, simplifiquei minha resposta original.

Estou usando gifs para ilustrar como isso é simples. Espero que você ache os gifs úteis. Caso você tenha um problema com gifs, incluímos a resposta antiga abaixo com capturas de tela simples.

Instruções:

1) Adicione seus botões ou etiquetas. Estou usando 3 botões.

2) Adicione uma restrição de centro x de cada botão à superview:

insira a descrição da imagem aqui

3) Adicione uma restrição de cada botão à restrição de layout inferior:

insira a descrição da imagem aqui

4) Ajuste a restrição adicionada no item 3 acima, da seguinte maneira:

a) selecione a restrição, b) remova a constante (defina como 0), c) altere o multiplicador da seguinte forma: pegue o número de botões + 1 e, começando pelo topo, defina o multiplicador como buttonCountPlus1: 1 e, em seguida, buttonCountPlus1 : 2 e finalmente buttonCountPlus1: 3 . (Explico de onde obtive essa fórmula na resposta antiga abaixo, se você estiver interessado).

insira a descrição da imagem aqui

5) Aqui está uma demonstração em execução!

insira a descrição da imagem aqui

Nota: Se os botões tiverem alturas maiores, será necessário compensar isso no valor constante, pois a restrição está na parte inferior do botão.


Resposta antiga


Apesar do que dizem os documentos da Apple e o excelente livro de Erica Sadun ( Auto Layout Demystified ), é possível espaçar uniformemente as visualizações sem espaçadores. Isso é muito simples de fazer no IB e no código de qualquer número de elementos que você deseja espaçar uniformemente. Tudo o que você precisa é de uma fórmula matemática chamada "fórmula da seção". É mais simples de fazer do que explicar. Eu farei o meu melhor demonstrando isso no IB, mas é tão fácil de fazer no código.

No exemplo em questão, você faria

1) comece definindo cada etiqueta para ter uma restrição central. Isso é muito simples de fazer. Basta controlar o arrasto de cada etiqueta para o fundo.

2) Mantenha pressionada a tecla Shift, pois você também pode adicionar a outra restrição que usaremos, a saber, o "espaço inferior ao guia de layout inferior".

3) Selecione o "guia de layout do espaço inferior à inferior" e "centralize horizontalmente no recipiente". Faça isso para todos os três rótulos.

Mantenha pressionada a tecla Shift para adicionar essas duas restrições para cada etiqueta

Basicamente, se pegarmos o rótulo cuja coordenada queremos determinar e dividi-lo pelo número total de rótulos mais 1, então temos um número que podemos adicionar ao IB para obter o local dinâmico. Estou simplificando a fórmula, mas você pode usá-la para definir o espaçamento horizontal ou vertical e horizontal ao mesmo tempo. É super poderoso!

Aqui estão nossos multiplicadores.

Rótulo1 = 1/4 = 0,25,

Etiqueta2 = 2/4 = 0,5,

Rótulo3 = 3/4 = 0,75

(Edit: @Rivera comentou que você pode simplesmente usar as proporções diretamente no campo multiplicador e xCode com as contas!)

4) Então, vamos selecionar Label1 e selecione a restrição inferior. Como isso: insira a descrição da imagem aqui

5) Selecione o "Segundo item" no Inspetor de atributos.

6) No menu suspenso, selecione "Reverter primeiro e segundo itens".

7) Zere a constante e o valor wC hAny. (Você pode adicionar um deslocamento aqui, se necessário).

8) Esta é a parte crítica: no campo multiplicador, adicione nosso primeiro multiplicador 0,25.

9) Enquanto estiver nele, defina o "Primeiro item" no topo como "CentroY", pois queremos centralizá-lo no centro y da etiqueta. Aqui está como tudo isso deve parecer.

insira a descrição da imagem aqui

10) Repita esse processo para cada etiqueta e conecte o multiplicador relevante: 0,5 para Label2 e 0,75 para Label3. Aqui está o produto final em todas as orientações com todos os dispositivos compactos! Super simples. Estive procurando muitas soluções envolvendo resmas de código e espaçadores. Essa é de longe a melhor solução que eu já vi sobre o assunto.

Atualização: @kraftydevil acrescenta que o guia de layout inferior só aparece nos storyboards, não nos xibs. Use 'Bottom Space to Container' no xibs. Boa pegada!

insira a descrição da imagem aqui

smileBot
fonte
2
Eu ainda estou lutando com isso. Eu acho que você deve estar usando o Xcode 6 porque não há opção 'wC hAny' nas restrições do Xcode 5. Quando sigo suas instruções, todas as subvisões ficam presas no centro superior.
Kraftydevil
4
@kraftydevil O guia de layout inferior do AFAIK aparece apenas nos storyboards, não nos xibs. Use 'Bottom Space to Container' no xibs.
Timur Kuchkarov
6
Ah - também vale a pena notar que o IB no Xcode 6 suporta multiplicadores de proporção, para que você possa inserir coisas como "1: 4", "2: 5", "3: 4" etc.)
Benjohn
3
Tentando descobrir como fazer isso horizontalmente - como o primeiro TextView 1/3 do lado esquerdo, e um segundo TextView 2/3 (restante do caminho) ... ... (EDIT) conseguiu - faça o primeira 1/3 (um esquerdo) de espaços extras para margem direita, reverso, zero, atribuir como antes ...
kfmfe04
3
Para espaçamento horizontal, simplesmente defina as restrições à supervisão para cada subvisão e defina o multiplicador de proporção, por exemplo. 5: 1, 5: 2, 5: 3, etc. movendo-se da esquerda para a direita.
user3344977
98

Solução muito rápida para o Interface Builder:

Para que qualquer número de visualizações seja uniformemente espaçado em uma superview, basta atribuir a cada uma uma restrição "Alinhar centro X à superview" para o layout horizontal, ou "Alinhar superview Center Y para o layout vertical e defina o Multiplicador como N:p( NOTA: alguns tiveram melhor sorte com p:N- veja abaixo )

Onde

N = total number of viewse

p = position of the view including spaces

A primeira posição é 1, depois um espaço, fazendo a próxima posição 3, então p se torna uma série [1,3,5,7,9, ...]. Funciona para qualquer número de visualizações.

Portanto, se você tiver três visualizações para espaçar, fica assim:

Ilustração de como distribuir vistas uniformemente no IB

EDIT Nota: A escolha N:pou p:Ndepende da ordem da relação da sua restrição de alinhamento. Se "Primeiro Item" for Superview.Center, você poderá usar p:N, enquanto se Superview.Center for "Segundo Item", poderá usar N:p. Em caso de dúvida, tente as duas ... :-)

Mete
fonte
Esta solução não refletirá o design se você alternar para outro idioma
wod
@ wod Como assim? Mudar o que para outro idioma? E o que quebra exatamente?
Mete
4
e se os elementos não tiverem o mesmo tamanho? esse padrão não funcionará corretamente, certo?
ucangetit
2
Não entendo como esta solução está correta? O espaço entre a borda da tela e os elementos externos é diferente do espaço entre os elementos externos e o elemento central?
myles
6
Isso não aconteceu para mim "como está". Eu tive que reverter os multiplicadores (ej. O 3: 1 do primeiro item se converte em 1: 3). Apenas jogando isso lá fora, se ajudar alguém.
Ces
70

A partir do iOS 9, a Apple tornou isso muito fácil com o (aguardado) UIStackView. Basta selecionar as visualizações que você deseja conter no Interface Builder e escolha Editor -> Incorporar -> Visualização da pilha. Defina as restrições de largura / altura / margem apropriadas para a exibição da pilha e defina a propriedade Distribution como 'Espaçamento igual':

Obviamente, se você precisar oferecer suporte ao iOS 8 ou inferior, terá que escolher uma das outras opções.

Glorfindel
fonte
Sim, é triste que isso não tenha compatibilidade retroativa, portanto, até que seu aplicativo precise de suporte para a resposta IOS8 @Mete, é o melhor por enquanto.
ZiggyST
51

Estive em uma montanha-russa de autolayout amoroso e odeio. A chave para amar isso parece ser aceitar o seguinte:

  1. A edição do construtor de interface e a criação automática de restrições "útil" são quase inúteis para todos, exceto o caso mais trivial
  2. Criar categorias para simplificar operações comuns é um salva-vidas, pois o código é muito repetitivo e detalhado.

Dito isto, o que você está tentando não é simples e seria difícil de obter no construtor de interface. É bem simples de fazer em código. Este código, em viewDidLoad, cria e posiciona três rótulos como você está solicitando:

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

Como eu disse, esse código é muito simplificado com alguns métodos de categoria UIView, mas, para maior clareza, eu o fiz até aqui.

A categoria está aqui para os interessados ​​e possui um método para espaçar uniformemente uma matriz de visualizações ao longo de um eixo específico.

jrturton
fonte
Você é um salva-vidas. Eu não entendi # 1 até agora. O construtor de interfaces está me deixando louco, mas eu pensei que era capaz de fazer as mesmas coisas. Eu prefiro usar código de qualquer maneira, então isso é perfeito, e eu vou direto nos métodos de categoria.
nothappybob
Embora definitivamente bem próximo, isso não resolve o problema exato solicitado (mantendo o tamanho da visualização o mesmo com espaçamento uniforme entre as sub-visualizações). Esta solução é a resposta refinada do caso geral.
smileyborg
21

A maioria dessas soluções depende da existência de um número ímpar de itens para que você possa pegar o item do meio e centralizá-lo. E se você tiver um número par de itens que ainda deseja distribuir igualmente? Aqui está uma solução mais geral. Esta categoria distribuirá uniformemente qualquer número de itens ao longo do eixo vertical ou horizontal.

Exemplo de uso para distribuir verticalmente 4 rótulos em sua superview:

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

NSLayoutConstraint + EvenDistribution.h

@interface NSLayoutConstraint (EvenDistribution)

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

@end

NSLayoutConstraint + EvenDistribution.m

@implementation NSLayoutConstraint (EvenDistribution)

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

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

    return constraints;
}

@end
Ben Dolman
fonte
1
Funcionou removendo todas as restrições e imediatamente depois, adicionando a largura e restrições de altura para esses objetos, em seguida, chamar as constraintsForEvenDistributionOfItems método: relativeToCenterOfItem: vertical:
carlos_ms
@carlos_ms Adoraria ver seu código, porque quando tento com um número par de itens, esse código funciona perfeitamente ao mudar as orientações do dispositivo. Tem certeza de que não possui inadvertidamente outras restrições (como restrições de redimensionamento automático) que estão causando um conflito?
precisa
Aqui está um exemplo de criação de 4 etiquetas e uniformemente o espaçamento entre eles ao longo do eixo vertical: gist.github.com/bdolman/5379465
Ben Dolman
20

A maneira correta e mais fácil é usar as Visualizações de pilha.

  1. Adicione seus rótulos / visualizações à Visualização em pilha :

insira a descrição da imagem aqui

  1. Selecione a Visualização de pilha e defina Distribuição como espaçamento igual :

insira a descrição da imagem aqui

  1. Adicione espaçamento às restrições de vizinhos mais próximos no Stack View e atualize os quadros:

insira a descrição da imagem aqui

  1. Adicione restrições de altura a todos os rótulos (opcional). Necessário apenas para visualizações que não possuem tamanho intrínseco). Os rótulos, por exemplo, não precisam de restrições de altura e precisam apenas de numberOfLines = 3 ou 0, por exemplo.

insira a descrição da imagem aqui

  1. Aprecie a visualização:

insira a descrição da imagem aqui

Oleh Kudinov
fonte
2
Isso é> = apenas iOS 9.0, portanto verifique seu destino de implantação antes de começar a implementar :) É bom ver que isso foi adicionado!
DivideByZer0
1
2018 agora, use stack view
Jingshao Chen
17

Confira a biblioteca de código aberto PureLayout . Ele oferece alguns métodos de API para distribuição de visualizações, incluindo variantes nas quais o espaçamento entre cada visualização é fixo (o tamanho da visualização varia conforme o necessário) e onde o tamanho de cada visualização é fixo (o espaçamento entre as visualizações varia conforme o necessário). Observe que tudo isso é realizado sem o uso de "vistas espaçadoras".

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

// ...

Como é tudo de código aberto, se você estiver interessado em ver como isso é alcançado sem visualizações espaçadoras, basta dar uma olhada na implementação. (Depende do aproveitamento das restrições constant e multiplier das restrições.)

smileyborg
fonte
Bom trabalho! Embora você deva renomear o repositório para poder disponibilizá-lo via cocoapods.
Jrturton
9

Estou com um problema semelhante e descobri este post. No entanto, nenhuma das respostas fornecidas atualmente resolve o problema da maneira que você deseja. Eles não fazem o espaçamento igualmente, mas distribuem igualmente o centro dos rótulos. É importante entender que isso não é o mesmo. Eu construí um pequeno diagrama para ilustrar isso.

Ver ilustração de espaçamento

Existem 3 visualizações, todas com 20pt de altura. O uso de qualquer um dos métodos sugeridos distribui igualmente os centros das vistas e fornece o layout ilustrado. Observe que o centro y das vistas é espaçado igualmente. No entanto, o espaçamento entre a super visão e a vista superior é de 15 pontos, enquanto o espaçamento entre as sub visões é de apenas 5 pontos. Para que as vistas sejam espaçadas igualmente, ambas devem ter 10 pontos, ou seja, todas as setas azuis devem ter 10 pontos.

No entanto, ainda não encontrei uma boa solução genérica. Atualmente, minha melhor idéia é inserir "vistas de espaçamento" entre as subvisões e definir as alturas das vistas de espaçamento iguais.

Florian
fonte
1
Eu usei a solução de visualizações espaçadoras ocultas, que funciona bem.
paulmelnikow
8

Consegui resolver isso totalmente no IB:

  1. Faça restrições para alinhar o centro Y de cada uma das suas subvisões com a borda inferior da superview.
  2. Defina o multiplicador de cada uma dessas restrições como 1 / 2n, 3 / 2n, 5 / 2n,…, n-1 / 2n em que n é o número de sub-visualizações que você está distribuindo.

Portanto, se você tiver três rótulos, defina os multiplicadores para cada uma dessas restrições como 0,1666667, 0,5, 0,833333.

Hayesk
fonte
1
I, na verdade tinham a fazer um / n 3/5 n / n 7 / n até (2n-1) / n
Hamzah Malik
5

Eu encontrei um método perfeito e simples. O layout automático não permite redimensionar os espaços igualmente, mas permite redimensionar as vistas igualmente. Basta colocar algumas visualizações invisíveis entre seus campos e informar ao layout automático para mantê-los do mesmo tamanho. Funciona perfeitamente!

XIB inicial

XIB esticado

Uma coisa digna de nota; quando reduzi o tamanho no designer de interface, às vezes ele ficava confuso e deixava uma etiqueta onde estava, e havia um conflito se o tamanho fosse alterado por uma quantia ímpar. Caso contrário, funcionou perfeitamente.

editar: descobri que o conflito se tornou um problema. Por causa disso, peguei uma das restrições de espaçamento, a excluí e a substitui por duas restrições, uma maior que ou igual e uma menor que ou igual. Ambos tinham o mesmo tamanho e tinham uma prioridade muito menor do que as outras restrições. O resultado não foi mais conflito.

Owen Godfrey
fonte
4

Com base na resposta de Ben Dolman, isso distribui as visualizações de maneira mais uniforme (com preenchimento, etc):

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

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

    return constraints;
}
Ray W
fonte
3

Com rótulos, isso funciona bem, pelo menos:

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

Se o primeiro tiver a mesma largura que o segundo, o segundo e o terceiro e o terceiro e o primeiro, todos terão a mesma largura ... Você pode fazer isso na horizontal (H) e na vertical (V).

Johannes
fonte
3

versão rápida 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
fonte
1
A elaboração de como esse código responde às perguntas ajudaria futuros visitantes.
JAL
2

Sei que já faz um tempo desde a primeira resposta, mas me deparei com o mesmo problema e quero compartilhar minha solução. Para as próximas gerações ...

Defino minhas visualizações em 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];

}

E então, no updateViewConstrains, primeiro excluo todas as restrições, depois crio o dicionário de visualizações e depois calculo o espaço a ser usado entre as visualizações. Depois disso, eu apenas uso o Visual Language Format para definir as restrições:

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



}

O lado bom desse método é que você precisa fazer muito pouca matemática. Não estou dizendo que esta é a solução perfeita, mas eu trabalho para o layout que estava tentando obter.

Espero que ajude.

Marcal
fonte
2

Aqui está uma solução que centralizará verticalmente qualquer número de subvisões, mesmo que elas tenham tamanhos exclusivos. O que você deseja fazer é criar um contêiner de nível médio, centralizar na superview, colocar todas as subvisões no contêiner e organizá-las uma em relação à outra. Mas, crucialmente, você também precisa restringi-los ao topo e inferior do contêiner, para que o contêiner possa ser dimensionado e centralizado corretamente na superview. Ao calcular a altura correta de suas subvisões, o contêiner pode ser centralizado verticalmente.

Neste exemplo, selfé a superview na qual você está centralizando todas as subviews.

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
fonte
Essa é a melhor solução ao usar vistas de tamanhos diferentes que devem ser centralizadas.
Noobmcgoobs # 25/15
2

Acabei de resolver meu problema usando o recurso multiplicador. Não sei se funciona para todos os casos, mas para mim funcionou perfeitamente. Estou no Xcode 6.3 FYI.

O que acabei fazendo foi:

1) Primeiro, posicionei meus botões em uma tela de 320px de largura distribuída da maneira que eu queria que fosse em um dispositivo de 320px.

passo 1: posicionando os botões

2) Em seguida, adicionei uma restrição de espaço à superview em todos os meus botões.

Etapa 2: adicionar restrições de espaço principais

3) Em seguida, modifiquei as propriedades do espaço inicial para que a constante fosse 0 e o multiplicador seja o deslocamento x dividido pela largura da tela (por exemplo, meu primeiro botão foi 8px da borda esquerda, então defino meu multiplicador como 8/320)

4) O passo importante aqui é alterar o segundo item na relação de restrição para ser a superview.Trailing ao invés de superview.leading. Isso é fundamental porque a superview. Líder é 0 e, no meu caso, é 320, então 8/320 é 8 px em um dispositivo de 320px; quando a largura da superview muda para 640 ou o que for, todas as visualizações se movem em uma proporção relativa à largura do tamanho da tela de 320 pixels. A matemática aqui é muito mais simples de entender.

etapas 3 e 4: altere o multiplicador para xPos / screenWidth e defina o segundo item como.

ucangetit
fonte
2

Muitas respostas não estão corretas, mas obtém muitas contagens. Aqui, eu apenas escrevo uma solução programaticamente, as três visualizações são alinhadas horizontalmente, sem o uso de visualizações espaçadoras , mas só funcionam quando as larguras dos rótulos são conhecidas quando usadas no storyboard .

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

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

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

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

Aqui está mais uma resposta. Eu estava respondendo a uma pergunta semelhante e vi o link referenciado a esta pergunta. Não vi nenhuma resposta semelhante à minha. Então, pensei em escrever aqui.

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

insira a descrição da imagem aqui

E, novamente, isso pode ser feito facilmente com o iOS9 UIStackViews também.

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

Observe que é exatamente a mesma abordagem acima. Ele adiciona quatro vistas de contêineres que são preenchidas igualmente e uma vista é adicionada a cada vista de pilha que está alinhada no centro. Porém, esta versão do UIStackView reduz alguns códigos e fica bonita.

Sandeep
fonte
1

Outra abordagem pode ser fazer com que os rótulos superior e inferior tenham restrições em relação à vista superior e inferior, respectivamente, e que a vista do meio tenha restrições superiores e inferiores em relação à primeira e terceira vistas, respectivamente.

Observe que você tem mais controle sobre as restrições do que parece arrastando as vistas próximas umas das outras até que as linhas tracejadas apareçam - elas indicam restrições entre os dois objetos que serão formados em vez de entre o objeto e a superview.

Nesse caso, você deseja alterar as restrições para "Maior que ou igual a" o valor desejado, em vez de "igual a" para permitir que elas sejam redimensionadas. Não tenho certeza se isso fará exatamente o que você deseja.

Colin
fonte
1

Maneira muito fácil de resolver isso no InterfaceBuilder:

Defina a etiqueta centralizada (etiqueta2) como "Centro horizontal no contêiner" e "Centro vertical no contêiner"

Selecione a etiqueta centralizada e a etiqueta superior (etiqueta1 + etiqueta2) e adicione DUAS restrições ao espaçamento vertical. Um com Maior ou Igual ao espaçamento mínimo. Um com menos que ou igual ao espaçamento máximo.

O mesmo para a etiqueta centralizada e a etiqueta inferior (etiqueta2 + etiqueta3).

Além disso, você também pode adicionar duas restrições ao label1 - Espaço superior ao SuperView e duas restrições ao label2 - Espaço inferior ao SuperView.

O resultado será que todos os quatro espaçamentos mudarão de tamanho igualmente.

sust86
fonte
2
Descobri que isso só funciona quando você redimensiona a superview, para que o espaçamento tome exatamente os valores mínimo ou máximo. Quando você o redimensiona para um valor em algum lugar no meio, as subvisões não são distribuídas uniformemente.
Dorian Roy
1

Eu fiz uma função que pode ajudar. Este exemplo de uso:

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

fará com que a distribuição vertical (desculpe não ter a reputação de incorporar imagens). E se você alterar o eixo e alguns valores de margem:

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

Você obterá essa distribuição horizontal .

Eu sou novato no iOS, mas 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
penumbra
fonte
1

Sim, você pode fazer isso apenas no construtor de interfaces e sem escrever código - a única ressalva é que você está redimensionando o rótulo em vez de distribuir espaços em branco. Nesse caso, alinhe os X e Y do Rótulo 2 à super visão para que fique fixo no centro. Em seguida, defina o espaço vertical da etiqueta 1 como a super visão geral e a etiqueta 2 como padrão, repita para a etiqueta 3. Após definir a etiqueta 2, a maneira mais fácil de definir as etiquetas 1 e 3 é redimensioná-las até que elas se encaixem.

Aqui está a exibição horizontal, observe que o espaço vertical entre os rótulos 1 e 2 está definido como padrão:exibição horizontal

E aqui está a versão em retrato:insira a descrição da imagem aqui

Sei que eles não são absolutamente 100% igualmente espaçados entre as linhas de base devido à diferença entre o espaço padrão entre rótulos e o espaço padrão para a superview. Se isso lhe incomoda, defina o tamanho como 0 em vez de padrão

James
fonte
1

Defino um valor de largura apenas para o primeiro item (> = uma largura) e uma distância mínima entre cada item (> = uma distância). Então eu uso Ctrl para arrastar o segundo, o terceiro ... item do primeiro para encadear dependências entre os itens.

insira a descrição da imagem aqui

Vladimír Slavík
fonte
1

Tarde para a festa, mas eu tenho uma solução funcional para criar um menu horizontalmente com espaçamento. Isso pode ser feito facilmente usando o ==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
fonte
0

O Android tem um método de encadear visualizações em seu sistema de layout baseado em restrições que eu queria imitar. As pesquisas me trouxeram aqui, mas nenhuma das respostas funcionou. Eu não queria usar o StackViews porque eles tendem a me causar mais tristeza do que economizam na frente. Acabei criando uma solução que usava UILayoutGuides colocados entre as visualizações. Controlar suas larguras permite diferentes tipos de distribuições, estilos de cadeia na linguagem Android. A função aceita uma âncora à esquerda e à direita em vez de uma exibição pai. Isso permite que a cadeia seja colocada entre duas visualizações arbitrárias em vez de distribuída dentro da visualização pai. Ele usa o UILayoutGuideque está disponível apenas no iOS 9+, mas isso não deve ser mais um 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
}
Sam Corder
fonte
0

Eu queria alinhar horizontalmente 5 imagens, então acabei seguindo a resposta do Mete com uma pequena diferença.

A primeira imagem será centralizada horizontalmente no contêiner igual a 0 e um multiplicador de 1: 5:

primeira imagem

A segunda imagem será centralizada horizontalmente no contêiner igual a 0 e um multiplicador de 3: 5: segunda imagem

E assim para o resto das imagens. Por exemplo, a quinta (e última) imagem será centralizada horizontalmente no contêiner igual a 0 e um multiplicador de 9: 5: última imagem

Como Mete explicou, a ordem vai 1, 3, 5, 7, 9, etc. As posições seguem a mesma lógica: a primeira posição é 1, depois o espaço, depois a próxima posição 3 e assim por diante.

Adriana Pineda
fonte
-1

Por que você simplesmente não cria um tableView e faz isScrollEnabled = false

Josh O'Connor
fonte
Não entendo o voto negativo, minha solução é pensar fora da caixa. Por que enfrentar a dor de cabeça de criar uma lista com o layout automático quando o cacau fornece uma lista no UITableView?
Josh O'Connor