web-dev-qa-db-fra.com

Comment redimensionner superview pour qu'il s'adapte à toutes les sous-vues avec autolayout?

Ma compréhension de l'autolayout est qu'il prend la taille de superview et se base sur des contraintes et des tailles intrinsèques pour calculer les positions des sous-vues.

Y a-t-il un moyen d'inverser ce processus? Je veux redimensionner superview sur la base des contraintes et des tailles intrinsèques. Quel est le moyen le plus simple d'y parvenir?

J'ai vu conçu dans Xcode que j'utilise comme un en-tête pour UITableView. Cette vue comprend une étiquette et un bouton. La taille de l'étiquette diffère selon les données. En fonction des contraintes, l'étiquette enfonce correctement le bouton ou, s'il existe une contrainte entre le bouton et le bas de la vue d'ensemble, l'étiquette est compressée.

J’ai trouvé quelques questions similaires mais elles n’ont pas de réponses faciles et faciles.

140
DAK

L'API correcte à utiliser est UIView systemLayoutSizeFittingSize:, en transmettant UILayoutFittingCompressedSize ou UILayoutFittingExpandedSize.

Pour un UIView normal utilisant autolayout, cela devrait fonctionner tant que vos contraintes sont correctes. Si vous souhaitez l'utiliser sur un UITableViewCell (pour déterminer la hauteur d'une ligne, par exemple), vous devez l'appeler contre votre cellule contentView et saisir la hauteur.

Il existe d'autres considérations si vous avez un ou plusieurs UILabel dans votre vue qui sont multilignes. Pour ceux-ci, il est impératif que la propriété preferredMaxLayoutWidth soit correctement définie de sorte que l'étiquette fournisse un intrinsicContentSize correct, qui sera utilisé dans le calcul systemLayoutSizeFittingSize's.

EDIT: à la demande, ajout d'un exemple de calcul de hauteur pour une cellule de vue tablea

L'utilisation de l'autolayout pour le calcul de la hauteur d'une cellule de tableau n'est pas très efficace, mais elle est pratique, surtout si vous avez une cellule à la disposition complexe.

Comme je l'ai dit ci-dessus, si vous utilisez une multiligne UILabel, il est impératif de synchroniser le preferredMaxLayoutWidth à la largeur de l'étiquette. J'utilise une sous-classe personnalisée UILabel pour le faire:

@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

Voici une sous-classe UITableViewController artificielle démontrant 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

Une cellule personnalisée simple:

#import "TSTableViewCell.h"
#import "TSLabel.h"

@implementation TSTableViewCell
{
    IBOutlet TSLabel* _label;
}

- (void) setText: (NSString *) text
{
    _label.text = text;
}

@end

Et, voici une photo des contraintes définies dans le Storyboard. Notez qu'il n'y a pas de contrainte hauteur/largeur sur l'étiquette - celles-ci sont déduites de la variable intrinsicContentSize de l'étiquette:

enter image description here

149
TomSwift

Le commentaire d'Eric Baker m'a fait comprendre l'idée de base que pour que la taille de la vue soit déterminée par le contenu qui y est placé, celui-ci doit avoir une relation explicite avec la vue conduire sa hauteur (ou largeur) dynamiquement. "Ajouter une sous-vue" ne crée pas cette relation comme vous pourriez le supposer. Vous devez choisir la sous-vue qui déterminera la hauteur et/ou la largeur du conteneur ... le plus souvent, quel que soit l'élément d'interface utilisateur que vous avez placé dans le coin inférieur droit de votre interface utilisateur globale. Voici du code et des commentaires en ligne pour illustrer ce point.

Notez que cela peut être particulièrement intéressant pour ceux qui travaillent avec les vues de défilement, car il est courant de concevoir une vue de contenu unique qui détermine sa taille (et la communique à la vue de défilement) de manière dynamique en fonction de ce que vous y insérez. Bonne chance, espérons que cela aidera quelqu'un là-bas.

//
//  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

How to resize superview to fit all subviews with autolayout.png

29
John Erck

Vous pouvez le faire en créant une contrainte et en la connectant via le constructeur d'interface.

Voir explication: Auto_Layout_Constraints_in_Interface_Builder

raywenderlich begin-auto-layout

Principes fondamentaux de la contrainte des articles AutolayoutPG

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

connectez cette prise Constraint à vos vues secondaires Constraint ou connectez des vues superbes Constraint également et réglez-la en fonction de vos besoins, comme ceci

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

J'espère que cela le clarifie.

3
chandan