web-dev-qa-db-fra.com

Utiliser la mise en page automatique pour définir UIView dynamique pour correspondre à la vue du conteneur

J'ai une UIView dans IB qui contient une autre UIView, que j'utilise comme vue de conteneur. Dans le code, je crée trois vues différentes, puis j'anime la vue appropriée dans la vue du conteneur en fonction de l'état de l'application. Une seule des trois vues différentes sera valide à tout moment. Le problème est que lorsque j'exécute différents iPhone dans le simulateur, mes nouvelles sous-vues ne sont pas mises à l'échelle pour correspondre à la vue du conteneur. J'utilise la mise en page automatique. À des fins de test, j'ai configuré mes sous-vues pour n'être qu'un gros bouton dont tous les bords sont limités à la vue d'ensemble. Et la vue du conteneur a également ses bords limités à sa vue d'ensemble. Ce que je veux, c'est que la sous-vue corresponde à la vue du conteneur. c'est-à-dire que le bouton étire toute la taille de la vue du conteneur. Lorsqu'elle est exécutée sur différents iPhones, la taille de la vue du conteneur et, par conséquent, la sous-vue doivent être proportionnelles aux tailles d'écran de l'iPhone.

Vous trouverez ci-dessous le code que j'utilise pour lancer ma sous-vue et configurer ses contraintes par rapport à la vue du conteneur.

UIView *containerView = self.subView;
UIView *newSubview = [[mySubview alloc] init];
[newSubview setTranslatesAutoresizingMaskIntoConstraints:NO];
[self.containerView addSubview:newSubview];

[self.containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.containerView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0]];

[self.containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.containerView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0]];

[self.containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.containerView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0]];

[self.containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.containerView attribute:NSLayoutAttributeHeight multiplier:1.0 constant:0]];  

Je n'arrive pas à faire fonctionner ça. Je suis assez nouveau pour la mise en page automatique et je ne suis pas sûr de ce que je fais mal et je voudrais arrêter de me cogner la tête contre ce mur. Toute aide serait formidable. :)


************* INFORMATION ADDITIONNELLE **************


Désolé, je n'ai pas énoncé mon problème aussi clairement que je l'aurais pu. Voici donc plus d'informations avec des captures d'écran. Tout d'abord, je vais décrire ce que j'ai fait au niveau du code.

Dans didFinishLaunchingWithOptions dans AppDelegate.m, je crée MyViewController comme ceci,

self.myViewController = [[MyViewController alloc] initWithNibName:@"MyViewController" bundle:nil];

Dans viewDidLoad dans MyViewController.m, je crée mySubview et l'ajoute à mon containerView et crée des contraintes pour cela comme ceci,

UIView *containerView = self.containerView;
UIView *mySubview = [[MySubview alloc] init];
[mySubview setTranslatesAutoresizingMaskIntoConstraints:NO];
[containerView addSubview:mySubview];

NSDictionary *views = NSDictionaryOfVariableBindings(mySubview);
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[mySubview]|"
                                                                 options:0
                                                                 metrics:nil
                                                                   views:views]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[mySubview]|"
                                                                  options:0
                                                                  metrics:nil
                                                                    views:views]];

Et enfin dans init dans MySubview.h j'ajoute la plume comme une sous-vue comme celle-ci,

- (id)init
{
    if(self = [super init])
    {
        NSArray *nibArray = [[NSBundle mainBundle]loadNibNamed:@"MySubview" owner:self options:nil];
        [self addSubview:[nibArray objectAtIndex:0]];
    }
    return self;
}

Quelques choses à noter qui peuvent aider,

Dans MyViewController.xib, j'ai une UIView que j'utilise comme containerView. Il possède un IBOutlet vers UIView * containerView et est celui référencé ci-dessus. Les seules contraintes que j'ai pour le containerView dans IB sont, l'espace de début, de fin et inférieur à Superview et l'espace supérieur à Superview = 44.

