¿Cómo usar el diseño automático para mover otras vistas cuando una vista está oculta?


334

Diseñé mi celda personalizada en IB, la subclasifiqué y conecté mis salidas a mi clase personalizada. Tengo tres subvistas en el contenido de la celda que son: UIView (cdView) y dos etiquetas (titleLabel y emailLabel). Dependiendo de los datos disponibles para cada fila, a veces quiero que se muestren UIView y dos etiquetas en mi celda y, a veces, solo dos etiquetas. Lo que estoy tratando de hacer es establecer restricciones de esa manera si configuro la propiedad UIView como oculta o la eliminaré de la vista general, las dos etiquetas se moverán hacia la izquierda. Traté de establecer la restricción principal de UIView en Superview (contenido de celda) para 10px y las restricciones principales de UILabels para 10 px en la siguiente vista (UIView). Más tarde en mi 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;
}

Estoy ocultando mi cell.cdView y me gustaría que las etiquetas se muevan hacia la izquierda, sin embargo, se mantienen en la misma posición en Cell. Traté de eliminar cell.cdView de la supervista pero tampoco funcionó. He adjuntado una imagen para aclarar de qué se trata.

célula

Sé cómo hacer esto programáticamente y no estoy buscando esa solución. Lo que quiero es establecer restricciones en IB y espero que mis subvistas se muevan dinámicamente si se eliminan u ocultan otras vistas. ¿Es posible hacer esto en IB con diseño automático?

.....

Cambiar el tiempo de ejecución del valor de las restricciones: verifique esta respuesta
Kampai

Para este caso específico, también puede usar un UIStackView. cuando ocultas el CD, las etiquetas ocuparán su espacio
Marco Pappalardo

Respuestas:


373

Es posible, pero tendrás que hacer un poco de trabajo extra. Primero hay un par de cosas conceptuales que debes resolver:

  • Las vistas ocultas, aunque no dibujan, aún participan en el diseño automático y, por lo general, conservan sus marcos , dejando otras vistas relacionadas en sus lugares.
  • Al eliminar una vista de su supervista, todas las restricciones relacionadas también se eliminan de esa jerarquía de vistas.

En su caso, esto probablemente significa:

  • Si configura su vista izquierda para que esté oculta, las etiquetas permanecen en su lugar, ya que esa vista izquierda todavía ocupa espacio (aunque no sea visible).
  • Si elimina su vista izquierda, sus etiquetas probablemente quedarán ambiguamente restringidas, ya que ya no tiene restricciones para los bordes izquierdos de sus etiquetas.

Lo que debe hacer es restringir juiciosamente sus etiquetas. Deje sus restricciones existentes (10 puntos de espacio para la otra vista) solo, pero agregue otra restricción: haga que los bordes izquierdos de sus etiquetas estén a 10 puntos del borde izquierdo de su supervista con una prioridad no requerida (la alta prioridad predeterminada probablemente funcionará bien).

Luego, cuando desee que se muevan hacia la izquierda, elimine la vista izquierda por completo. La restricción obligatoria de 10 puntos para la vista izquierda desaparecerá junto con la vista con la que se relaciona, y solo tendrá una restricción de alta prioridad para que las etiquetas estén a 10 puntos de distancia de su supervista. En la próxima pasada de diseño, esto debería hacer que se expandan a la izquierda hasta que llenen el ancho de la supervista, pero para su espacio alrededor de los bordes.

Una advertencia importante: si alguna vez desea que su vista izquierda vuelva a aparecer en la imagen, no solo tiene que volver a agregarla a la jerarquía de vistas, sino que también debe restablecer todas sus restricciones al mismo tiempo. Esto significa que necesita una forma de volver a colocar la restricción de espacio de 10 puntos entre la vista y sus etiquetas cada vez que se vuelve a mostrar esa vista.


8
Si bien esta respuesta ciertamente funcionará, la OMI, la restricción excesiva para manejar varios casos de uso parece ser un olor a código, particularmente porque tendría que restablecer todas las restricciones para las vistas eliminadas que desea mostrar nuevamente.
memmons

