web-dev-qa-db-fra.com

Mise en page automatique UIScrollView avec sous-vues avec des hauteurs dynamiques

Je rencontre des problèmes avec UIScrollView lors de l'utilisation de contraintes de présentation automatiques . J'ai la hiérarchie d'affichage suivante, avec des contraintes définies via IB:

- ScrollView (leading, trailing, bottom and top spaces to superview)
-- ContainerView (leading, trailing, bottom and top spaces to superview)
--- ViewA (full width, top of superview)
--- ViewB (full width, below ViewA)
--- Button (full width, below ViewB)

Les ViewA et ViewB ont des hauteurs initiales de 200 points, mais vous pouvez les agrandir verticalement jusqu'à une hauteur de 400 points en cliquant dessus. ViewA et ViewB sont développés en mettant à jour leur contrainte de hauteur (de 200 à 400). Voici l'extrait correspondant:

if(self.contentVisible) {
    heightConstraint.constant -= ContentHeight;
    // + additional View's internal constraints update to hide additional content 
    self.contentVisible = NO;
} else {
    heightConstraint.constant += ContentHeight;
    // + additional View's internal constraints update to show additional content
    self.contentVisible = YES;
}

[self.view setNeedsUpdateConstraints];
[UIView animateWithDuration:.25f animations:^{
    [self.view layoutIfNeeded];
}];

Mon problème est que si les deux vues sont développées, je dois pouvoir faire défiler pour voir tout le contenu, et pour le moment, le défilement ne fonctionne pas. Comment puis-je gérer la mise à jour de la vue de défilement en utilisant des contraintes pour refléter les modifications des hauteurs ViewA et ViewB?

La seule solution à laquelle je puisse penser jusqu'ici consiste à définir manuellement la hauteur de ContainerView après l'animation, qui correspond à la somme des hauteurs de ViewA + ViewB + Button. Mais je crois qu'il y a une meilleure solution?

Merci

38
lukas

J'utilise la structure pure comme suit

-view
  -scrollView
    -view A
    -view B
    -Button

Assurez-vous que Button (LA DERNIÈRE vue) est soumis à une contrainte (espacement vertical du bas à superview, qui est la vue défilement), dans ce cas, quels que soient les changements pour votre vue A et la vue B, hauteur de scrollView sera changé en conséquence.

Je fais référence à ce site en ligne livre .

Lisez simplement la section "Créer une vue de défilement", vous devriez avoir une idée. 

Le problème était similaire: je créais une vue détaillée et l'utilisation d'Interface Builder avec la disposition automatique convenait parfaitement à la tâche!

Bonne chance!

(Ressources supplémentaires:

Discussion sur le débordement de pile concernant la mise en page automatique pour la vue de défilement .

iOS 6 a un Notes de publication concernant le support de la disposition automatique pour UIScrollView.

Explication gratuite en ligne livre iOS à propos de l'affichage par défilement. Cela m'a beaucoup aidé!

58
lorixx

Supposons que nous ayons une hiérarchie comme celle-ci (Label1 est une sous-vue de ContentView; ContentView est une sous-vue de ScrollView, ScrollView est une sous-vue de la vue du contrôleur de vue):

ViewController's View ScrollView ContentView Label1 Label2 Label3

ScrollView est contraint avec l'autolayout de manière normale à la vue du contrôleur de la vue.

ContentView est épinglé en haut/gauche/droite/en bas pour faire défiler l'affichage. Cela signifie que vous avez des contraintes qui contraignent les bords supérieur/inférieur/avant/arrière de ContentView à être identiques aux mêmes bords de la vue ScrollView. Voici une clé: ces contraintes concernent la taille du contenu de ScrollView, et non sa taille d'image telle qu'elle est affichée dans la vue du contrôleur View. Donc, il n’est pas indiqué à ContentView d’avoir la même taille que le cadre ScrollView affiché, mais plutôt à Scrollview de dire que ContentView correspond à son contenu. Par conséquent, si contentview est plus grande que le cadre ScrollView, le défilement s’effectue, comme pour définir scrollView.contentSize que scrollView.frame rend le contenu défilable.

Voici une autre clé: vous devez maintenant disposer de suffisamment de contraintes entre ContentView, Label1-3 et toute autre fonction que Scrollview pour ContentView afin de pouvoir déterminer la largeur et la hauteur de ces contraintes.

Ainsi, par exemple, si vous souhaitez un ensemble d'étiquettes à défilement vertical, définissez une contrainte pour que la largeur de ContentView soit égale à la largeur de la vue ViewController, qui prend en charge la largeur. Pour prendre soin de la hauteur, épinglez Label1 en haut sur ContentView, Label2 en haut à Label1 en bas, Label3 en haut à Label2 en bas et enfin (et surtout) épinglez le bas de Label3 en bas à ContentView. Maintenant, il dispose de suffisamment d'informations pour calculer la hauteur de ContentView.

J'espère que cela donnera à quelqu'un un indice, car j'ai lu les articles ci-dessus et je ne pouvais toujours pas comprendre comment rendre correctement les contraintes de largeur et de hauteur de ContentView. Ce qui me manquait, c'était d'épingler le bas de Label3 au bas de ContentView, sinon comment ContentView pourrait-il savoir quelle est sa hauteur (car Label3 serait alors flottant, et il n'y aurait aucune contrainte pour indiquer à ContentView où se trouve sa position inférieure y).