Pour MySubview.xib, la hauteur et la largeur sont de 300 (aucune contrainte n'est utilisée pour la hauteur ou la largeur). Je pense que ces tailles ne devraient pas avoir d'importance car mySubview est censé être contraint à containerView.

Dans MySubview.xib, j'ai 3 objets, topButton: height = 29, middleView: height = 242 et bottomButton: height = 29. (voir l'image jointe) TopButton a des contraintes de début, de fin et de haut en Superview et une contrainte de bas en middleView. MiddleView a des contraintes de début et de fin pour Superview et une contrainte de haut en hautButton et une contrainte de bas en basButton. Enfin, bottomButton a des contraintes de début, de fin et de bas pour Superview et une contrainte de haut en milieu.

Ce que je veux, c'est que mySubview soit mis à l'échelle pour s'adapter au containerView puisque des contraintes ont été créées et ajoutées, mais à la place, mySubview devient très volumineux et coupe containerViewView.

Voici quelques captures d'écran:

MyViewController.xib, le rectangle bleu sous le titre est ma vue de conteneur et a le conteneur IBOutlet containerView.

http://i33.tinypic.com/14o3lmd.png

MySubview.xib

http://i37.tinypic.com/344ukk3.png

Et enfin, le résultat, qui est incorrect.

http://i34.tinypic.com/nbyqua.png

Au lieu de cela, je voudrais cela, que j'ai truqué juste pour obtenir la capture d'écran.

Sur iPhone 4,

http://tinypic.com/r/w9fp5e/4

Sur iPhone 5,

http://tinypic.com/r/2z8nldv/4

Comme vous pouvez le voir sur les fausses captures d'écran, mySubview s'adapte à la ContainViewView, même si ContainerView évolue un peu pour s'adapter aux différentes tailles d'écran du téléphone.

J'espère que je ne suis pas allé trop loin avec l'info. Toute aide serait formidable. J'ai l'impression d'être proche, mais il me manque une étape clé. Grrrrr.

30
Feta

