¿Cómo cambiar el tamaño de la supervista para que se ajuste a todas las subvistas con autolayout?

Mi comprensión de autolayout es que toma el tamaño de superview y base en restricciones y tamaños intrínsecos, calcula las posiciones de las subvistas.

¿Hay alguna manera de revertir este proceso? Quiero cambiar el tamaño de la supervista en la base de restricciones y tamaños intrínsecos. ¿Cuál es la forma más simple de lograr esto?

Tengo una vista diseñada en Xcode que uso como encabezado para UITableView . Esta vista incluye una etiqueta y un botón. El tamaño de la etiqueta varía según los datos. Dependiendo de las restricciones, la etiqueta empuja con éxito el botón hacia abajo o si hay una restricción entre el botón y la parte inferior de la supervista, la etiqueta se comprime.

He encontrado algunas preguntas similares pero no tienen respuestas buenas y fáciles.

La API correcta para usar es UIView systemLayoutSizeFittingSize: pasando UILayoutFittingCompressedSize o UILayoutFittingExpandedSize .

Para una UIView normal que usa autolayout, esto debería funcionar siempre que tus restricciones sean correctas. Si desea usarlo en una UITableViewCell (para determinar la altura de la fila, por ejemplo), debe llamarlo a su celda contentView y obtener la altura.

Existen consideraciones adicionales si tiene uno o más UILabel en su vista que son multilínea. Para estos, es imperioso que la propiedad preferredMaxLayoutWidth se establezca correctamente de modo que la etiqueta proporcione un intrinsicContentSize correcto, que se utilizará en systemLayoutSizeFittingSize's cálculo systemLayoutSizeFittingSize's .

EDITAR: por solicitud, agregando un ejemplo de cálculo de altura para una celda de vista de tabla

El uso del ajuste automático para el cálculo de la altura de la celda de la tabla no es muy eficiente, pero es seguro, especialmente si tiene una celda que tiene un diseño complejo.

