web-dev-qa-db-fra.com

UITableViewCell avec marge gauche autolayout différente sur iPhone et iPad

J'utilise une UITableView groupée avec des cellules statiques pour un écran/une scène d'options. Tout est fait dans Xcode 6.1/iOS 8.1.x/Storyboard avec Autolayout. Dans les groupes de tables, il y a plusieurs types de cellules et il y a deux types de problèmes:

  1. Cellules avec style personnalisé et
  2. Cellules avec style "Right Detail"

Sur la cellule n ° 1, je peux définir une contrainte pour la marge gauche entre l'étiquette et le conteneur de tête. Dans la cellule 2, je ne peux pas définir de contrainte dans Interface Builder pour autant que je sache. J'ai défini la marge de gauche sur l'étiquette de la cellule n ° 1 afin qu'elle s'aligne sur l'étiquette de la cellule n ° 2. Tout semble bien sur un iPhone, mais si je montre la même table sur un iPad où la taille du conteneur de la vue de table est la moitié de la taille de l'écran, la cellule n ° 2 obtient plus de marge (dynamiquement?), Tandis que la cellule n ° 1 conserve la marge absolue. définir dans les contraintes. J'ai aussi essayé de changer la marge de gauche dans la cellule n ° 1 avec l'attribut "relatif à la marge" mais en vain.

iPhone: 

Table on iPhone

iPad (avec une largeur de table = 1/2 taille d'écran)

Table on iPad

La question est donc la suivante: comment définir les contraintes pour l’étiquette dans la cellule n ° 1 afin qu’elle s’aligne comme la cellule n ° 2.

Voici également un lien vers un exemple de projet Xcode 6.1 illustrant le problème. Courez sur iPhone et iPad pour voir la différence:

https://dl.dropboxusercontent.com/u/5252156/Code/tableViewTest.Zip

Cette question peut être liée à Cellule de tableau statique Layout pour iPhone et iPad , mais elle pourrait également différer pour iOS 8, étant donné que tout est censé être adaptatif maintenant. C'est pourquoi j'ai décidé de poster cette question de toute façon.

55
Georg

 Comment le réparer

Après avoir lutté contre l'équipe de rapports de bogues d'Apple avec de nombreux exemples de projets et de captures d'écran et disséqué cette réponse , j'ai trouvé la solution pour que vos cellules de style personnalisé se comportent de manière cohérente en ce qui concerne leurs marges et soient exactement comme les UITableViewCells par défaut, vous devez faire ce qui suit (principalement basé sur la réponse de Becky , j'ai souligné ce qui est différent et ce qui l'a fait fonctionner pour moi):

  1. Sélectionnez le affichage du contenu de votre cellule dans IB
  2. Aller à l'inspecteur de taille
  3. Dans la section Marges de mise en page, cochez Conserver les marges de superview} (ne cliquez pas sur le signe plus).

     Checking preserve superview margins

  4. (Et voici la clé) Faites la même chose {pour la cellule elle-même} _ (le parent de la vue de contenu si vous voulez)

     The cell and not the content view this time

  5. Configurez vos contraintes comme suit: Label.Leading = Superview.Leading Margin (avec une constante de 0)

     Setting the constraint for the custom cell's label

Désormais, toutes vos cellules auront leur étiquette conforme aux cellules par défaut! Cela fonctionne pour moi dans Xcode 7 et versions ultérieures et inclut le correctif mentionné dans le fil auquel j'ai fait référence. IB et le simulateur doivent maintenant afficher des étiquettes correctement alignées.

 Final result in the simulator

Vous pouvez également le faire par programme, par exemple dans la classe du contrôleur de vue:

cell.preservesSuperviewLayoutMargins = true
cell.contentView.preservesSuperviewLayoutMargins = true

Vous pouvez également le configurer en appelant UIAppearance une fois au démarrage (je ne connais que Swift, désolé):

UITableViewCell.appearance().preservesSuperviewLayoutMargins = true
UITableViewCell.appearance().contentView.preservesSuperviewLayoutMargins = true

Comment et pourquoi ça marche

Comme Ethan a gentiment fait remarquer, la documentation d’Apple sur UIView décrit preservesSuperviewLayoutMargins comme suit: 

Lorsque la valeur de cette propriété est true, les marges de superview sont également prises en compte lors de la présentation du contenu. Cette marge affecte les dispositions où la distance entre le bord d'une vue et son aperçu est inférieure à la marge correspondante. Par exemple, vous pouvez avoir une vue de contenu dont le cadre correspond précisément aux limites de son aperçu. Lorsque l’une des marges de la vue de dessus est à l’intérieur de la zone représentée par la vue du contenu et de ses propres marges, UIKit ajuste la disposition de la vue de contenu pour respecter les marges de la vue de dessus. Le montant de l’ajustement est le plus petit montant nécessaire pour que le contenu se trouve également dans les marges de Superview.

Par conséquent, si vous souhaitez que le contenu de votre cellule s'aligne sur les marges TableView (c'est un arrière-grand-parent si vous le souhaitez), vous devez conserver les deux ascendants de votre contenu, Affichage du contenu et la cellule du tableau de leur propre superview.

Pourquoi ce comportement n'est pas par défaut me surprend: je pense que la plupart des développeurs qui ne souhaitent pas tout personnaliser s'attendent à ce que cet "héritage" soit configuré par défaut.