Quelques réflexions:

  1. Cela semble fondamentalement bien, à part une bizarrerie de nom de variable: votre variable locale, containerView est égale à self.subView, mais toutes vos autres lignes font référence à une variable différente, une propriété de classe, self.containerView. Avez-vous omis une ligne où vous avez défini cette propriété de classe containerView? Mais quand j'ai corrigé cela, votre code a bien fonctionné pour moi.

  2. Assurez-vous que vous n'essayez pas de regarder frame immédiatement après avoir défini les contraintes, car la modification ne sera pas encore reflétée dans les paramètres frame. Vous pouvez faire un [containerView layoutIfNeeded]; si vous voulez le forcer à tout relayer en fonction des contraintes. De plus, si vous souhaitez confirmer les paramètres frame, il est préférable de différer la recherche de ces valeurs jusqu'à ce que viewDidAppear (c.-à-d. viewDidLoad soit trop tôt dans le processus de construction de la vue).

  3. Un tweak mineur sur votre code (et sans rapport avec votre problème), mais lorsque je définis les contraintes dans une vue de conteneur, je vais souvent définir non seulement NSLayoutAttributeTop et NSLayoutAttributeLeading, comme vous l'avez fait , mais aussi NSLayoutAttributeBottom et NSLayoutAttributeTrailing (plutôt que NSLayoutAttributeWidth et NSLayoutAttributeHeight). Il accomplit la même chose que votre code, mais lorsque vous utilisez des valeurs non nulles, c'est une fraction plus intuitive.

    Quoi qu'il en soit, je viens de faire le code suivant, permutation de la vôtre, et cela fonctionne très bien:

    - (IBAction)didTouchUpInsideAddView:(id)sender
    {
        UIView *containerView = self.containerView;
        UIView *newSubview = [[UIView alloc] initWithFrame:CGRectZero]; // initializing with CGRectZero so we can see it change
        newSubview.translatesAutoresizingMaskIntoConstraints = NO;
        newSubview.backgroundColor = [UIColor lightGrayColor];
        [containerView addSubview:newSubview];
    
        [containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview
                                                                  attribute:NSLayoutAttributeTop
                                                                  relatedBy:NSLayoutRelationEqual
                                                                     toItem:containerView
                                                                  attribute:NSLayoutAttributeTop
                                                                 multiplier:1.0
                                                                   constant:0.0]];
    
        [containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview
                                                                  attribute:NSLayoutAttributeLeading
                                                                  relatedBy:NSLayoutRelationEqual
                                                                     toItem:containerView
                                                                  attribute:NSLayoutAttributeLeading
                                                                 multiplier:1.0
                                                                   constant:0.0]];
    
        [containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview
                                                                  attribute:NSLayoutAttributeBottom
                                                                  relatedBy:NSLayoutRelationEqual
                                                                     toItem:containerView
                                                                  attribute:NSLayoutAttributeBottom
                                                                 multiplier:1.0
                                                                   constant:0.0]];
    
        [containerView addConstraint:[NSLayoutConstraint constraintWithItem:newSubview
                                                                  attribute:NSLayoutAttributeTrailing
                                                                  relatedBy:NSLayoutRelationEqual
                                                                     toItem:containerView
                                                                  attribute:NSLayoutAttributeTrailing
                                                                 multiplier:1.0
                                                                   constant:0.0]];
    
        // the frame is still `CGRectZero` at this point
    
        NSLog(@"newSubview.frame before = %@", NSStringFromCGRect(newSubview.frame));
    
        // if not doing this in `viewDidLoad` (e.g. you're doing this in
        // `viewDidAppear` or later), you can force `layoutIfNeeded` if you want
        // to look at `frame` values. Generally you don't need to do this unless
        // manually inspecting `frame` values or when changing constraints in a
        // `animations` block of `animateWithDuration`.
    
        [containerView layoutIfNeeded];
    
        // everything is ok here
    
        NSLog(@"containerView.bounds after = %@", NSStringFromCGRect(containerView.bounds));
        NSLog(@"newSubview.frame after = %@", NSStringFromCGRect(newSubview.frame));
    }
    
  4. Vous pouvez simplifier un peu ce code en utilisant langage de format visuel , par exemple:

    NSDictionary *views = NSDictionaryOfVariableBindings(newSubview);
    
    [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[newSubview]|"
                                                                          options:0
                                                                          metrics:nil
                                                                            views:views]];
    [containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[newSubview]|"
                                                                          options:0
                                                                          metrics:nil
                                                                            views:views]];
    

    Je trouve plus facile d'obtenir les bonnes contraintes en utilisant un langage de format visuel. Un peu moins sujet aux erreurs (du moins pour moi). Il existe cependant certaines contraintes qui ne peuvent pas être représentées dans un langage de format visuel, auquel cas je reviens à la syntaxe que vous décrivez.

  5. Dans votre question révisée, vous nous montrez une méthode init pour votre sous-vue, qui fait une autre addSubview. Vous devez également définir des contraintes. En bout de ligne, où que vous fassiez addSubview, vous devez définir vos contraintes, par exemple.

    - (id)init
    {
        if(self = [super init])
        {
            NSArray *nibArray = [[NSBundle mainBundle]loadNibNamed:@"MySubview" owner:self options:nil];
            UIView *subview = [nibArray objectAtIndex:0];
            subview.translatesAutoresizingMaskIntoConstraints = NO;
    
            [self addSubview:subview];
    
            NSDictionary *views = NSDictionaryOfVariableBindings(subview);
            [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[subview]|"
                                                                         options:0
                                                                         metrics:nil
                                                                           views:views]];
            [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[subview]|"
                                                                         options:0
                                                                         metrics:nil
                                                                           views:views]];
        }
        return self;
    }
    
58
Rob

Dans le problème, vous dites "mes nouvelles sous-vues ne sont pas mises à l'échelle pour correspondre à la vue du conteneur" - mais d'après la description, je pense qu'elles le sont - le problème est que votre vue du conteneur n'a pas de taille fixe.

Je pense que si vous définissez votre vue conteneur pour avoir une largeur et une hauteur fixes qui devraient le faire, vous devrez peut-être également appeler

[self.containerView layoutSubviews]

pour forcer un redimensionnement après la mise à jour des contraintes.

Je vous suggère également de passer à la mise en forme basée sur du texte, vous pouvez remplacer vos lignes ci-dessus par quelque chose comme "H: | [newSubview] |" et "V: | [newSubview] |"

1
James