Como dije anteriormente, si está utilizando un UILabel , es imperativo sincronizar el UILabel preferredMaxLayoutWidth con el ancho de la etiqueta. Yo uso una subclase UILabel personalizada para hacer esto:

 @implementation TSLabel - (void) layoutSubviews { [super layoutSubviews]; if ( self.numberOfLines == 0 ) { if ( self.preferredMaxLayoutWidth != self.frame.size.width ) { self.preferredMaxLayoutWidth = self.frame.size.width; [self setNeedsUpdateConstraints]; } } } - (CGSize) intrinsicContentSize { CGSize s = [super intrinsicContentSize]; if ( self.numberOfLines == 0 ) { // found out that sometimes intrinsicContentSize is 1pt too short! s.height += 1; } return s; } @end 

Aquí hay una subclase ideada de UITableViewController que demuestra heightForRowAtIndexPath:

 #import "TSTableViewController.h" #import "TSTableViewCell.h" @implementation TSTableViewController - (NSString*) cellText { return @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; } #pragma mark - Table view data source - (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView { return 1; } - (NSInteger) tableView: (UITableView *)tableView numberOfRowsInSection: (NSInteger) section { return 1; } - (CGFloat) tableView: (UITableView *) tableView heightForRowAtIndexPath: (NSIndexPath *) indexPath { static TSTableViewCell *sizingCell; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sizingCell = (TSTableViewCell*)[tableView dequeueReusableCellWithIdentifier: @"TSTableViewCell"]; }); // configure the cell sizingCell.text = self.cellText; // force layout [sizingCell setNeedsLayout]; [sizingCell layoutIfNeeded]; // get the fitting size CGSize s = [sizingCell.contentView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize]; NSLog( @"fittingSize: %@", NSStringFromCGSize( s )); return s.height; } - (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath { TSTableViewCell *cell = (TSTableViewCell*)[tableView dequeueReusableCellWithIdentifier: @"TSTableViewCell" ]; cell.text = self.cellText; return cell; } @end 

Una celda personalizada simple:

 #import "TSTableViewCell.h" #import "TSLabel.h" @implementation TSTableViewCell { IBOutlet TSLabel* _label; } - (void) setText: (NSString *) text { _label.text = text; } @end 

Y, aquí hay una imagen de las restricciones definidas en el Storyboard. Tenga en cuenta que no hay restricciones de altura / ancho en la etiqueta, que se infieren a partir de la etiqueta intrinsicContentSize :

enter image description here

El comentario de Eric Baker me llevó a la idea central de que para que una vista tenga su tamaño determinado por el contenido que se coloca dentro de él, entonces el contenido colocado dentro de él debe tener una relación explícita con la vista que lo contiene para impulsar su altura (o ancho) dinámicamente . “Agregar subvista” no crea esta relación como podría suponer. Debe elegir qué subvista va a conducir la altura y / o el ancho del contenedor … más comúnmente cualquier elemento de UI que haya colocado en la esquina inferior derecha de su UI general. Aquí hay algunos códigos y comentarios en línea para ilustrar el punto.

Tenga en cuenta que esto puede ser de particular valor para quienes trabajan con vistas de desplazamiento, ya que es común diseñar alrededor de una única vista de contenido que determine su tamaño (y lo comunique a la vista de desplazamiento) de forma dinámica en función de lo que coloque en él. Buena suerte, espero que esto ayude a alguien por ahí.

 // // ViewController.m // AutoLayoutDynamicVerticalContainerHeight // #import "ViewController.h" @interface ViewController () @property (strong, nonatomic) UIView *contentView; @property (strong, nonatomic) UILabel *myLabel; @property (strong, nonatomic) UILabel *myOtherLabel; @end @implementation ViewController - (void)viewDidLoad { // INVOKE SUPER [super viewDidLoad]; // INIT ALL REQUIRED UI ELEMENTS self.contentView = [[UIView alloc] init]; self.myLabel = [[UILabel alloc] init]; self.myOtherLabel = [[UILabel alloc] init]; NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_contentView, _myLabel, _myOtherLabel); // TURN AUTO LAYOUT ON FOR EACH ONE OF THEM self.contentView.translatesAutoresizingMaskIntoConstraints = NO; self.myLabel.translatesAutoresizingMaskIntoConstraints = NO; self.myOtherLabel.translatesAutoresizingMaskIntoConstraints = NO; // ESTABLISH VIEW HIERARCHY [self.view addSubview:self.contentView]; // View adds content view [self.contentView addSubview:self.myLabel]; // Content view adds my label (and all other UI... what's added here drives the container height (and width)) [self.contentView addSubview:self.myOtherLabel]; // LAYOUT // Layout CONTENT VIEW (Pinned to left, top. Note, it expects to get its vertical height (and horizontal width) dynamically based on whatever is placed within). // Note, if you don't want horizontal width to be driven by content, just pin left AND right to superview. [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_contentView]" options:0 metrics:0 views:viewsDictionary]]; // Only pinned to left, no horizontal width yet [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_contentView]" options:0 metrics:0 views:viewsDictionary]]; // Only pinned to top, no vertical height yet /* WHATEVER WE ADD NEXT NEEDS TO EXPLICITLY "PUSH OUT ON" THE CONTAINING CONTENT VIEW SO THAT OUR CONTENT DYNAMICALLY DETERMINES THE SIZE OF THE CONTAINING VIEW */ // ^To me this is what's weird... but okay once you understand... // Layout MY LABEL (Anchor to upper left with default margin, width and height are dynamic based on text, font, etc (ie UILabel has an intrinsicContentSize)) [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_myLabel]" options:0 metrics:0 views:viewsDictionary]]; [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[_myLabel]" options:0 metrics:0 views:viewsDictionary]]; // Layout MY OTHER LABEL (Anchored by vertical space to the sibling label that comes before it) // Note, this is the view that we are choosing to use to drive the height (and width) of our container... // The LAST "|" character is KEY, it's what drives the WIDTH of contentView (red color) [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_myOtherLabel]-|" options:0 metrics:0 views:viewsDictionary]]; // Again, the LAST "|" character is KEY, it's what drives the HEIGHT of contentView (red color) [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_myLabel]-[_myOtherLabel]-|" options:0 metrics:0 views:viewsDictionary]]; // COLOR VIEWS self.view.backgroundColor = [UIColor purpleColor]; self.contentView.backgroundColor = [UIColor redColor]; self.myLabel.backgroundColor = [UIColor orangeColor]; self.myOtherLabel.backgroundColor = [UIColor greenColor]; // CONFIGURE VIEWS // Configure MY LABEL self.myLabel.text = @"HELLO WORLD\nLine 2\nLine 3, yo"; self.myLabel.numberOfLines = 0; // Let it flow // Configure MY OTHER LABEL self.myOtherLabel.text = @"My OTHER label... This\nis the UI element I'm\narbitrarily choosing\nto drive the width and height\nof the container (the red view)"; self.myOtherLabel.numberOfLines = 0; self.myOtherLabel.font = [UIFont systemFontOfSize:21]; } @end 

Cómo cambiar el tamaño de la supervista para que se ajuste a todas las subvistas con autolayout.png

Puede hacer esto creando una restricción y conectándola a través del constructor de interfaz

Ver explicación: Auto_Layout_Constraints_in_Interface_Builder

raywenderlich starting-auto-layout

AutolayoutPG Articles constraint Fundamentos

 @interface ViewController : UIViewController { IBOutlet NSLayoutConstraint *leadingSpaceConstraint; IBOutlet NSLayoutConstraint *topSpaceConstraint; } @property (weak, nonatomic) IBOutlet NSLayoutConstraint *leadingSpaceConstraint; 

conecte esta toma de restricción con su restricción de vistas secundarias o conecte las superpuestas de restricción también y configúrelo de acuerdo con sus requisitos como este

  self.leadingSpaceConstraint.constant = 10.0;//whatever you want to assign 

Espero que esto lo aclare.

Esto se puede hacer para una subview normal dentro de una UIView más UIView , pero no funciona automáticamente para headerViews . La altura de un headerView está determinada por lo que devuelve tableView:heightForHeaderInSection: por lo tanto, debe calcular la height función del height del espacio UILabel plus para el UIButton y cualquier padding que necesite. Debes hacer algo como esto:

 -(CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section { NSString *s = self.headeString[indexPath.section]; CGSize size = [s sizeWithFont:[UIFont systemFontOfSize:17] constrainedToSize:CGSizeMake(281, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping]; return size.height + 60; } 

Aquí headerString es la cadena que desee para rellenar el UILabel , y el número 281 es el width del UILabel (como se configuró en el Interface Builder )