iOS Autolayout avec UIScrollview: pourquoi la vue de contenu de scroll view ne remplit pas la vue de scroll view?

le code suivant (appelé dans viewDidLoad) donne un écran entièrement rouge. Je m'attends à ce qu'il soit entièrement écran vert. Pourquoi est-il rouge? Et comment puis-je tout 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]];
41
demandé sur dragosaur 2013-05-30 22:42:12

3 réponses

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

si vous voulez définir la taille contentView à l'écran ou quelque chose comme ça, vous devez ajouter une contrainte entre le contentView et la vue principale, par exemple. C'est, certes, antithétique à mettre du 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 conduite 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]];

Maintenant, vous verrez 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 voulez le contentView pour vous adapter également à la largeur de la vue principale, vous pouvez redéfinir votre viewDict comme cela, 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 des étiquettes multilignes dans scrollviews, que si vous voulez redimensionner en fonction de la quantité de texte, vous devez faire un tour de main, comme:

dispatch_async(dispatch_get_main_queue(), ^{
    randomLabel.preferredMaxLayoutWidth = self.view.bounds.size.width;
});
68
répondu Rob 2017-05-23 12:25:36

J'ai essayé la réponse de Rob semble bien au début. mais! si vous avez aussi activé zoomer , ce code D'Autolayout vous gênera. il continue à redimensionner le contentView quand il zoome. C'est-à-dire que si je zoomais (zoomScale > 1), Je ne pourrais pas faire défiler les parties à l'extérieur de l'écran.

après des jours de combats avec Autolayout, Je n'ai malheureusement pas pu trouver de solution. En fin de compte, il n'est pas même question (wat? ok, désolé). En fin de compte, je viens de supprimer Autolayout sur contentView (utiliser un contentView de taille fixe), puis dans layoutSubviews, j'ajuste le self.scrollView.contentInset . (J'utilise Xcode5, iOS 7)

je sais que ce n'est pas une solution directe à cette question. Mais je veux juste souligner un contournement facile. Il fonctionne parfaitement pour centrer un contentView de taille fixe dans un scrollView, avec seulement 2 lignes de code! Espérons qu'il 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
répondu Hlung 2014-02-14 18:25:04

en général, Auto Layout considère les bords supérieur, gauche, inférieur et droit d'une vue comme les bords visibles. C'est-à-dire, si vous épinglez une vue au bord gauche de sa superview, vous l'épinglez vraiment à la valeur x minimale des limites de la superview. Changer l'origine des limites de la superview ne change pas la position de la vue.

la classe UIScrollView décompose son contenu en changeant l'origine de ses limites. Pour faire ce travail avec la mise en page automatique, le haut, les bords gauche, bas et droit dans une vue scroll signifient maintenant les bords de sa vue de contenu.

les contraintes sur les sous-vues de la vue scroll doivent se traduire par une taille à remplir, qui est alors interprétée comme la taille du contenu de la vue scroll. (Ceci ne doit pas être confondu avec la méthode intrinsicContentSize utilisée pour la mise en page automatique.) Pour dimensionner le cadre de la vue scroll avec la mise en page automatique, les contraintes doivent être soit explicites en ce qui concerne la largeur et la hauteur de la vue scroll, soit les bords de la vue scroll doivent être liés à des vues en dehors de son sous-arbre.

notez que vous pouvez faire apparaître un sous-Affichage de la vue scroll (pas de scroll) au-dessus de l'autre contenu en créant des contraintes entre la vue et une vue en dehors du sous-arbre de la vue scroll, comme la superview de la vue scroll.

voici deux exemples de configuration de la vue scroll, d'abord l'approche mixte, puis l'approche pure

-1
répondu Afzal Siddiqui 2017-11-28 09:16:05