¿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.

Author: giampaolo, 2013-08-08

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:

introduzca la descripción de la imagen aquí

 147
Author: TomSwift,
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

Cómo cambiar el tamaño de superview para adaptarse a todas las subviews con diseño automático.png

 28
Author: John Erck,
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.

 3
Author: chandan,
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)

 -1
Author: rdelmar,
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