web-dev-qa-db-fra.com

Ambiguïté de la taille du contenu défilable UIScrollView

Mes collègues, J'ai des problèmes avec AutoLayout dans Interface Builder (Xcode 5/iOS 7) . C'est très basique et important, je pense donc que tout le monde devrait savoir comment cela fonctionne correctement. S'il s'agit d'un bogue dans Xcode, il s'agit d'un problème critique!

Donc, chaque fois que j'ai une hiérarchie de vues comme celle-ci, j'ai des problèmes:

>UIViewController
>> UIView
>>>UIScrollView
>>>>UILabel (or any other comparable UIKit Element)

UIScrollView a des contraintes solides, par exemple 50 px de chaque côté (pas de problème) . Ensuite, j'ajoute une contrainte Top Space à UILabel (pas de problème) (et je peux même épingler hauteur/largeur de l'étiquette, rien ne change , mais devrait être inutile en raison de la taille intrinsèque du label)

Le problème commence lorsque j'ajoute une contrainte de fin à UILabel:

Espace de fuite à: Superview égal: 25

Maintenant, deux avertissements se produisent - et je ne comprends pas pourquoi:

A) Ambiguïté de la taille du contenu défilant (la vue défilement a une hauteur/largeur de contenu défilable ambiguë)

B) Vues mal placées (libellé attendu: x = -67 réel: x = 207

J'ai fait cet exemple minimal dans un nouveau projet que vous pouvez télécharger et j'ai joint une capture d'écran. Comme vous pouvez le constater, Interface Builder s'attend à ce que l'étiquette se trouve en dehors de la limite de UIScrollView (le rectangle en pointillé orange). La mise à jour du cadre de l'étiquette avec l'outil de résolution de problèmes le déplace ici.

Remarque: si vous remplacez UIScrollView par un UIView, le comportement est celui attendu (le cadre de l'étiquette est correct et conforme à la contrainte). Donc, il semble y avoir un problème avec UIScrollView ou je manque quelque chose d'important.

Lorsque je lance l'application sans mettre à jour le cadre de l'étiquette, comme suggéré par IB, celle-ci est correctement positionnée, exactement là où elle est supposée se trouver et UIScrollView est consultable en défilement . ne défile pas.

Aidez-moi Obi-Wan Kenobi! Pourquoi cette mise en page ambiguë? Pourquoi la vue égarée?

Vous pouvez télécharger l'exemple de projet ici et essayer de comprendre ce qui se passe: https://github.com/Wirsing84/AutoLayoutProblem

Illustration of the problem in Interface Builder

417
Wirsing

REMARQUE: sur iOS> 10.0, cela n'a pas été testé. par conséquent, la solution pourrait être incomplète dans certaines situations.

Donc, je viens de trier de cette façon:

  1. Dans UIScrollViewadd a UIView (nous pouvons appeler cela contentView);

  2. Dans cette contentView, définissez les marges supérieure, inférieure, gauche et droite sur 0 (bien sûr à partir de la scrollView qui est la superView); Set aligne également le centre horizontalement et verticalement;

Fini.

Vous pouvez maintenant ajouter toutes vos vues dans cette contentView et la contentSize de la scrollView sera automatiquement redimensionnée en fonction de la contentView.

Mettre à jour:

video posté par @Sergio dans les commentaires ci-dessous.

918
Matteo Gobbi

Cette erreur m'a pris un certain temps à repérer. Au départ, j'ai essayé de déterminer la taille de ScrollView, mais le message d'erreur indique clairement "taille du contenu". Je me suis assuré que tout ce que j'avais épinglé depuis le haut de la ScrollView était également épinglé vers le bas. Ainsi, ScrollView peut calculer la hauteur de son contenu en recherchant la hauteur de tous les objets et contraintes. Cela résout la hauteur du contenu ambigu, la largeur est assez similaire ... Au départ, la plupart des choses étaient centrées sur X dans le ScrollView, mais je devais également épingler les objets sur le côté du ScrollView. Je n'aime pas cela car l'iPhone6 ​​a peut-être un écran plus large, mais il supprimera l'erreur «largeur du contenu ambigu».

91
self.name

C'est la réponse à ma question personnelle UIScrollView + Vue centrée + Taille du contenu à défilement ambigu + + plusieurs tailles d'iPhone .

Mais cela couvre également votre cas!

Donc, voici l'état initial pour le cas le plus simple:

  1. scrollView avec 0 contrainte sur tous les bords
  2. Bouton centré horizontal et vertical avec contraintes fixes de largeur et de hauteur
  3. Et bien sûr - avertissements gênantsHas ambiguous scrollable content width et Has ambiguous scrollable content height.

1

Tout ce que nous devons faire, c'est:

  • Ajoutez 2 contraintes supplémentaires, par exemple "0" pour l'espace trailing et/ou bottom (voir l'exemple sur la capture d'écran ci-dessous).

Important: vous devez ajouter des contraintes de fin et/ou de fond. Pas "leader et top" - ça ne marche pas!

2

Vous pouvez le vérifier dans mon exemple de projet , qui montre comment résoudre ce problème.

P.S.

Selon la logique, cette action devrait entraîner des "contraintes contradictoires". Mais non!

Je ne sais pas pourquoi cela fonctionne et comment Xcode détecte quelle contrainte est la plus prioritaire (parce que je ne donne pas la priorité à ces contraintes explicitement) .

60
skywinder

Vous devez créer une UIView en tant que sous-vue de la UIScrollView comme décrit ci-dessous:

  • UIViewController
  • UIView 'Main View'
    • UIScrollView
      • UIView 'Vue du conteneur'
        • [Votre contenu]

La deuxième étape consiste à créer les contraintes UIScrollView. Je l'ai fait avec les contraintes de haut, de bas, de début et de fin de son superView.

Ensuite, j’ai ajouté les contraintes pour le conteneur de UIScrollView et c’est ici que le problème commence. Lorsque vous mettez les mêmes contraintes (haut, bas, début et fin), le storyboard envoie un message d'avertissement: 

"A une largeur de contenu défilable ambiguë" et ​​"A une hauteur de contenu défilable ambiguë"

Continuez avec les mêmes contraintes ci-dessus, et ajoutez simplement les contraintesEqual HeightetEqual Widthà votre Vue du conteneur en relation avec la Vue principale et pas à votre UIScrollView . En d'autres termes, les contraintes de la vue conteneur sont liées à la superview de UIScrollView.

Après cela, vous ne recevrez plus d’avertissements dans votre Storyboard et vous pourrez continuer à ajouter les contraintes pour vos vues secondaires.

45
Thiago Monteiro

J'ai finalement compris cela, j'espère que c'est plus facile à comprendre. 

Vous pouvez souvent contourner cet avertissement en ajoutant une UIView de base à la vue de défilement en tant que "vue de contenu", comme indiqué, et lui donner la même taille que votre vue de défilement et, sur cette vue de contenu, définir ces 6 paramètres. 

 enter image description here

Comme vous pouvez le constater, vous avez besoin de 6 paramètres au total! Ce qui .. dans des circonstances normales, vous dupliquez 2 contraintes mais c’est le moyen d’éviter cette erreur de scénarimage.

24
William T.

iOS 12, Swift 4

La manière la plus simple d'utiliser autolayout:

  1. Ajoutez UIScrollView et épinglez-le 0,0,0,0 à superview (ou à la taille souhaitée)
  2. Ajoutez UIView dans ScrollView, épinglez-le 0,0,0,0 sur les 4 côtés et centrez-le horizontally et vertically.
  3. Dans l'inspecteur de taille, définissez la priorité bottom et align center Y sur 250. (pour le défilement horizontal, changez trailing et align center X)
  4. Ajoutez toutes les vues dont vous avez besoin dans cette vue. N'oubliez pas pour définir la contrainte inférieure sur la vue la plus basse.
20

Je sais que je serai peut-être en retard, mais la solution suivante résout ce genre de problèmes sans code supplémentaire , en utilisant simplement des storyboards:

Pour l'affichage du contenu, vous devez définir des contraintes pour scrollview et ceci ne changera pas le cadre d'affichage du contenu , comme ceci: enter image description here

Bien sûr, vous devez créer des contraintes supplémentaires pour la vue du contenu afin que la vue défilement puisse connaître la taille du contenu. Par exemple, définissez hauteur fixe et centre x.

J'espère que cela t'aides.

14
Borzh

Découvrez ce didacticiel très rapide et pertinent sur la manière de faire fonctionner la vue par défilement et de le faire défiler entièrement avec la mise en page automatique. Maintenant, la seule chose qui me laisse encore perplexe est pourquoi la taille du contenu de la vue de défilement est toujours plus grande que nécessaire.

http://samwize.com/2014/03/14/how-to-use-uiscrollview-with-autolayout/

6
Mike Simz

Pour moi, ajouter une contentView n'a pas vraiment fonctionné comme suggéré. De plus, cela crée une surcharge en raison de la vue ajoutée (bien que je ne considère pas cela comme un gros problème). Ce qui a fonctionné le mieux pour moi a été de désactiver la désactivation de la vérification d’ambiguïté pour ma scrollView. Tout se passe bien, donc je pense que ça va dans des cas simples comme le mien. Mais gardez à l’esprit que, si d’autres contraintes pour votre scrollView sautent, Interface-Builder _ vous avertira pas.

 enter image description here

3
Nick Podratz

Voir ci-dessous: vue du contenu dans scrollview vertical et horizontal centralisé . Si vous avez une erreur d’ambiguïté, assurez-vous toujours que deux des éléments ajoutés dans scrollview depuis votre vue du contenu sont 1). capture d'écran, 

ces contraintes signifient en défilement combien vous pouvez faire défiler après la hauteur ou la largeur de votre vue de contenu.

 enter image description here

cela peut vous aider.

3
Ravi H Malviya

La réponse de @Matteo Gobbi est parfaite, mais dans mon cas, le défilement ne peut pas défiler, je supprime " aligner le centre Y " et ajouter " hauteur> = 1 ", le défilement deviendra déroulant. 

2
ygweric

Les personnes qui luttent avec uiscrollview ne défilant pas, définissez simplement la contrainte inférieure de la vue contenu avec la disposition inférieure de votre dernière vue (située à l'intérieur de la vue contenu). Ne pas oublier de supprimer la contrainte de centre Y. 

Reste que toutes les contraintes sont identiques à celles définies ci-dessus. Scrollview ne souhaitait obtenir qu'une hauteur maximale à partir de la vue de contenu, et nous la définissons comme contrainte inférieure de la dernière vue, ce qui signifie que scrollview modifiera automatiquement le décalage de son contenu.

Dans mon cas, la dernière vue était UILable avec la propriété no of lines = 0 (qui ajuste automatiquement sa hauteur en fonction de son contenu), de sorte qu'elle augmente de façon dynamique la hauteur de uilable et que, éventuellement, notre zone de défilement augmente car la disposition du bas de uilable est alignée sur celle de notre vue de contenu, ce qui oblige scrollview à augmenter son offset de contenu. 

2
import UIKit

class ViewController: UIViewController {

    //
    let scrollView: UIScrollView = {

        let sv = UIScrollView()
        sv.translatesAutoresizingMaskIntoConstraints = false
        return sv
    }()

    let lipsumLbl: UILabel = { // you can use here any of your subview

        let lbl = UILabel()
        lbl.translatesAutoresizingMaskIntoConstraints = false
        lbl.text = """
        Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce eleifend nisi sit amet mi aliquet, ut efficitur risus pellentesque. Phasellus nulla justo, scelerisque ut libero id, aliquet ullamcorper lectus. Integer id iaculis nibh. Duis feugiat mi vitae magna tincidunt, vitae consequat dui tempor. Donec blandit urna nec urna volutpat, sit amet sagittis massa fringilla. Pellentesque luctus erat nec dui luctus, sed maximus urna fermentum. Nullam purus nibh, cursus vel ex nec, pulvinar lobortis leo. Proin ac libero rhoncus, bibendum lorem sed, congue sapien. Donec commodo, est non mollis malesuada, nisi massa tempus ipsum, a varius quam lorem vitae nunc.

        Cras scelerisque nisi dolor, in gravida ex ornare a. Interdum et malesuada fames ac ante ipsum primis in faucibus. Maecenas vitae nisl id erat sollicitudin accumsan sit amet viverra sapien. Etiam scelerisque vulputate ante. Donec magna nibh, pharetra sed pretium ac, feugiat sit amet mi. Vestibulum in ipsum vitae dui vehicula pulvinar eget ut lectus. Fusce sagittis a elit ac elementum. Fusce iaculis nunc odio, at fermentum dolor varius et. Suspendisse consectetur congue massa non gravida. Sed id elit vitae nulla aliquam euismod. Pellentesque accumsan risus dolor, eu cursus nibh semper id.

        Vestibulum vel tortor tellus. Suspendisse potenti. Pellentesque id sapien eu augue placerat dictum. Fusce tempus ligula at diam lacinia congue in ut metus. Maecenas volutpat tellus in tellus maximus imperdiet. Integer dignissim condimentum lorem, id luctus nisi maximus at. Nulla pretium, est sit amet mollis eleifend, tellus nulla viverra dolor, ac feugiat massa risus a lectus. Pellentesque ut pulvinar urna, blandit gravida ipsum. Nullam volutpat arcu nec fringilla auctor. Integer egestas enim commodo, faucibus neque ac, rutrum magna. Vivamus tincidunt rutrum auctor. Cras at rutrum felis. Fusce elementum lorem ut pharetra venenatis.
        """
        lbl.textColor = .darkGray
        lbl.font = UIFont.systemFont(ofSize: 16)
        lbl.numberOfLines = 0
        return lbl
    }()

    //======================================================================//
    //
    // MARK:- viewDidLoad
    //
    override func viewDidLoad() {
        super.viewDidLoad()

        setupViews()
        setupAutoLayout()
    }

    func setupViews() {

        title = "ScrollView Demo"
        view.backgroundColor = .white
        view.addSubview(scrollView)
        scrollView.addSubview(lipsumLbl)
    }

    func setupAutoLayout() {

        scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        scrollView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true

        lipsumLbl.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
        lipsumLbl.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
        lipsumLbl.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        lipsumLbl.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        lipsumLbl.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
    }
}

Sortie:

enter image description here

1
iAj

En utilisant contentView (UView) comme conteneur dans UIScrollView, respectez les bords (top, bottom, trailing, leading) de superview (UIScrollView) et contentView devraient avoir equalwidth et equalheight à supern. Ce sont des contraintes. Et appel de méthode:

-(void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    self.scrollView.contentSize = self.contentView.frame.size;
}

Problèmes résolus pour moi.

1
Spiridon Kopicl

J'ai résolu ce type de problème pour mon affichage en utilisant l'option "Résoudre les problèmes de présentation automatique"> "Ajouter des contraintes manquantes" pour la vue sélectionnée  enter image description here

Les deux contraintes suivantes résolvent mon problème:

trailing = Stack View.trailing - 10
bottom = Stack View.bottom + 76

dans lequel: fin, bas sont les bas, fin de UIScrollView

1
Linh Dao

J'ai fait une vidéo sur youTube 
Faites défiler StackViews en utilisant uniquement Storyboard dans Xcode

Je pense que 2 types de scénarios peuvent apparaître ici.

La vue à l'intérieur du scrollView -

  1. n'a pas de contenu intrinsèque Taille (p. ex. UIView)
  2. a son propre contenu intrinsèque Taille (par exemple UIStackView)

Pour une vue à défilement vertical dans les deux cas, vous devez ajouter ces contraintes:

  1. 4 contraintes en haut, à gauche, en bas et à droite.

  2. Largeur égale à défiler (pour arrêter de défiler horizontalement)

Vous n'avez besoin d'aucune autre contrainte pour les vues ayant sa propre hauteur de contenu intrinsèque.

 enter image description here


Pour les vues qui n'ont pas de hauteur de contenu intrinsèque, vous devez ajouter une contrainte de hauteur. La vue défilera uniquement si la contrainte de hauteur est supérieure à la hauteur de scrollView.

 enter image description here

1
Warif Akhand Rishi

Définissez la taille du ViewController (celui qui contient UIScrollView) sur Freeform dans l'inspecteur de taille dans Interface Builder.

Paramètre Freeform dans l'inspecteur de taille dans Interface Builder pour le contrôleur UIView contenant

0
nico

Défilement vertical

  1. Ajoutez un UIScrollView avec des contraintes 0,0,0,0 à superview.
  2. Ajoutez un UIView dans ScrollView, avec les contraintes 0,0,0,0 à superview.
  3. Ajoutez la même contrainte de largeur pour UIView à UIScrollView.
  4. Ajoutez de la hauteur à UIView.
  5. Ajoutez des éléments avec des contraintes à UIView.
  6. Pour l'élément le plus proche du bas, assurez-vous qu'il a une contrainte au bas de UIView.
0
BitCon

Vous devez juste vous assurer qu'il y a un moyen de faire une continue ligne de contraintes du haut en bas de la vue à défilement . [-X-X-X]

Dans mon cas - une vue de défilement horizontal - j'avais également besoin d'ajouter des contraintes de largeur et de hauteur pour chaque enfant de la vue de défilement, bien que la note technique d'Apple ne vous le dis pas.

0
johnnyMac

[testé sous XCode 7.2 et pour iOS 9.2]

Ce qui a supprimé les erreurs et les avertissements dans Storyboard, c’était de régler la taille intrinsèque de la vue de défilement et de la vue du contenu (dans mon cas, une pile) à Placeholder. Ce paramètre se trouve dans l'inspecteur Taille du Storyboard. Et cela dit - La définition d’une taille de contenu intrinsèque au moment de la conception n’affecte que la vue lors de la modification dans Interface Builder. La vue n'aura pas cette taille de contenu intrinsèque au moment de l'exécution.

Donc, je suppose que nous ne nous trompons pas en réglant ceci. 

Remarque: dans le storyboard, j'ai épinglé tous les bords de la vue de défilement à la vue d'ensemble et tous les bords de la vue de pile à la vue de défilement. Dans mon code, j'ai défini la valeur translatesAutoresizingMaskIntoConstraints sur false pour les vues scrollview et stackview. Et je n'ai pas mentionné de taille de contenu. Lorsque la vue de pile croît de manière dynamique, les contraintes définies dans le storyboard permettent de faire défiler la pile. 

L'avertissement du storyboard me rendait fou et je ne voulais pas centrer les choses horizontalement ou verticalement, juste pour supprimer l'avertissement. 

0
gtl_pm

Approche rapide 4+:

1) Définissez les marges supérieure, inférieure, gauche et droite de UIScrollView sur 0

2) Dans UIScrollView, ajoutez un UIView et définissez les marges de fin, de haut, de bas et de début à 0 (égales aux marges de UIScrollView). 

