Disons que j'ai ajouté plus de vues dans UIStackView qui peuvent être affichées, comment puis-je faire le UIStackView
défilement?
Disons que j'ai ajouté plus de vues dans UIStackView qui peuvent être affichées, comment puis-je faire le UIStackView
défilement?
Réponses:
Dans le cas où quelqu'un cherche une solution sans code , j'ai créé un exemple pour le faire complètement dans le storyboard, en utilisant la mise en page automatique.
Vous pouvez l'obtenir auprès de github .
Fondamentalement, pour recréer l'exemple (pour le défilement vertical):
UIScrollView
et définissez ses contraintes.UIStackView
auUIScrollView
Leading
, Trailing
, Top
et Bottom
devrait être égal à ceux deUIScrollView
Width
contrainte égale entre le UIStackView
et UIScrollView
.UIStackView
UIViews
laUIStackView
Échange Width
de l' Height
étape 4, et ensemble Axis
= Horizontal
dans l' étape 5, pour obtenir un UIStackView horizontal.
Le guide de mise en page automatique d'Apple comprend une section entière sur l'utilisation des vues de défilement . Quelques extraits pertinents:
- Épinglez les bords supérieur, inférieur, avant et arrière de la vue de contenu aux bords correspondants de la vue de défilement. La vue de contenu définit maintenant la zone de contenu de la vue de défilement.
- (Facultatif) Pour désactiver le défilement horizontal, définissez la largeur de la vue de contenu égale à la largeur de la vue de défilement. La vue de contenu remplit maintenant la vue de défilement horizontalement.
- (Facultatif) Pour désactiver le défilement vertical, définissez la hauteur de la vue de contenu égale à la hauteur de la vue de défilement. La vue de contenu remplit maintenant la vue de défilement horizontalement.
En outre:
Votre mise en page doit définir entièrement la taille de la vue du contenu (sauf lorsque défini aux étapes 5 et 6). … Lorsque la vue du contenu est plus haute que la vue de défilement, la vue de défilement permet le défilement vertical. Lorsque la vue de contenu est plus large que la vue de défilement, la vue de défilement permet le défilement horizontal.
Pour résumer, la vue de contenu de la vue de défilement (dans ce cas, une vue de pile) doit être épinglée sur ses bords et avoir sa largeur et / ou sa hauteur autrement limitée. Cela signifie que le contenu de la vue de pile doit être contraint (directement ou indirectement) dans la ou les directions dans lesquelles le défilement est souhaité, ce qui pourrait signifier ajouter une contrainte de hauteur à chaque vue à l'intérieur d'une vue de pile à défilement vertical, par exemple. Voici un exemple de comment autoriser le défilement vertical d'une vue de défilement contenant une vue de pile:
// Pin the edges of the stack view to the edges of the scroll view that contains it
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
// Set the width of the stack view to the width of the scroll view for vertical scrolling
stackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
Need constraints for: Y position or height.
cette erreur disparaît uniquement si vous définissez à la fois une contrainte de largeur et de hauteur pour la vue de la pile, ce qui désactive le défilement vertical. Ce code fonctionne-t-il toujours pour vous?
Les contraintes dans la réponse la plus votée ici ont fonctionné pour moi, et j'ai collé une image des contraintes ci-dessous, telle que créée dans mon storyboard.
J'ai cependant rencontré deux problèmes dont d'autres devraient être conscients:
Après avoir ajouté des contraintes similaires à celles de la réponse acceptée, j'obtiendrais l'erreur de mise en page automatique rouge Need constraints for: X position or width
. Cela a été résolu en ajoutant un UILabel comme sous-vue de la vue de la pile.
J'ajoute les sous-vues par programme, donc à l'origine je n'avais pas de sous-vues sur le storyboard. Pour supprimer les erreurs de mise en page automatique, ajoutez une sous-vue au storyboard, puis supprimez-la lors du chargement avant d'ajouter vos vraies sous-vues et contraintes.
J'ai essayé à l'origine d'ajouter UIButtons
à UIStackView. Les boutons et les vues se chargeraient, mais la vue de défilement ne défilerait pas . Cela a été résolu en ajoutant UILabels
à la vue de la pile au lieu de boutons. En utilisant les mêmes contraintes, cette hiérarchie de vues avec les UILabels défile mais pas les UIButtons.
Je suis confus par ce problème, car les UIButtons ne semblent avoir un IntrinsicContentSize (utilisé par la vue Stack). Si quelqu'un sait pourquoi les boutons ne fonctionnent pas, j'aimerais savoir pourquoi.
Voici ma hiérarchie de vue et les contraintes, pour référence:
Comme le dit Eik, UIStackView
et UIScrollView
jouez bien ensemble, voyez ici .
La clé est que le UIStackView
gère la hauteur / largeur variable pour différents contenus et le fait UIScrollView
alors bien son travail de défilement / rebondir ce contenu:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.contentSize = CGSize(width: stackView.frame.width, height: stackView.frame.height)
}
Je cherchais à faire la même chose et suis tombé sur cet excellent post. Si vous souhaitez le faire par programme à l'aide de l'API d'ancrage, c'est la voie à suivre.
Pour résumer, intégrez votre UIStackView
dans votre UIScrollView
et définissez les contraintes d'ancrage du UIStackView
pour correspondre à celles du UIScrollView
:
stackView.leadingAnchor.constraintEqualToAnchor(scrollView.leadingAnchor).active = true
stackView.trailingAnchor.constraintEqualToAnchor(scrollView.trailingAnchor).active = true
stackView.bottomAnchor.constraintEqualToAnchor(scrollView.bottomAnchor).active = true
stackView.topAnchor.constraintEqualToAnchor(scrollView.topAnchor).active = true
stackView.widthAnchor.constraintEqualToAnchor(scrollView.widthAnchor).active = true
Avoir une scène vierge en plein écran
Ajoutez une vue de défilement. Faites glisser la souris de la vue de défilement à la vue de base , ajoutez gauche-droite-haut-bas, tout à zéro.
Ajoutez une vue de pile dans la vue de défilement. Faites glisser la vue de la pile vers la vue de défilement tout en maintenant la touche Ctrl enfoncée , ajoutez gauche-droite-haut-bas, tout à zéro.
Mettez deux ou trois étiquettes dans la vue de la pile.
Pour plus de clarté, rendez la couleur d'arrière-plan de l'étiquette rouge. Réglez la hauteur de l'étiquette sur 100.
Réglez maintenant le Définissez largeur de chaque UILabel:
Étonnamment, faites glisser UILabel
la souris de la vue vers la vue de défilement, pas vers la vue de la pile, et sélectionnez des largeurs égales .
Répéter:
C'est si simple. Voilà le secret.
Astuce secrète - Bug Apple:
Cela ne fonctionnera pas avec un seul élément! Ajoutez quelques étiquettes pour faire fonctionner la démo.
Vous avez terminé.
Conseil: vous devez ajouter une hauteur à chaque nouvel élément . Chaque élément d'une vue de pile de défilement doit avoir une taille intrinsèque (telle qu'une étiquette) ou ajouter une contrainte de hauteur explicite.
L'approche alternative:
Dans ce qui précède: de manière surprenante, définissez les largeurs des étiquettes UIL sur la largeur de la vue de défilement (pas la vue de la pile).
Alternativement...
Vous avez donc deux options:
ou
Pour être clair, faites UNE de ces méthodes, ne faites PAS les deux.
Pour le défilement horizontal. Créez d'abord un UIStackView
et un UIScrollView
et ajoutez-les à votre vue de la manière suivante:
let scrollView = UIScrollView()
let stackView = UIStackView()
scrollView.addSubview(stackView)
view.addSubview(scrollView)
Se souvenant de mettre la translatesAutoresizingMaskIntoConstraints
à false
le UIStackView
et UIScrollView
:
stackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
Pour que tout fonctionne, les ancres arrière, avant, supérieure et inférieure UIStackView
doivent être égales aux UIScrollView
ancres:
stackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
Mais la largeur de l'ancre UIStackView
doit être égale ou supérieure à la largeur de l' UIScrollView
ancre:
stackView.widthAnchor.constraint(greaterThanOrEqualTo: scrollView.widthAnchor).isActive = true
Maintenant, ancrez votre UIScrollView
, par exemple:
scrollView.heightAnchor.constraint(equalToConstant: 80).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo:view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo:view.trailingAnchor).isActive = true
Ensuite, je suggère d'essayer les paramètres suivants pour l' UIStackView
alignement et la distribution:
topicStackView.axis = .horizontal
topicStackView.distribution = .equalCentering
topicStackView.alignment = .center
topicStackView.spacing = 10
Enfin, vous devrez utiliser la addArrangedSubview:
méthode pour ajouter des sous-vues à votre UIStackView.
Une fonctionnalité supplémentaire que vous pourriez trouver utile est que, parce que le contenu se UIStackView
trouve dans un, UIScrollView
vous avez maintenant accès à des encarts de texte pour rendre les choses un peu plus jolies.
let inset:CGFloat = 20
scrollView.contentInset.left = inset
scrollView.contentInset.right = inset
// remember if you're using insets then reduce the width of your stack view to match
stackView.widthAnchor.constraint(greaterThanOrEqualTo: topicScrollView.widthAnchor, constant: -inset*2).isActive = true
Ajoutez simplement ceci à viewdidload
:
let insets = UIEdgeInsetsMake(20.0, 0.0, 0.0, 0.0)
scrollVIew.contentInset = insets
scrollVIew.scrollIndicatorInsets = insets
Vous pouvez essayer ScrollableStackView : https://github.com/gurhub/ScrollableStackView
C'est une bibliothèque compatible avec Objective-C et Swift. Il est disponible via CocoaPods .
Exemple de code (Swift)
import ScrollableStackView
var scrollable = ScrollableStackView(frame: view.frame)
view.addSubview(scrollable)
// add your views with
let rectangle = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 55))
rectangle.backgroundColor = UIColor.blue
scrollable.stackView.addArrangedSubview(rectangle)
// ...
Exemple de code (Objective-C)
@import ScrollableStackView
ScrollableStackView *scrollable = [[ScrollableStackView alloc] initWithFrame:self.view.frame];
scrollable.stackView.distribution = UIStackViewDistributionFillProportionally;
scrollable.stackView.alignment = UIStackViewAlignmentCenter;
scrollable.stackView.axis = UILayoutConstraintAxisVertical;
[self.view addSubview:scrollable];
UIView *rectangle = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 55)];
[rectangle setBackgroundColor:[UIColor blueColor]];
// add your views with
[scrollable.stackView addArrangedSubview:rectangle];
// ...
Si vous avez une contrainte pour centrer la vue de pile verticalement à l'intérieur de la vue de défilement, supprimez-la simplement.
Exemple pour une vue de pile / défilement verticale (en utilisant la mise EasyPeasy
en page automatique):
let scrollView = UIScrollView()
self.view.addSubview(scrollView)
scrollView <- [
Edges(),
Width().like(self.view)
]
let stackView = UIStackView(arrangedSubviews: yourSubviews)
stackView.axis = .vertical
stackView.distribution = .fill
stackView.spacing = 10
scrollView.addSubview(stackView)
stackView <- [
Edges(),
Width().like(self.view)
]
Assurez-vous simplement que la hauteur de chaque sous-vue est définie!
Avant tout, concevez votre vue, de préférence dans quelque chose comme Sketch ou faites-vous une idée de ce que vous voulez en tant que contenu déroulant.
Ensuite, rendez le contrôleur de vue libre (choisissez parmi l'inspecteur d'attributs) et définissez la hauteur et la largeur en fonction de la taille du contenu intrinsèque de votre vue (à choisir dans l'inspecteur de taille).
Après cela, dans le contrôleur de vue, mettez une vue de défilement et c'est une logique, que j'ai trouvé fonctionner presque tout le temps dans iOS (cela peut nécessiter de parcourir la documentation de cette classe de vue que l'on peut obtenir via la commande + cliquez sur cette classe ou via googler)
Si vous travaillez avec deux ou plusieurs vues, commencez d'abord par une vue qui a été introduite plus tôt ou qui est plus primitive, puis passez à la vue qui a été introduite plus tard ou qui est plus moderne. Donc, ici, puisque la vue de défilement a été introduite en premier, commencez par la vue de défilement puis passez à la vue de la pile. Ici, mettez les contraintes de défilement à zéro dans toutes les directions par rapport à sa super vue. Placez toutes vos vues dans cette vue de défilement, puis placez-les en vue de pile.
Tout en travaillant avec la vue pile
Commencez d'abord par les motifs vers le haut (approche ascendante), c'est-à-dire si vous avez des étiquettes, des champs de texte et des images dans votre vue, puis disposez ces vues en premier (à l'intérieur de la vue de défilement) et ensuite mettez-les dans la vue de la pile.
Après cela, ajustez la propriété de la vue de la pile. Si la vue souhaitée n'est toujours pas obtenue, utilisez une autre vue de pile.
Après avoir créé votre vue, placez une contrainte entre la vue de la pile mère et la vue de défilement, tandis que les enfants de contrainte empilent la vue avec la vue de la pile mère. J'espère qu'à ce moment cela devrait fonctionner correctement ou vous pouvez recevoir un avertissement de Xcode donnant des suggestions, lisez ce qu'il dit et mettez-les en œuvre. Si tout va bien maintenant vous devriez avoir une vue fonctionnante selon vos espérances :).
Pour les vues imbriquées ou simples, la vue de défilement doit avoir une largeur fixe avec la vue racine. La vue de la pile principale qui se trouve à l'intérieur de la vue de défilement doit définir la même largeur. [Ma vue de défilement est inférieure à une vue, ignorez-la]
Configurez une contrainte de largeur égale entre UIStackView et UIScrollView.
Si quelqu'un cherche un défilement horizontal
func createHorizontalStackViewsWithScroll() {
self.view.addSubview(stackScrollView)
stackScrollView.translatesAutoresizingMaskIntoConstraints = false
stackScrollView.heightAnchor.constraint(equalToConstant: 85).isActive = true
stackScrollView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor).isActive = true
stackScrollView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor).isActive = true
stackScrollView.bottomAnchor.constraint(equalTo: visualEffectViews.topAnchor).isActive = true
stackScrollView.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.topAnchor.constraint(equalTo: stackScrollView.topAnchor).isActive = true
stackView.leadingAnchor.constraint(equalTo: stackScrollView.leadingAnchor).isActive = true
stackView.trailingAnchor.constraint(equalTo: stackScrollView.trailingAnchor).isActive = true
stackView.bottomAnchor.constraint(equalTo: stackScrollView.bottomAnchor).isActive = true
stackView.heightAnchor.constraint(equalTo: stackScrollView.heightAnchor).isActive = true
stackView.distribution = .equalSpacing
stackView.spacing = 5
stackView.axis = .horizontal
stackView.alignment = .fill
for i in 0 ..< images.count {
let photoView = UIButton.init(frame: CGRect(x: 0, y: 0, width: 85, height: 85))
// set button image
photoView.translatesAutoresizingMaskIntoConstraints = false
photoView.heightAnchor.constraint(equalToConstant: photoView.frame.height).isActive = true
photoView.widthAnchor.constraint(equalToConstant: photoView.frame.width).isActive = true
stackView.addArrangedSubview(photoView)
}
stackView.setNeedsLayout()
}
Placez une vue de défilement sur votre scène et redimensionnez-la afin qu'elle remplisse la scène. Ensuite, placez une vue de pile dans la vue de défilement et placez le bouton Ajouter un élément dans la vue de pile. Dès que tout est en place, définissez les contraintes suivantes:
Scroll View.Leading = Superview.LeadingMargin
Scroll View.Trailing = Superview.TrailingMargin
Scroll View.Top = Superview.TopMargin
Bottom Layout Guide.Top = Scroll View.Bottom + 20.0
Stack View.Leading = Scroll View.Leading
Stack View.Trailing = Scroll View.Trailing
Stack View.Top = Scroll View.Top
Stack View.Bottom = Scroll View.Bottom
Stack View.Width = Scroll View.Width
code: Stack View.Width = Scroll View.Width
est la clé.
Ajout d'une nouvelle perspective pour macOS Catalyst. Étant donné que les applications macOS prennent en charge le redimensionnement de la fenêtre, il est possible que vous UIStackView
passiez d'un état non défilant à un état défilant, ou vice versa. Il y a deux choses subtiles ici:
UIStackView
est conçu pour s'adapter à tous les domaines qu'il peut.UIScrollView
tentera de redimensionner ses limites pour tenir compte de la zone nouvellement gagnée / perdue sous votre barre de navigation (ou barre d'outils dans le cas des applications macOS).Cela créera malheureusement une boucle infinie. Je ne suis pas très familier avec UIScrollView
et son adjustedContentInset
, mais à partir de mon journal dans sa layoutSubviews
méthode, je vois le comportement suivant:
UIScrollView
tente de réduire ses limites (car pas besoin de la zone sous la barre d'outils).UIStackView
suit.UIScrollView
n'est pas satisfait et décide de revenir aux limites plus larges. Cela me semble très étrange, car ce que je vois dans le journal, c'est que UIScrollView.bounds.height == UIStackView.bounds.height
.UIStackView
suit.Il me semble que deux étapes régleraient le problème:
UIStackView.top
sur UIScrollView.topMargin
.contentInsetAdjustmentBehavior
sur .never
.Ici, je suis concerné par une vue défilante verticalement avec une croissance verticale UIStackView
. Pour une paire horizontale, modifiez le code en conséquence.
J'espère que cela aidera n'importe qui à l'avenir. Je n'ai trouvé personne qui en parle sur Internet et il m'a fallu beaucoup de temps pour comprendre ce qui s'est passé.