143
Jonas Zaugg

J'ai eu le même problème que vous et j'ai trouvé une solution.

Tout d’abord, un peu d’arrière-plan: depuis iOS 8, les cellules d’affichage de tableau par défaut respectent la variable layoutMargins de la cellule et s’adaptent à différents traits (écrans, ou appareils). Par exemple, les marges de mise en page sur tous les iPhones (à l'exception de l'iPhone 6 Plus dans une feuille de formulaire) sont {8, 16, 8, 16}. Sur iPad, ils sont {8, 20, 8, 20}. Nous savons maintenant qu'il existe une différence de 4 pixels, ce que votre cellule d'affichage de tableau personnalisé ne respecte probablement pas.

La sous-classe de cellules de votre vue tableau doit adapter la contrainte de marge de gauche lorsque layoutMargins change.

Voici l'extrait de code pertinent:

 - (void)layoutMarginsDidChange
{
    [super layoutMarginsDidChange];

    self.leftLayoutMarginConstraint.constant = self.layoutMargins.left;
    self.rightLayoutMarginConstraint.constant = self.layoutMargins.right;
}

L’adaptation aux marges du code de mise en page vous permet d’obtenir le bon remplissage pour votre titre.

Vous pouvez également consulter l'une de mes sous-classes UITableViewCell qui respecte déjà layoutMargins: https://github.com/bhr/BHRExtensions/blob/master/BHRExtensions/Utilities/BHRTitleAndValueTableCell.m

À votre santé

6
tubtub

Après avoir lu les réponses existantes et ne pas avoir trouvé de solution programmatique évidente, j’ai creusé un peu plus et ai maintenant une bonne réponse pour tous ceux qui sont confrontés à ce problème.

Tout d'abord, il n'est pas nécessaire de définir preservesSuperviewLayoutMargins dans la vue de la cellule ou dans la vue du contenu sous autreréponses imply. Alors que la valeur par défaut est false, le changer en true n'a eu aucun effet notable que je pouvais voir.

La clé pour que cela fonctionne réellement est la propriété layoutMarginsGuide sur UIView. En utilisant cette valeur, nous pouvons simplement épingler la leadingAnchor de toute sous-vue à la leadingAnchor du guide. Voici à quoi cela ressemble dans le code (et peut très bien être ce que fait IB dans les coulisses comme dans réponse de Jonas ).

Dans une sous-classe UITableViewCell, vous feriez quelque chose comme ceci:

override func updateConstraints() {
    let margins = contentView.layoutMarginsGuide
    let leading = margins.leadingAnchor
    subview1.leadingAnchor.constraintEqualToAnchor(leading).active = true
    subview2.leadingAnchor.constraintEqualToAnchor(leading).active = true

    super.updateConstraints()
}

Mise à jour Swift 4.1

override func updateConstraints() {
    let margins = contentView.layoutMarginsGuide
    let leading = margins.leadingAnchor
    subview1.leadingAnchor.constraint(equalTo: leading).isActive = true
    subview2.leadingAnchor.constraint(equalTo: leading).isActive = true

    super.updateConstraints()
}

C'est tout! Si vous développez des versions pour iOS antérieures à iOS 9, vous devrez remplacer les ancres de présentation et utiliser plutôt l'encart layoutMargins.


Note: J'ai écrit une bibliothèque pour que l'ancre soit un peu plus jolie, si vous préférez une syntaxe plus propre. Il s'appelle SuperLayout et est disponible sur Cocoapods. En haut de votre fichier source, importez SuperLayout:

import SuperLayout

Ensuite, dans votre bloc de présentation, utilisez ~~, ≤≤ et ≥≥ pour épingler des contraintes:

override func updateConstraints() {
    let margins = contentView.layoutMarginsGuide

    subview1.leadingAnchor ~~ margins.leadingAnchor
    subview2.leadingAnchor ~~ margins.leadingAnchor

    super.updateConstraints()
}
4
Dan Loewenherz

J'ai pu obtenir des cellules avec des styles personnalisés alignés sur les cellules standard en procédant comme suit:

  1. Dans la structure du document, sélectionnez "Affichage du contenu" pour la cellule avec Le style personnalisé.
  2. Allez à l'inspecteur de taille. 
  3. Sous le menu déroulant "Layout Margins", cliquez sur le petit plus à côté de "Preserve Superview Margins". 
  4. Sélectionnez la classe de taille de l'iPad, à savoir "Largeur régulière x Hauteur normale". 
  5. Cochez la case en regard de "Conserver les marges Superview".
  6. Résolvez les avertissements de disposition automatique en mettant à jour les cadres.

Cela a fonctionné pour moi dans Xcode 7; J'espère que cela fonctionnera également dans Xcode 6.

1
Becky Hansmeyer

J'ai eu ce problème lors de tests sur un iPad Air, OS 10.1.1. Les en-têtes de table étaient en retrait beaucoup plus loin qu'ils n'auraient dû l'être, et c'était encore pire en orientation paysage. Mais ils étaient bien sur les iphones jusqu’à OS 11.

La solution surprenante était la ligne de code suivante, juste après la création de la table (désolé, je ne travaille qu'en C #, mais il est facile de résoudre les équivalents Obj-C et Swift):

myTableView.SeparatorInset = myTableView.SeparatorInset;

Ensuite, tout était en retrait comme il se doit!

0
diyaddict