25
Doug Voss

Voici un exemple de la manière dont j'ai présenté UIScrollView avec autolayout pur avec une vue conteneur. J'ai commenté pour le rendre plus clair:

le conteneur est un UIView standard et le corps est un UITextView

- (void)viewDidLoad
{
    [super viewDidLoad];

    //add scrollview
    [self.view addSubview:self.scrollView];

    //add container view
    [self.scrollView addSubview:self.container];

    //body as subview of container (body size is undetermined)
    [self.container addSubview:self.body];

    NSDictionary *views = @{@"scrollView" : self.scrollView, @"container" : self.container, @"body" : self.body};
    NSDictionary *metrics = @{@"margin" : @(100)};

    //constrain scrollview to superview, pin all edges
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:kNilOptions metrics:metrics views:views]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:kNilOptions metrics:metrics views:views]];

    //pin all edges of the container view to the scrollview (i've given it a horizonal margin as well for my purposes)
    [self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[container]|" options:kNilOptions metrics:metrics views:views]];
    [self.scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-margin-[container]-margin-|" options:kNilOptions metrics:metrics views:views]];

    //the container view must have a defined width OR height, here i am constraining it to the frame size of the scrollview, not its bounds
    //the calculation for constant is so that it's the width of the scrollview minus the margin * 2
    [self.scrollView addConstraint:[NSLayoutConstraint constraintWithItem:self.container attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.scrollView attribute:NSLayoutAttributeWidth multiplier:1.0f constant:-([metrics[@"margin"] floatValue] * 2)]];

    //now as the body grows vertically it will force the container to grow because it's trailing Edge is pinned to the container's bottom Edge
    //it won't grow the width because the container's width is constrained to the scrollview's frame width
    [self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[body]|" options:kNilOptions metrics:metrics views:views]];
    [self.container addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[body]|" options:kNilOptions metrics:metrics views:views]];
}

Dans mon exemple, "body" est une UITextView, mais peut être autre chose. Si vous utilisez également une UITextView, notez que, pour qu’elle se développe verticalement, elle doit avoir une contrainte de hauteur qui est définie dans viewDidLayoutSubviews. Ajoutez donc la contrainte suivante dans viewDidLoad et conservez une référence à celle-ci:

self.bodyHeightConstraint = [NSLayoutConstraint constraintWithItem:self.body attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:nil multiplier:1.0f constant:100.0f];
[self.container addConstraint:self.bodyHeightConstraint];

Ensuite, dans viewDidLayoutSubviews, calculez la hauteur et mettez à jour la constante de la contrainte:

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];

    [self.bodyHeightConstraint setConstant:[self.body sizeThatFits:CGSizeMake(self.container.width, CGFLOAT_MAX)].height];

    [self.view layoutIfNeeded];
}

La deuxième passe de présentation est nécessaire pour redimensionner UITextView.

7
jwswart

Utilisez ce code. ScrollView setContentSize doit être appelé asynchrone dans le fil principal.

Rapide:

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()

    DispatchQueue.main.async {
        var contentRect = CGRect.zero

        for view in self.scrollView.subviews {
           contentRect = contentRect.union(view.frame)
        }

        self.scrollView.contentSize = contentRect.size
    }
}

Objectif c:

 - (void)viewDidLayoutSubviews {
     [super viewDidLayoutSubviews];

     dispatch_async(dispatch_get_main_queue(), ^ {
                        CGRect contentRect = CGRectZero;

                        for(UIView *view in scrollView.subviews)
                           contentRect = CGRectUnion(contentRect,view.frame);

                           scrollView.contentSize = contentRect.size;
                       });
}
1
Karen Hovhannisyan

Ma variante pour la vue de défilement avec! Dynamic! la taille:

1) Ajoutez scroll view à votre UIView. Épingler toutes les contraintes (haut, bas, piste, piste).

2) Ajoutez UIView à Scroll View. Épingler toutes les contraintes (haut, bas, piste, piste). Ce sera votre Content view. Vous pouvez également le renommer.

3) Faites glisser le curseur de Content view à Scroll view - Equal width

4) Ajouter du contenu à votre UIView. Définir les contraintes nécessaires. Et! En bas, ajouter la contrainte du basPASGreater or equal (>=) (comme la plupart des gens parlent)MAISEqual! Définissez-le sur 20 par exemple. 

Dans ma situation, j'ai UIImageView dans le contenu. J'ai connecté c'est height au code. Et si je le change en type 1000, le défilement est visible. Et tout fonctionne.

Fonctionne comme un charme pour moi. Toutes les questions - bienvenue aux commentaires.

0
Vlad Pulichev

La vue de défilement doit connaître à tout moment la taille de son contenu. La taille du contenu est déduite des sous-vues du scrollview. Il est très pratique de mapper les propriétés du contrôleur sur les contraintes du fichier xib décrivant les hauteurs des sous-vues. Ensuite, dans le code (un bloc d'animation), vous pouvez simplement modifier les constantes de ces propriétés de contrainte. Si vous devez modifier l'intégralité de la contrainte, conservez une référence afin de pouvoir l'actualiser ultérieurement dans le conteneur parent. 

0
Vladimír Slavík