En mi opinión, este no es el camino a seguir. En su lugar, debe usar una restricción de ancho / alto para la vista que desea ocultar.
ullstrm

26
Respetuosamente no estoy de acuerdo. Si (por ejemplo) establece el ancho de la vista en 0, se encontrará con dos problemas. Primero, ahora tiene un doble espacio entre la supervista y la vista visible: |-(space)-[hidden(0)]-(space)-[visible]es efectivo |-(2*space)-[visible]. En segundo lugar, esa vista podría comenzar a generar infracciones de restricciones dependiendo de su propio subárbol de vista y restricciones: no puede garantizar que pueda restringir arbitrariamente una vista con un ancho de 0 y que siga funcionando.
Tim

1
Si está utilizando una vista con un tamaño de contenido intrínseco, la respuesta de Tim parece la única manera de hacerlo.
balkoth

Gracias Tim Establecí una restricción de 0 con una prioridad más alta para evitar incompatibilidades de restricción, pero ahora me doy cuenta de que existe el problema con el doble espacio. No tuve ese problema porque nunca muestro las dos vistas juntas (mi caso:) |-[otherViews]-[eitherThis][orThis]-|, pero eventualmente me encontré con ese problema.
Ferran Maylinch

207

Agregar o eliminar restricciones durante el tiempo de ejecución es una operación pesada que puede afectar el rendimiento. Sin embargo, hay una alternativa más simple.

Para la vista que desea ocultar, configure una restricción de ancho. Restrinja las otras vistas con un espacio horizontal principal para esa vista.

Para ocultar, actualice la .constantrestricción de ancho a 0.f. Las otras vistas se moverán automáticamente a la izquierda para asumir la posición.

Vea mi otra respuesta aquí para más detalles:

¿Cómo cambiar las restricciones de etiqueta durante el tiempo de ejecución?


29
El único problema con esta solución es que el margen a la izquierda será el doble de lo que probablemente desee, por lo que también actualizaría una de esas restricciones, pero incluso entonces creo que esto es menos trabajo que eliminar la subvista.
José Manuel Sánchez

2
@ skinsfan00atg Si está utilizando una vista con tamaño de contenido intrínseco, no puede utilizar esta solución.
balkoth

@balkoth ¿por qué no? Podrías reducir la prioridad de abrazar el contenido
Max MacLeod

2
@MaxMacLeod Si reduce la prioridad de abrazo de contenido, entonces no está utilizando el tamaño de contenido intrínseco, está utilizando el tamaño indicado por la restricción.
balkoth

2
@MaxMacLeod Ok, entiendo lo que quieres decir. Debe establecer la prioridad de resistencia a la compresión en 0 (no el contenido abrazado) en el código cuando desea ocultar la vista y cuando desea mostrar nuevamente restaurar ese valor. Aparte de eso, debe agregar una restricción en el generador de interfaces para establecer el tamaño de la vista en 0. No es necesario tocar esta restricción en el código.
balkoth

87

Para aquellos que solo admiten iOS 8+ , hay una nueva propiedad booleana activa . Ayudará a habilitar solo las restricciones necesarias dinámicamente

La salida de restricción PS debe ser fuerte , no débil

Ejemplo:

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

Además, para corregir un error en el guión gráfico, deberá desmarcar la Installedcasilla de verificación de una de estas restricciones.

UIStackView (iOS 9+)
Una opción más es ajustar sus vistas UIStackView. Una vez que la vista esté oculta, UIStackViewse actualizará el diseño automáticamente


activo en parte funciona. Pero si lo uso dentro de una celda reutilizable, desde activar para desactivar, funciona, pero no para desactivar para activar. ¿Algunas ideas? ¿O podrías dar un ejemplo tal vez?
Aoke Li

10
Ocurre porque la restricción desactivada no se carga o desasigna. Su salida de restricción debe ser fuerte, no débil
Silmaril

¿Dónde podemos encontrar esa propiedad "activa"?
Lakshmi Reddy


