web-dev-qa-db-fra.com

Mise en page automatique iOS avec UIScrollview: Pourquoi la vue de contenu de la vue de défilement ne remplit-elle pas la vue de défilement?

Le code suivant (appelé dans viewDidLoad) donne un écran entièrement rouge. Je m'attendrais à ce que ce soit un écran entièrement vert. Pourquoi est-il rouge? Et comment puis-je le rendre vert?

UIScrollView* scrollView = [UIScrollView new];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.backgroundColor = [UIColor redColor];
[self.view addSubview:scrollView];

UIView* contentView = [UIView new];
contentView.translatesAutoresizingMaskIntoConstraints = NO;
contentView.backgroundColor = [UIColor greenColor];
[scrollView addSubview:contentView];

NSDictionary* viewDict = NSDictionaryOfVariableBindings(scrollView,contentView);

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:0 views:viewDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics:0 views:viewDict]];

[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:0 views:viewDict]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|" options:0 metrics:0 views:viewDict]];
43
dragosaur

Les contraintes avec les vues de défilement fonctionnent légèrement différemment qu'avec les autres vues. Les contraintes entre contentView et son superview (le scrollView) sont au scrollView du contentSize, pas à son frame. Cela peut sembler déroutant, mais il est en fait très utile, ce qui signifie que vous n'avez jamais à ajuster le contentSize, mais plutôt que le contentSize s'ajustera automatiquement pour s'adapter à votre contenu. Ce comportement est décrit dans Note technique TN2154 .

Si vous voulez définir la taille contentView à l'écran ou quelque chose comme ça, vous devrez ajouter une contrainte entre contentView et la vue principale, par exemple. C'est, certes, contraire à la mise en contenu dans le scrollview, donc je ne conseillerais probablement pas cela, mais cela peut être fait.


Pour illustrer ce concept, que la taille de contentView sera déterminée par son contenu, et non par le bounds du scrollView, ajoutez une étiquette à votre contentView:

UIScrollView* scrollView = [UIScrollView new];
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.backgroundColor = [UIColor redColor];
[self.view addSubview:scrollView];

UIView* contentView = [UIView new];
contentView.translatesAutoresizingMaskIntoConstraints = NO;
contentView.backgroundColor = [UIColor greenColor];
[scrollView addSubview:contentView];

UILabel *randomLabel = [[UILabel alloc] init];
randomLabel.text = @"this is a test";
randomLabel.translatesAutoresizingMaskIntoConstraints = NO;
randomLabel.backgroundColor = [UIColor clearColor];
[contentView addSubview:randomLabel];

NSDictionary* viewDict = NSDictionaryOfVariableBindings(scrollView, contentView, randomLabel);

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics:0 views:viewDict]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics:0 views:viewDict]];

[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[contentView]|" options:0 metrics:0 views:viewDict]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[contentView]|" options:0 metrics:0 views:viewDict]];

[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[randomLabel]-|" options:0 metrics:0 views:viewDict]];
[contentView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[randomLabel]-|" options:0 metrics:0 views:viewDict]];

Vous verrez maintenant que le contentView (et, par conséquent, le contentSize du scrollView) sont ajustés pour s'adapter à l'étiquette avec des marges standard. Et parce que je n'ai pas spécifié la largeur/hauteur de l'étiquette, cela s'ajustera en fonction du texte que vous mettez dans cette étiquette.


Si vous souhaitez que le contentView s'ajuste également à la largeur de la vue principale, vous pouvez redéfinir votre viewDict comme tel, puis ajouter ces contraintes supplémentaires (en plus de toutes les autres, ci-dessus ):

UIView *mainView = self.view;

NSDictionary* viewDict = NSDictionaryOfVariableBindings(scrollView, contentView, randomLabel, mainView);

[mainView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[contentView(==mainView)]" options:0 metrics:0 views:viewDict]];

Il y a un problème connu (bug?) avec les étiquettes multilignes dans les vues de défilement, si vous voulez qu'il redimensionne en fonction de la quantité de texte, vous devez faire un tour de passe-passe, comme:

dispatch_async(dispatch_get_main_queue(), ^{
    randomLabel.preferredMaxLayoutWidth = self.view.bounds.size.width;
});
71
Rob

J'ai essayé la réponse de Rob semble bien au premier abord. MAIS! si vous avez également activé zoom, ce code de mise en page automatique vous gênera. Il redimensionne le contentView lors des zooms. Autrement dit, si j'ai zoomé (zoomScale> 1), je ne pourrai pas faire défiler les parties en dehors de l'écran.

Après des jours de combats avec Autolayout, je n'ai malheureusement pas trouvé de solution. En fin de compte, cela n'a même pas d'importance (wat? Ok, désolé). En fin de compte, je supprime simplement Autolayout sur contentView (utilisez un contentView de taille fixe), puis dans layoutSubviews, j'ajuste leself.scrollView.contentInset. (J'utilise Xcode5, iOS 7)

Je sais que ce n'est pas une solution directe à cette question. Mais je veux juste souligner une solution de contournement facile. Il fonctionne parfaitement pour centrer un contentView de taille fixe dans un scrollView, avec seulement 2 lignes de code! J'espère que cela peut aider quelqu'un;)

- (void)layoutSubviews {
    [super layoutSubviews];

    // move contentView to center of scrollView by changing contentInset,
    // because I CANNOT set this with AUTOLAYOUT!!!
    CGFloat topInset = (self.frame.size.height - self.contentView.frame.size.height)/2;
    self.scrollView.contentInset = UIEdgeInsetsMake(topInset, 0, 0, 0);
}
1
Hlung