3) La partie la plus importante est de régler la largeur et la hauteur, où la contrainte de hauteur doit avoir une priorité faible .

private func setupConstraints() {
    // Constraints for scrollView
    scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
    scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
    scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
    scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
    // Constraints for containerView
    containerView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
    containerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
    containerView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
    containerView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
    containerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
    let heightConstraint = containerView.heightAnchor.constraint(equalTo: scrollView.heightAnchor)
    heightConstraint.priority = UILayoutPriority(rawValue: 250)
    heightConstraint.isActive = true
}
0
Gentian Sadiku

Je recevais la même erreur .. j'ai fait suite

  1. Vue (vue d'ensemble)
  2. ScrollView 0,0,600,600
  3. UIView inside ScrollView: 0,0,600,600
  4. UIView contient une image, une étiquette

Maintenant, ajoutez scroll/lead/bottom/top/bottom pour scrollView (2) puis UIView (3).

Sélectionnez Afficher (1) et Afficher (3) pour régler la hauteur et le poids de manière égale. Le problème est résolu.

J'ai fait la vidéo qui aidera:

https://www.youtube.com/watch?v=s-CPN3xZS1A

0
Vinod Joshi

Ce que j'ai fait est de créer une vue de contenu distincte, comme illustré ici. 

La vue de contenu est de forme libre et toutes les sous-vues peuvent y être ajoutées avec leurs propres contraintes par rapport à contentView.

UIScrollView est ajouté au contrôleur de vue principal.

J'ajoute par programme le contentView qui est lié via IBOutlet à class et définit le contentView de UIScrollView.

UIScrollView with ContentView via Interface Builder

0
Vlad

Si quelqu'un a un comportement où vous remarquez que la barre de défilement se trouve à droite mais que le contenu ne bouge pas, ce fait mérite probablement d'être pris en compte:

Vous pouvez également utiliser des contraintes entre le contenu de la vue de défilement et les objets situés en dehors de la vue de défilement pour définir une position fixe pour le contenu de la vue de défilement, de sorte que ce contenu semble flotter sur la vue de défilement.

Cela provient de la documentation d'Apple. Par exemple, si vous épingliez accidentellement votre étiquette/bouton/image/vue en haut de la vue en dehors de la zone de défilement (peut-être un en-tête ou quelque chose juste au-dessus de scrollView?) Au lieu de contentView, vous figeiez tout votre contentView en place.

0
Dave G

Comme mentionné dans les réponses précédentes, vous devez ajouter une vue personnalisée dans la vue de défilement:

 The custom view is the ContentView marked in the image

Ajoutez toutes vos sous-vues à la vue du contenu . À un moment donné, une vue du contenu défilant affiche un avertissement ambigu sur la taille du contenu, vous devez sélectionner la vue du contenu et cliquer sur le bouton "Résoudre les problèmes de mise en page automatique" (dans le coin inférieur droit. de la présentation IB) et sélectionnez l’option "Ajouter les contraintes manquantes".

 enter image description here

À partir de maintenant, lorsque vous exécuterez le projet, la vue de défilement mettra automatiquement à jour la taille de son contenu, aucun code supplémentaire n'est nécessaire.

0
supergegs