Parece que la actividad de restricción excesiva + restricción establecida puede ser la respuesta más razonable.
Ting Yi Shih el

68

UIStackViewreposiciona sus vistas automáticamente cuando hiddense cambia la propiedad en cualquiera de sus subvistas (iOS 9+).

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

Pase a las 11:48 en este video de WWDC para una demostración:

Misterios del diseño automático, parte 1


Escondí una vista de pila anidada y desapareció toda la vista de pila que contenía.
Ian Warburton el

55
Esta debería ser la respuesta aceptada. Siguiendo las recomendaciones de Apple de wwdc 2015 sobre el diseño del generador de interfaces.
thibaut noah

@thibautnoah ¿Y qué pasa con el soporte para iOS 8 entonces?
bagage

55
@bagage A menos que sea Facebook o Google, puede soltarlo. Con cobertura más allá de iOS 9, admitirá más del 90% de los dispositivos, más que suficiente. El soporte a continuación paralizará su proceso de desarrollo y le impedirá usar las últimas funciones imo
thibaut noah

16

Mi proyecto utiliza una @IBDesignablesubclase personalizada de UILabel(para garantizar la coherencia en color, fuente, inserciones, etc.) y he implementado algo como lo siguiente:

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

Esto permite que la subclase de etiquetas participe en el diseño automático, pero no ocupa espacio cuando está oculto.


13

Para los Googlers: basándose en la respuesta de Max, para resolver el problema del relleno que muchos han notado, simplemente aumenté la altura de la etiqueta y usé esa altura como separador en lugar del relleno real. Esta idea podría ampliarse para cualquier escenario con vistas que contengan.

Aquí hay un ejemplo simple:

Captura de pantalla de IB

En este caso, asigno la altura de la etiqueta Autor a un apropiado IBOutlet:

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

y cuando establezco la altura de la restricción en 0.0f, conservamos el "relleno", porque la altura del botón Reproducir lo permite.


Para aquellos que son nuevos en NSLayoutConstraintCreo que desea actualizar la constantpropiedad de su authorLabelHeight.
Kyle

8

conecte la restricción entre uiview y las etiquetas como IBOutlet y establezca el miembro de prioridad en un valor menor cuando se configura oculto = SÍ


3
No puede ajustar la prioridad de una NSLayoutConstraint una vez que se establece; debe eliminar y leer una nueva restricción con una prioridad diferente.
Tim

77
Este método me ha funcionado. Tengo un caso que usa una etiqueta y un botón, donde necesito ocultar el botón y expandir la etiqueta. Tengo dos restricciones, una inicialmente con prioridad 751 y la otra con 750. Luego, cuando oculto el botón, volteo las prioridades y la longitud de la etiqueta aumenta. Debe tenerse en cuenta que si intenta hacer que la prioridad 1000 sea más alta, aparece el error "No se admite la mutación de una prioridad de requerida a no en una restricción instalada (o viceversa)". Entonces no lo hagas y pareces estar bien. Xcode 5.1 / viewDidLoad.
John Bushnell

8

Lo que terminé haciendo fue crear 2 xibs. Uno con la vista izquierda y otro sin ella. Registré ambos en el controlador y luego decidí cuál usar durante cellForRowAtIndexPath.

Usan la misma clase UITableViewCell. La desventaja es que hay cierta duplicación del contenido entre los xibs, pero estas celdas son bastante básicas. Lo bueno es que no tengo un montón de código para gestionar manualmente la eliminación de la vista, la actualización de restricciones, etc.

En general, esta es probablemente una mejor solución ya que son diseños técnicamente diferentes y, por lo tanto, deberían tener diferentes xibs.

