¿Cómo cambiar el tamaño de superview para que se ajuste a todas las subviews con diseño automático?
Mi comprensión del diseño automático es que toma el tamaño de superview y se basa en restricciones y tamaños intrínsecos para calcular las posiciones de subviews.
¿Hay alguna manera de revertir este proceso? Quiero cambiar el tamaño de superview sobre la base de restricciones y tamaños intrínsecos. ¿Cuál es la forma más sencilla de lograrlo?
Tengo vista diseñada en Xcode que uso como cabecera para UITableView
. Esta vista incluye una etiqueta y un botón. El tamaño de la etiqueta varía en función de los datos. Dependiendo de las restricciones, la etiqueta presiona con éxito el botón hacia abajo o si hay una restricción entre el botón y la parte inferior de superview, la etiqueta se comprime.
He encontrado algunas preguntas similares, pero no tienen respuestas buenas y fáciles.
4 answers
La API correcta para usar es UIView systemLayoutSizeFittingSize:
, pasando ya sea UILayoutFittingCompressedSize
o UILayoutFittingExpandedSize
.
Para un UIView
normal usando autolayout esto debería funcionar siempre y cuando sus restricciones sean correctas. Si desea usarlo en un UITableViewCell
(para determinar la altura de la fila, por ejemplo), entonces debe llamarlo contra su celda contentView
y agarrar la altura.
Existen otras consideraciones si tiene uno o más UILabel en su opinión que son multilínea. Para estos es imperativo que se establezca la propiedad preferredMaxLayoutWidth
correctamente tal que la etiqueta proporcione un intrinsicContentSize
correcto, que se utilizará en el cálculo systemLayoutSizeFittingSize's
.
EDITAR: por solicitud, agregando ejemplo de cálculo de altura para una celda de vista de tabla
Usar el diseño automático para calcular la altura de la celda de la tabla no es súper eficiente, pero seguro que es conveniente, especialmente si tiene una celda que tiene un diseño complejo.
Como dije anteriormente, si está utilizando una multilínea UILabel
es imperativo sincronizar el preferredMaxLayoutWidth
con el ancho de la etiqueta. Yo uso una subclase personalizada UILabel
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 artificial 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 guion gráfico. Tenga en cuenta que no hay restricciones de altura/anchura en la etiqueta - que se deducen de la etiqueta intrinsicContentSize
:
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-08-10 17:09:28
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 colocado dentro de ella, entonces el contenido colocado dentro de ella debe tener una relación explícita con la vista contenedora para conducir su altura (o anchura) dinámicamente. "Add subview" no crea esta relación como podría suponer. Usted tiene que elegir qué subview va a conducir la altura y / o ancho del contenedor... más comúnmente cualquier interfaz de usuario elemento que ha colocado en la esquina inferior derecha de la interfaz de usuario general. Aquí hay algunos comentarios de código y en línea para ilustrar el punto.
Tenga en cuenta que esto puede ser de particular valor para aquellos que trabajan con vistas de desplazamiento, ya que es común diseñar en torno a una sola vista de contenido que determina su tamaño (y lo comunica a la vista de desplazamiento) dinámicamente en función de lo que ponga en ella. Buena suerte, espero que esto ayude a alguien.
//
// 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 (i.e. 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-28 16:32:38
Puede hacer esto creando una restricción y conectándola a través de interface builder
Ver explicación: Auto_Layout_Constraints_in_Interface_builder
Raywenderlich beginning-auto-layout
AutolayoutPG Artículos fundamentos de restricciones
@interface ViewController : UIViewController {
IBOutlet NSLayoutConstraint *leadingSpaceConstraint;
IBOutlet NSLayoutConstraint *topSpaceConstraint;
}
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *leadingSpaceConstraint;
Conecte esta salida de restricción con su restricción sub views o conecte la restricción super views también y configúrela de acuerdo con sus requisitos de esta manera
self.leadingSpaceConstraint.constant = 10.0;//whatever you want to assign
Espero que esto lo aclara.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-04-29 14:39:56
Esto se puede hacer para un subview
normal dentro de un UIView
más grande, pero no funciona automáticamente para headerViews
. La altura de un headerView
está determinada por lo que devuelve tableView:heightForHeaderInSection:
, por lo que debe calcular el height
basado en el height
del UILabel
más espacio para el UIButton
y cualquier padding
que necesite. Necesitas 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 cualquier cadena que desee llenar el UILabel
, y el número 281 es el width
del UILabel
(como se configura en Interface Builder
)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-04 11:15:54