Como usar o layout automático para mover outras visualizações quando ela está oculta?


334

Eu projetei minha célula personalizada no IB, subclassifiquei-a e conectei minhas tomadas à minha classe personalizada. Eu tenho três subvisões no conteúdo da célula que são: UIView (cdView) e dois rótulos (titleLabel e emailLabel). Dependendo dos dados disponíveis para cada linha, às vezes quero exibir o UIView e dois rótulos na minha célula e, às vezes, apenas dois rótulos. O que estou tentando fazer é definir restrições dessa maneira, se eu definir a propriedade UIView como oculta ou removê-la da super visão, os dois rótulos serão movidos para a esquerda. Tentei definir a restrição principal do UIView como Superview (conteúdo da célula) para 10px e as restrições principais do UILiew para 10 px na próxima visualização (UIView). Mais tarde no meu código

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(IndexPath *)indexPath {
...
Record *record = [self.records objectAtIndex:indexPath.row];

if ([record.imageURL is equalToString:@""]) {
     cell.cdView.hidden = YES;
}

Estou ocultando meu cell.cdView e gostaria que os rótulos se movessem para a esquerda, mas eles permanecem na mesma posição no Cell. Tentei remover cell.cdView da superview, mas também não funcionou. Anexei uma imagem para esclarecer o que sou.

célula

Sei como fazer isso programaticamente e não estou procurando essa solução. O que eu quero é definir restrições no IB e espero que minhas subvisões sejam movidas dinamicamente se outras visualizações forem removidas ou ocultas. É possível fazer isso no IB com layout automático?

.....

Alterar o valor limitações de tempo de execução - marque essa resposta
Kampai

Para este caso específico, você também pode usar um UIStackView. quando você esconder o cd, os rótulos vai ocupar o seu espaço
Marco Pappalardo

Respostas:


373

É possível, mas você terá que fazer um pouco de trabalho extra. Existem algumas coisas conceituais para sair do caminho primeiro:

  • As visualizações ocultas, mesmo que não sejam desenhadas, ainda participam do Layout automático e geralmente retêm seus quadros , deixando outras visualizações relacionadas em seus lugares.
  • Ao remover uma visão de sua super visão, todas as restrições relacionadas também são removidas dessa hierarquia de visão.

No seu caso, isso provavelmente significa:

  • Se você definir a vista esquerda como oculta, os rótulos permanecerão no lugar, pois a vista esquerda ainda está ocupando espaço (mesmo que não esteja visível).
  • Se você remover a vista esquerda, seus rótulos provavelmente ficarão com restrições ambíguas, pois você não terá mais restrições nas bordas esquerdas dos rótulos.

O que você precisa fazer é restringir criteriosamente os rótulos. Deixe suas restrições existentes (10 pontos de espaço para a outra visualização), mas adicione outra restrição: faça as bordas esquerdas dos seus rótulos 10 pontos da borda esquerda da superview com uma prioridade não necessária (a alta prioridade padrão provavelmente funcionará bem).

Em seguida, quando você desejar que eles se movam para a esquerda, remova completamente a vista esquerda. A restrição obrigatória de 10 pontos à vista esquerda desaparecerá junto com a vista a que se refere, e você terá apenas uma restrição de alta prioridade de que os rótulos estejam a 10 pontos da visão geral. Na próxima passagem de layout, isso deve fazer com que eles se expandam para a esquerda até preencherem a largura da superview, exceto pelo espaçamento nas bordas.

Uma ressalva importante: se você quiser voltar à vista esquerda da imagem, não apenas precisará adicioná-la novamente à hierarquia da vista, mas também precisará restabelecer todas as restrições ao mesmo tempo. Isso significa que você precisa de uma maneira de colocar sua restrição de espaçamento de 10 pontos entre a exibição e seus rótulos sempre que essa exibição for exibida novamente.


8
Embora essa resposta certamente funcione, na IMO, o excesso de restrições para lidar com vários casos de uso parece ser um cheiro de código - principalmente porque você teria que restabelecer todas as restrições para as visualizações removidas que deseja exibir novamente.
memmons

Na minha opinião, este não é o caminho a percorrer. Em vez disso, você deve usar uma restrição de largura / altura para a exibição que deseja ocultar.
Ullstrm

26
Eu discordo respeitosamente. Se você (por exemplo) definir a largura da visualização como 0, terá dois problemas. Primeiro, agora você tem um espaçamento duplo entre a super visão e a visão visível: |-(space)-[hidden(0)]-(space)-[visible]é efetivamente |-(2*space)-[visible]. Segundo, essa visualização pode começar a gerar violações de restrição, dependendo de sua própria subárvore e restrições de visualização - você não pode garantir que pode restringir arbitrariamente uma visualização na largura 0 e mantê-la funcionando.
Tim

1
Se você estiver usando uma visualização com tamanho de conteúdo intrínseco, a resposta de Tim parece ser o caminho único a seguir.
Balkoth 27/05

Obrigado Tim. Defino uma restrição de 0 com uma prioridade mais alta para evitar incompatibilidades de restrição, mas agora percebo que há um problema com o espaçamento duplo. Eu não tive esse problema porque nunca mostro as duas visualizações juntas (meu caso |-[otherViews]-[eitherThis][orThis]-|:), mas acabei encontrando esse problema eventualmente.
Ferran Maylinch

207

Adicionar ou remover restrições durante o tempo de execução é uma operação pesada que pode afetar o desempenho. No entanto, existe uma alternativa mais simples.

Para a vista que você deseja ocultar, configure uma restrição de largura. Restrinja as outras vistas com uma lacuna horizontal principal a essa vista.

Para ocultar, atualize a .constantrestrição de largura para 0.f. As outras vistas se moverão automaticamente para a esquerda para assumir a posição.

Veja minha outra resposta aqui para obter mais detalhes:

Como alterar as restrições de rótulo durante o tempo de execução?


29
O único problema com esta solução é que a margem à esquerda será o dobro do que você provavelmente deseja, então eu atualizaria uma dessas restrições também, mas mesmo assim acho que isso é menos trabalhoso do que remover a subvisão.
José Manuel Sánchez

2
@ skinsfan00atg Se você estiver usando uma exibição com tamanho de conteúdo intrínseco, não poderá usar esta solução.
Balkoth 27/05

@balkoth porque não? Você poderia apenas reduzir a prioridade abraços conteúdo
Max MacLeod

2
@MaxMacLeod Se você reduzir a prioridade de restrição de conteúdo, não estará usando o tamanho do conteúdo intrínseco, mas sim o tamanho indicado pela restrição.
Balkoth 29/05

2
@MaxMacLeod Ok, entendi o que você quer dizer. Você precisa definir a prioridade da resistência à compactação como 0 (não o conteúdo encolhido) no código quando deseja ocultar a exibição e quando deseja mostrar novamente restaurar esse valor. Além disso, você precisa adicionar uma restrição no construtor de interface para definir o tamanho da visualização como 0. Não há necessidade de tocar nessa contraint no código.
Balkoth 29/05

87

Para aqueles que oferecem suporte apenas ao iOS 8 ou superior , há uma nova propriedade booleana ativa . Ajudará a habilitar apenas as restrições necessárias dinamicamente

A saída de restrição PS deve ser forte , não fraca

Exemplo:

@IBOutlet weak var optionalView: UIView!
@IBOutlet var viewIsVisibleConstraint: NSLayoutConstraint!
@IBOutlet var viewIsHiddenConstraint: NSLayoutConstraint!

func showView() {
    optionalView.isHidden = false
    viewIsVisibleConstraint.isActive = true
    viewIsHiddenConstraint.isActive = false
}

func hideView() {
    optionalView.isHidden = true
    viewIsVisibleConstraint.isActive = false
    viewIsHiddenConstraint.isActive = true
}

Além disso, para corrigir um erro no storyboard, desmarque a caixa de Installedseleção de uma dessas restrições.

UIStackView (iOS 9 ou superior)
Mais uma opção é agrupar suas visualizações UIStackView. Quando a vista estiver oculta UIStackView, o layout será atualizado automaticamente


ativo funciona parcialmente. Mas se eu usá-lo dentro de uma célula reutilizável, de ativar para desativar, funciona, mas não para desativar para ativar. Alguma ideia? Ou você poderia dar um exemplo, talvez?
Aoke Li

10
Isso acontece porque a restrição desativada não é carregada ou desalocada. Sua tomada de restrição deve ser forte, não fraco
Silmaril

Onde podemos encontrar essa propriedade "ativa"?
Lakshmi Reddy /


Parece que o excesso de restrições + a atividade de restrição definida podem ser a resposta mais razoável.
Ting Yi Shih

68

UIStackViewreposiciona suas visualizações automaticamente quando a hiddenpropriedade é alterada em qualquer uma de suas subvisões (iOS 9+).

UIView.animateWithDuration(1.0) { () -> Void in
   self.mySubview.hidden = !self.mySubview.hidden
}

Vá para 11:48 neste vídeo da WWDC para uma demonstração:

Mistérios do layout automático, parte 1


Ocultei uma exibição de pilha aninhada e a exibição inteira da pilha desapareceu.
11118 Ian Ian Burburton

5
Essa deve ser a resposta aceita. Seguindo as recomendações da apple do wwdc 2015 no design do construtor de interface.
Thibaut noah

@thibautnoah E o suporte ao iOS 8, então?
bagage

5
@ bagage A menos que você seja facebook ou google, você pode soltá-lo. Com a cobertura além do iOS 9, você suportará mais de 90% dos dispositivos, mais do que suficiente. Apoiando abaixo irá prejudicar o seu processo de desenvolvimento e impedi-lo de usar recursos mais recentes imo
thibaut noah

16

Meu projeto usa uma @IBDesignablesubclasse personalizada de UILabel(para garantir consistência em cores, fontes, inserções etc.) e implementei algo como o seguinte:

override func intrinsicContentSize() -> CGSize {
    if hidden {
        return CGSizeZero
    } else {
        return super.intrinsicContentSize()
    }
}

Isso permite que a subclasse de etiqueta participe do Layout automático, mas não ocupa espaço quando oculta.


13

Para os Googlers: com base na resposta de Max, para resolver o problema do preenchimento que muitos notaram, simplesmente aumentei a altura do rótulo e usei essa altura como separador em vez do preenchimento real. Essa ideia pode ser expandida para qualquer cenário que contenha visualizações.

Aqui está um exemplo simples:

IB Screenshot

Nesse caso, mapeio a altura do rótulo do autor para um apropriado IBOutlet:

@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;

e quando defino a altura da restrição 0.0f, preservamos o "preenchimento", porque a altura do botão Reproduzir permite isso.


Para quem é novo NSLayoutConstraint, acredito que você deseja atualizar a constantpropriedade do seu authorLabelHeight.
30516 Kyle

8

conecte a restrição entre a uiview e os rótulos como IBOutlet e configure o membro de prioridade com um valor menor quando configurado oculto = YES


3
Você não pode ajustar a prioridade de um NSLayoutConstraint depois de estabelecido; você precisa remover e ler uma nova restrição com uma prioridade diferente.
Tim

7
Eu consegui esse método para funcionar. Eu tenho um caso que usa um rótulo e um botão, onde preciso ocultar o botão e expandir o rótulo. Eu tenho duas restrições, uma inicialmente com prioridade 751 e a outra com 750. Então, quando oculto o botão, lancei as prioridades e o comprimento do rótulo aumenta. Deve-se observar que, se você tentar aumentar a prioridade 1000, você receberá o erro "Não há suporte para a alteração da prioridade necessária para não em uma restrição instalada (ou vice-versa)". Então não, e você parece estar bem. Xcode 5.1 / viewDidLoad.
John Bushnell

8

O que acabei fazendo foi criar 2 xibs. Um com a vista esquerda e outro sem ela. Registrei o controlador e decidi qual usar durante cellForRowAtIndexPath.

Eles usam a mesma classe UITableViewCell. A desvantagem é que há alguma duplicação do conteúdo entre os xibs, mas essas células são bastante básicas. A vantagem é que não tenho um monte de código para gerenciar manualmente a remoção de exibição, atualização de restrições etc.

Em geral, essa provavelmente é uma solução melhor, pois são layouts tecnicamente diferentes e, portanto, devem ter xibs diferentes.

[self.table registerNib:[UINib nibWithNibName:@"TrackCell" bundle:nil] forCellReuseIdentifier:@"TrackCell"];
[self.table registerNib:[UINib nibWithNibName:@"TrackCellNoImage" bundle:nil] forCellReuseIdentifier:@"TrackCellNoImage"];

TrackCell *cell = [tableView dequeueReusableCellWithIdentifier:(appDelegate.showImages ? @"TrackCell" : @"TrackCellNoImage") forIndexPath:indexPath];

7

Nesse caso, mapeio a altura do rótulo do autor para um IBOutlet apropriado:

@property (retain, nonatomic) IBOutlet NSLayoutConstraint* authorLabelHeight;

e quando defino a altura da restrição como 0,0f, preservamos o "preenchimento", porque a altura do botão Reproduzir permite isso.

cell.authorLabelHeight.constant = 0;

insira a descrição da imagem aqui insira a descrição da imagem aqui


6

Use duas UIStackView Horizontal e Vertical, quando alguma subvisualização da pilha estiver oculta, outras subvisões da pilha serão movidas, use Distribuição -> Preencher proporcionalmente para a pilha vertical com dois UILabels e precisar definir consensos de largura e altura para o primeiro UIViewinsira a descrição da imagem aqui


2

Tente isso, eu implementei o código abaixo,

Eu tenho uma View no ViewController na qual adicionamos outras três views. Quando alguma view estiver oculta, outras duas view se moverão, siga as etapas abaixo. ,

1.ViewController.h Arquivo

#import <UIKit/UIKit.h>
@interface ViewController : UIViewController
@property (strong, nonatomic) IBOutlet UIView *viewOne;
@property (strong, nonatomic) IBOutlet UIView *viewTwo;
@property (strong, nonatomic) IBOutlet UIView *viewThree;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewOneWidth;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewTwoWidth;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewThreeWidth;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *viewBottomWidth;
@end

2.ViewController.m

 #import "ViewController.h"
 @interface ViewController ()
{
  CGFloat viewOneWidthConstant;
  CGFloat viewTwoWidthConstant;
  CGFloat viewThreeWidthConstant;
  CGFloat viewBottomWidthConstant;
}
@end

@implementation ViewController
@synthesize viewOne, viewTwo, viewThree;

- (void)viewDidLoad {
  [super viewDidLoad];
 // Do any additional setup after loading the view, typically from a 
  nib.

  /*
   0  0   0
   0  0   1
   0  1   0
   0  1   1
   1  0   0
   1  0   1
   1  1   0
   1  1   1
   */

  //    [viewOne setHidden:NO];
  //    [viewTwo setHidden:NO];
  //    [viewThree setHidden:NO];

  //    [viewOne setHidden:NO];
  //    [viewTwo setHidden:NO];
  //    [viewThree setHidden:YES];

  //    [viewOne setHidden:NO];
  //    [viewTwo setHidden:YES];
  //    [viewThree setHidden:NO];

  //    [viewOne setHidden:NO];
  //    [viewTwo setHidden:YES];
  //    [viewThree setHidden:YES];


  //    [viewOne setHidden:YES];
  //    [viewTwo setHidden:NO];
  //    [viewThree setHidden:NO];

  //    [viewOne setHidden:YES];
  //    [viewTwo setHidden:NO];
  //    [viewThree setHidden:YES];

 //    [viewOne setHidden:YES];
 //    [viewTwo setHidden:YES];
 //    [viewThree setHidden:NO];

//    [viewOne setHidden:YES];
//    [viewTwo setHidden:YES];
//    [viewThree setHidden:YES];

 [self hideShowBottomBar];
  }

- (void)hideShowBottomBar
{
  BOOL isOne = !viewOne.isHidden;
  BOOL isTwo = !viewTwo.isHidden;
  BOOL isThree = !viewThree.isHidden;

  viewOneWidthConstant = _viewOneWidth.constant;
  viewTwoWidthConstant = _viewTwoWidth.constant;
  viewThreeWidthConstant = _viewThreeWidth.constant;
  viewBottomWidthConstant = _viewBottomWidth.constant;

   if (isOne && isTwo && isThree) {
    // 0    0   0
    _viewOneWidth.constant = viewBottomWidthConstant / 3;
    _viewTwoWidth.constant = viewBottomWidthConstant / 3;
    _viewThreeWidth.constant = viewBottomWidthConstant / 3;
    }
    else if (isOne && isTwo && !isThree) {
     // 0    0   1
    _viewOneWidth.constant = viewBottomWidthConstant / 2;
    _viewTwoWidth.constant = viewBottomWidthConstant / 2;
    _viewThreeWidth.constant = 0;
    }
   else if (isOne && !isTwo && isThree) {
    // 0    1   0
    _viewOneWidth.constant = viewBottomWidthConstant / 2;
    _viewTwoWidth.constant = 0;
    _viewThreeWidth.constant = viewBottomWidthConstant / 2;
    }
    else if (isOne && !isTwo && !isThree) {
    // 0    1   1
    _viewOneWidth.constant = viewBottomWidthConstant;
    _viewTwoWidth.constant = 0;
    _viewThreeWidth.constant = 0;
   }
   else if (!isOne && isTwo && isThree) {
    // 1    0   0
    _viewOneWidth.constant = 0;
    _viewTwoWidth.constant = viewBottomWidthConstant / 2;
    _viewThreeWidth.constant = viewBottomWidthConstant / 2;
   }
   else if (!isOne && isTwo && !isThree) {
    // 1    0   1
    _viewOneWidth.constant = 0;
    _viewTwoWidth.constant = viewBottomWidthConstant;
    _viewThreeWidth.constant = 0;
   }
   else if (!isOne && !isTwo && isThree) {
    // 1    1   0
    _viewOneWidth.constant = 0;
    _viewTwoWidth.constant = 0;
    _viewThreeWidth.constant = viewBottomWidthConstant;
   }
   else if (isOne && isTwo && isThree) {
    // 1    1   1
    _viewOneWidth.constant = 0;
    _viewTwoWidth.constant = 0;
    _viewThreeWidth.constant = 0;
   }
  }

 - (void)didReceiveMemoryWarning {
  [super didReceiveMemoryWarning];
 // Dispose of any resources that can be recreated.
 }
 @end

insira a descrição da imagem aqui insira a descrição da imagem aqui insira a descrição da imagem aqui

Esperança Então, essa lógica ajudará alguém.


1

No meu caso, defino a constante da restrição de altura como 0.0fe também a hiddenpropriedade YES.

Para mostrar a vista (com os subviews) mais uma vez eu fiz o contrário: eu definir a constante altura para um valor diferente de zero e defina a hiddenpropriedade para NO.


1

Vou usar o stackview horizontal. Ele pode remover o quadro quando a subvisão está oculta.

Na imagem abaixo, a exibição em vermelho é o contêiner real do seu conteúdo e possui 10 pontos à direita da superview laranja (ShowHideView), basta conectar o ShowHideView ao IBOutlet e mostrar / ocultar / remover programaticamente.

  1. É quando a visualização é visível / instalada.

a vista é visível

  1. É quando a visualização está oculta / não instalada.

a vista está oculta / removida


1

Esta é minha outra solução usando restrição de prioridade. A ideia é definir a largura como 0.

  1. crie uma visualização de contêiner (laranja) e defina a largura. insira a descrição da imagem aqui

  2. crie uma visualização de conteúdo (vermelho) e defina o espaço à direita 10pt para superview (laranja). Observe as restrições de espaço à direita, existem 2 restrições à direita com prioridade diferente. Baixo (= 10) e Alto (<= 10). Isso é importante para evitar ambiguidade. insira a descrição da imagem aqui

  3. Defina a largura da vista laranja como 0 para ocultar a vista. insira a descrição da imagem aqui


0

Como no_scene sugeriu, você pode definitivamente fazer isso alterando a prioridade da restrição no tempo de execução. Isso foi muito mais fácil para mim, porque eu tinha mais de uma visão de bloqueio que precisaria ser removida.

Aqui está um trecho usando ReactiveCocoa:

RACSignal* isViewOneHiddenSignal = RACObserve(self.viewModel, isViewOneHidden);
RACSignal* isViewTwoHiddenSignal = RACObserve(self.viewModel, isViewTwoHidden);
RACSignal* isViewThreeHiddenSignal = RACObserve(self.viewModel, isViewThreeHidden);
RAC(self.viewOne, hidden) = isViewOneHiddenSignal;
RAC(self.viewTwo, hidden) = isViewTwoHiddenSignal;
RAC(self.viewThree, hidden) = isViewThreeHiddenSignal;

RAC(self.viewFourBottomConstraint, priority) = [[[[RACSignal
    combineLatest:@[isViewOneHiddenSignal,
                    isViewTwoHiddenSignal,
                    isViewThreeHiddenSignal]]
    and]
    distinctUntilChanged]
    map:^id(NSNumber* allAreHidden) {
        return [allAreHidden boolValue] ? @(780) : @(UILayoutPriorityDefaultHigh);
    }];

RACSignal* updateFramesSignal = [RACObserve(self.viewFourBottomConstraint, priority) distinctUntilChanged];
[updateFramesSignal
    subscribeNext:^(id x) {
        @strongify(self);
        [self.view setNeedsUpdateConstraints];
        [UIView animateWithDuration:0.3 animations:^{
            [self.view layoutIfNeeded];
        }];
    }];


0

Aqui está como eu realinharia minhas uiviews para obter sua solução:

  1. Arraste para soltar um UIImageView e coloque-o à esquerda.
  2. Arraste e solte um UIView e coloque-o à direita do UIImageView.
  3. Arraste e solte dois UILabels dentro desse UIView cujas restrições à esquerda e à direita são zero.
  4. Defina a principal restrição do UIView que contém 2 rótulos como superview em vez de UIImagView.
  5. Se o UIImageView estiver oculto, defina a constante de restrição principal como 10 px como superview. ELSE, defina a constante de restrição principal como 10 px + UIImageView.width + 10 px.

Eu criei minha própria regra de ouro. Sempre que você precisar ocultar / mostrar qualquer visão geral cujas restrições possam ser afetadas, adicione todas as subvisões afetadas / dependentes dentro de uma visão geral e atualize sua restrição principal / final / superior / inferior constante programaticamente.


0

Esta é uma pergunta antiga, mas ainda espero que ajude. Vindo do Android, nesta plataforma, você tem um método útil isVisiblepara ocultá-lo da visualização, mas também não considera o quadro quando o layout automático desenha a visualização.

usando a extensão e a extensão "uiview", você pode executar uma função semelhante no ios (não sabe por que ela ainda não está no UIKit) aqui uma implementação no swift 3:

    func isVisible(_ isVisible: Bool) {
        self.isHidden = !isVisible
        self.translatesAutoresizingMaskIntoConstraints = isVisible
        if isVisible { //if visible we remove the hight constraint 
            if let constraint = (self.constraints.filter{$0.firstAttribute == .height}.first){
                self.removeConstraint(constraint)
            }
        } else { //if not visible we add a constraint to force the view to have a hight set to 0
            let height = NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal , toItem: nil, attribute: .notAnAttribute, multiplier: 0, constant: 0)
            self.addConstraint(height)
        }
        self.layoutIfNeeded()
    }

0

a maneira correta de fazer isso é desabilitar as restrições com isActive = false. observe, no entanto, que a desativação de uma restrição a remove e libera, portanto você precisa ter saídas fortes para elas.


0

A solução mais fácil é usar o UIStackView (horizontal). Adicionar à exibição de pilha: primeira exibição e segunda exibição com etiquetas. Em seguida, defina a propriedade isHidden da primeira visualização como false. Todas as restrições serão calculadas e atualizadas automaticamente.


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.