[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

En este caso, asigno la altura de la etiqueta Autor a un IBOutlet apropiado:

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

y cuando configuro la altura de la restricción a 0.0f, conservamos el "relleno", porque la altura del botón Reproducir lo permite.

cell.authorLabelHeight.constant = 0;

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí


6

Use dos UIStackView Horizontal y Vertical, cuando se oculta alguna vista de subvista en la pila, se moverán otras subvistas de pila, use Distribución -> Rellenar proporcionalmente para la pila Vertical con dos UILabels y necesita establecer constantes de ancho y alto para la primera UIViewingrese la descripción de la imagen aquí


2

Prueba esto, he implementado el siguiente código,

Tengo una Vista en ViewController que agregó otras tres vistas. Cuando cualquier vista esté oculta, se moverán otras dos vistas, siga los pasos a continuación. ,

1.ViewController.h File

#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

ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí ingrese la descripción de la imagen aquí

Espero que esta lógica ayude a alguien.


1

En mi caso establecí la constante de la restricción de altura en 0.0fy también configuré la hiddenpropiedad en YES.

Para mostrar la vista (con las subvistas) nuevamente hice lo contrario: establecí la altura constante en un valor distinto de cero y establecí la hiddenpropiedad en NO.


1

Usaré stackview horizontal. Puede eliminar el marco cuando la subvista está oculta.

En la imagen a continuación, la vista roja es el contenedor real de su contenido y tiene un espacio final de 10 puntos a la supervista naranja (MostrarOcultarVer), luego simplemente conecte MostrarOcultar a IBOutlet y muéstrelo / oculte / elimínelo programáticamente.

  1. Esto es cuando la vista es visible / instalada.

la vista es visible

  1. Esto es cuando la vista está oculta / no instalada.

la vista está oculta / eliminada


1

Esta es mi otra solución usando restricción de prioridad. La idea es establecer el ancho en 0.

  1. crear vista de contenedor (naranja) y establecer el ancho. ingrese la descripción de la imagen aquí

  2. cree una vista de contenido (rojo) y establezca el espacio final 10pt en supervista (naranja) Observe las restricciones de espacio final, hay 2 restricciones finales con diferente prioridad. Bajo (= 10) y Alto (<= 10). Esto es importante para evitar la ambigüedad. ingrese la descripción de la imagen aquí

  3. Establezca el ancho de la vista naranja en 0 para ocultar la vista. ingrese la descripción de la imagen aquí


0

Como sugirió no_scene, definitivamente puede hacer esto cambiando la prioridad de la restricción en tiempo de ejecución. Esto fue mucho más fácil para mí porque tenía más de una vista de bloqueo que debía eliminarse.

Aquí hay un fragmento con 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

Así es como realinearía mis uiviews para obtener su solución:

  1. Arrastre y suelte un UIImageView y colóquelo a la izquierda.
  2. Arrastre, suelte una UIView y colóquela a la derecha de UIImageView.
  3. Arrastre y suelte dos UILabels dentro de esa UIView cuyas restricciones iniciales y finales son cero.
  4. Establezca la restricción principal de UIView que contiene 2 etiquetas para supervista en lugar de UIImagView.
  5. SI UIImageView está oculto, establezca la constante de restricción principal en 10 px para supervista. ELSE, establezca la constante de restricción principal en 10 px + UIImageView.width + 10 px.

Creé una regla de oro propia. Siempre que tenga que ocultar / mostrar cualquier uiview cuyas restricciones puedan verse afectadas, agregue todas las subvistas afectadas / dependientes dentro de una uiview y actualice su restricción inicial / final / superior / inferior constante mediante programación.


0

Esta es una vieja pregunta, pero aun así espero que ayude. Procedente de Android, en esta plataforma tiene un método útil isVisiblepara ocultarlo de la vista, pero tampoco tiene el marco considerado cuando la distribución automática dibuja la vista.

usando extension y "extend" uiview, podría hacer una función similar en ios (no estoy seguro de por qué no está en UIKit ya) aquí una implementación en 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

la forma correcta de hacerlo es deshabilitar las restricciones con isActive = false. Sin embargo, tenga en cuenta que la desactivación de una restricción la elimina y la libera, por lo que debe tener salidas fuertes para ellos.


0

La solución más fácil es usar UIStackView (horizontal). Agregar a la vista de pila: primera vista y segunda vista con etiquetas. Luego establezca la propiedad isHidden de la primera vista en falso. Se calcularán todas las restricciones y se actualizarán automáticamente.


Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.