Comportement étrange d'uitableview dans iOS11. Les cellules défilent avec l'animation de poussée de navigation

116

J'ai récemment migré du code vers le nouveau SDK iOS 11 beta 5.

J'obtiens maintenant un comportement très déroutant de UITableView. Le tableau lui-même n'est pas si sophistiqué. J'ai des cellules personnalisées, mais dans la plupart des cas, c'est juste pour leur hauteur.

Quand je pousse mon contrôleur de vue avec tableview, j'obtiens une animation supplémentaire où les cellules "défilent" (ou peut-être tout le cadre de tableview est changé) et vers le bas le long de l'animation de navigation push / pop. S'il vous plaît voir gif:

table ondulée

Je crée manuellement tableviewdans la loadViewméthode et configure les contraintes de mise en page automatique pour qu'elles soient égales au début, à la fin, en haut, en bas de la vue de la table. Le superview est la vue racine du contrôleur de vue.

Le code de poussée du contrôleur de vue est très standard: self.navigationController?.pushViewController(notifVC, animated: true)

Le même code fournit un comportement normal sur iOS 10.

Pourriez-vous s'il vous plaît m'indiquer ce qui ne va pas?

EDIT: J'ai fait un contrôleur tableview très simple et je peux reproduire le même comportement là-bas. Code:

class VerySimpleTableViewController : UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        self.tableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
    }


    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 4
    }


    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)

        cell.textLabel?.text = String(indexPath.row)
        cell.accessoryType = .disclosureIndicator

        return cell
    }


    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)

        let vc = VerySimpleTableViewController.init(style: .grouped)

        self.navigationController?.pushViewController(vc, animated: true)
    }
}

EDIT 2: J'ai pu réduire le problème à ma personnalisation de UINavigationBar. J'ai une personnalisation comme celle-ci:

rootNavController.navigationBar.setBackgroundImage(createFilledImage(withColor: .white, size: 1), for: .default)

createFilledImagecrée une image carrée avec une taille et une couleur données.

Si je commente cette ligne, je retrouve un comportement normal.

J'apprécierais toute réflexion sur cette question.

iur
la source
Cela ne pose peut-être pas de problème avec la personnalisation de la barre de navigation. J'avais le même problème (la réponse acceptée l'a résolu) sans aucune personnalisation. Je pense que cela pourrait être un problème avec la façon dont iOS gère la vue de la table lorsqu'elle est créée manuellement en tant que sous-vue, au lieu d'utiliser UITableViewController.
Mark Leonard
2
Je ne vois ce comportement que lorsque je suis réglé navigationBar.isTranslucentsur false, sinon cela fonctionne bien.
b_ray
5
Cela semble être un bogue dans iOS11 GM, veuillez duper ce rapport de bogue afin que ce problème retienne l'attention d'Apple: openradar.appspot.com/34465226
b_ray
1
Ce problème semble être résolu dans iOS 11.2 beta. Je ne définirais pas contentInsetAdjustmentBehavior sur Never, car il interrompt les vues de défilement de l'iPhone X en ne donnant pas de remplissage en bas de l'écran. Le bas de votre vue de contenu reste sous le "bouton" d'accueil de l'iPhone X.
batu

Réponses:

150

Cela est dû à la nouvelle propriété UIScrollView's (UITableView est une sous-classe de UIScrollview)contentInsetAdjustmentBehavior , qui est définie .automaticpar défaut.

Vous pouvez remplacer ce comportement avec l'extrait de code suivant dans le viewDidLoad de tous les contrôleurs affectés:

    tableView.contentInsetAdjustmentBehavior = .never

https://developer.apple.com/documentation/uikit/uiscrollview/2902261-contentinsetadjustmentbehavior

Maggy Hillen
la source
2
ce commentaire sur la définition de UIScrollViewContentInsetAdjustmentBehavior.automatic dit: "... pour la compatibilité descendante ajustera également le contenu supérieur et inférieur contentInset lorsque la vue de défilement appartient à un contrôleur de vue avec automaticAdjustsScrollViewInsets = YES à l'intérieur d'un contrôleur de navigation, indépendamment du fait que le défilement la vue est déroulante ". Ma théorie est que contentInset de la barre de navigation est affecté par la définition de l'image d'arrière-plan, qui s'ajuste ensuite dynamiquement.
Maggy Hillen
8
Vous pouvez également le faire avec le storyboard. Inspecteur de taille -> Inserts de contenu -> Définissez «Jamais».
Woongbi Kim
4
Si votre contenu s'étend derrière la barre d'onglets, la désactivation tableView.contentInsetAdjustmentBehaviorbriserait les encarts.
kean
4
En outre, la simple désactivation de cette option placera l'indicateur de défilement derrière le (gizmo supérieur) sur l'iPhone X en mode paysage. L'intérêt de ce comportement est d'ajuster la zone de contenu des vues de défilement afin qu'elles soient visibles sur les écrans qui ne sont pas rectangulaires. Je pense que nous ne pouvons le voir actuellement que sur l'iPhone X Sim.
PhoneyDeveloper
8
Ce problème a été causé par un bogue dans iOS 11 où les safeAreaInsets de la vue du contrôleur de vue ont été mal définis lors de la transition de navigation, ce qui devrait être corrigé dans iOS 11.2. Définir le contentInsetAdjustmentBehaviorsur .nevern'est pas une excellente solution de contournement, car il aura probablement d'autres effets secondaires indésirables. Si vous utilisez une solution de contournement, vous devez vous assurer de la supprimer pour les versions iOS> = 11.2.
smileyborg
23

En plus de la réponse de maggy

OBJECTIF C

if (@available(iOS 11.0, *)) {
    scrollViewForView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}

Ce problème était causé par un bogue dans iOS 11 où la safeAreaInsetsvue du contrôleur de vue était mal définie lors de la transition de navigation, ce qui devrait être corrigé dans iOS 11.2. Définir le contentInsetAdjustmentBehaviorsur .nevern'est pas une excellente solution de contournement, car il aura probablement d'autres effets secondaires indésirables. Si vous utilisez une solution de contournement, vous devez vous assurer de la supprimer pour les versions iOS> = 11.2

-mentionné par smileyborg (ingénieur logiciel chez Apple)

Lal Krishna
la source
6

Vous pouvez modifier ce comportement à la fois dans toute l'application en utilisant NSProxy dans par exemple didFinishLaunchingWithOptions:

if (@available(iOS 11.0, *)) {
      [UIScrollView appearance].contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
} 
BigDanceMouse
la source
1
Cela contrôleur de système briser, comme UIImagePickerController
galway
6

Voici comment j'ai réussi à résoudre ce problème tout en permettant à iOS 11 de définir automatiquement des inserts . J'utilise UITableViewController.

  • Sélectionnez «Étendre les bords sous les barres supérieures» et «Étendre les bords sous les barres opaques» dans votre contrôleur de vue dans le storyboard (ou par programme ). Les inserts de zone de sécurité empêcheront votre vue de passer sous la barre supérieure.
  • Cochez le bouton "Insets to Safe Area" sur votre vue de tableau dans votre storyboard. (ou tableView.insetsContentViewsToSafeArea = true) - Ce n'est peut-être pas nécessaire mais c'est ce que j'ai fait.
  • Définissez le comportement d'ajustement de l'encart de contenu sur "Axes de défilement" (ou tableView.contentInsetAdjustmentBehavior = .scrollableAxes) - .alwayspeut également fonctionner, mais je n'ai pas testé.

Une autre chose à essayer si tout le reste échoue:

Override viewSafeAreaInsetsDidChange UIViewControllerpour que la vue de la table force la définition des encarts de la vue de défilement sur les encarts de la zone de sécurité. Ceci est en conjonction avec le paramètre «Jamais» dans la réponse de Maggy.

- (void)viewSafeAreaInsetsDidChange {
    [super viewSafeAreaInsetsDidChange];
    self.tableView.contentInset = self.view.safeAreaInsets;
}

Remarque: self.tableViewet self.viewdevrait être la même chose pourUITableViewController

père Noël
la source
J'ai presque tout essayé sur ce fil, et cela a fonctionné pour moi. TableViewVC intégré dans TabBarVC. Je vous remercie!
Josh Wolff
3

Cela ressemble plus à un bogue qu'à un comportement prévu. Cela se produit lorsque la barre de navigation n'est pas translucide ou lorsque l'image d'arrière-plan est définie.

Si vous définissez simplement contentInsetAdjustmentBehavior sur .never, les insertions de contenu ne seront pas définies correctement sur l'iPhone X, par exemple, le contenu irait dans la zone inférieure, sous les barres de défilement.

Il est nécessaire de faire deux choses:
1. empêcher scrollView de s'animer sur push / pop
2. conserver le comportement automatique car il est nécessaire pour l'iPhone X. Sans cela, par exemple en portrait, le contenu ira sous la barre de défilement inférieure.

Nouvelle solution simple: dans XIB: il suffit d'ajouter un nouveau UIView au-dessus de votre vue principale avec le dessus, le début et la fin de superview et la hauteur définie sur 0. Vous n'avez pas à le connecter à d'autres sous-vues ou quoi que ce soit.

Ancienne solution:

Remarque: Si vous utilisez UIScrollView en mode paysage, il ne définit toujours pas correctement les encarts horizontaux (un autre bogue?), Vous devez donc épingler le début / la fin de scrollView à safeAreaInsets dans IB.

Remarque 2: La solution ci-dessous pose également le problème que si tableView défile vers le bas et que vous poussez le contrôleur et que vous revenez en arrière, il ne sera plus en bas.

override func viewDidLoad()
{
    super.viewDidLoad()

    // This parts gets rid of animation when pushing
    if #available(iOS 11, *)
    {
        self.tableView.contentInsetAdjustmentBehavior = .never
    }
}

override func viewDidDisappear(_ animated: Bool)
{
    super.viewDidDisappear(animated)
    // This parts gets rid of animation when popping
    if #available(iOS 11, *)
    {
        self.tableView.contentInsetAdjustmentBehavior = .never
    }
}

override func viewDidAppear(_ animated: Bool)
{
    super.viewDidAppear(animated)
    // This parts sets correct behaviour(insets are correct on iPhone X)
    if #available(iOS 11, *)
    {
        self.tableView.contentInsetAdjustmentBehavior = .automatic
    }
}
El Horrible
la source
Qu'est-ce que parentView?
PhoneyDeveloper
La vue principale de votre contrôleur de vue, j'ai mis à jour la réponse. THX.
El Horrible
Lorsque j'utilise UITableViewController, la tableView est la vue du contrôleur de vue. En outre, les inserts doivent être différents lorsque l'appareil est tourné. Je veux l'ajustement. Je n'aime tout simplement pas l'animation lorsque la tableView apparaît pour la première fois.
PhoneyDeveloper
Dans votre cas, étant donné que UITableView est la vue du contrôleur, il doit avoir safeAreaInsets.bottom = 34 (portrait), vous pouvez donc simplement définir tableView.contentInset = tableView.safeAreaInsets.
El Horrible
Cela ne fonctionne pas pour moi. Dans viewDidLoad, tous les inserts sont nuls. Si j'essaie d'utiliser ce code dans viewWillLayoutSubviews, l'indicateur de défilement est inséré mais le tableauView lui-même devient capable de défiler horizontalement. Si je regarde juste les inserts dans viewWillLayoutSubviews sans désactiver l'ajustement, le bottom AdjustContentInset devient 21 et c'est tout ce qui semble changer.
PhoneyDeveloper
3

Je peux reproduire le bogue pour iOS 11.1 mais il semble que le bogue soit corrigé depuis iOS 11.2. Voir http://openradar.appspot.com/34465226

Linda
la source
oui, corrigé sur iOS 11.2
Richie Hyatt
2

Veuillez vous assurer qu'avec le code ci-dessus, ajoutez du code supplémentaire comme suit. Cela a résolu le problème

override func viewDidLayoutSubviews() { 
     super.viewDidLayoutSubviews() 
     tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0) // print(thisUISBTV.adjustedContentInset) 
}
Basavaraj Kalaghatagi
la source
Cela seul a résolu mon problème !! Je vous remercie. J'ai perdu trop d'heures sur cette question!
Murat Yasar
2

De plus, si vous utilisez la barre d'onglets, l'encart de contenu inférieur de la vue de collection sera égal à zéro. Pour cela, mettez le code ci-dessous dans viewDidAppear:

if #available(iOS 11, *) {
    tableView.contentInset = self.collectionView.safeAreaInsets
}
hasankose
la source
2

Dans mon cas, cela a fonctionné (mettez-le dans viewDidLoad):

self.navigationController.navigationBar.translucent = YES;
nemissme
la source
1

Suppression d'espace supplémentaire en haut collectionViewoutableView

    if #available(iOS 11.0, *) {
        collectionView.contentInsetAdjustmentBehavior  = .never
        //tableView.contentInsetAdjustmentBehavior  = .never
    } else {
        automaticallyAdjustsScrollViewInsets = false
    }

Au-dessus du code collectionViewou tableViewpasse sous la barre de navigation.
Le code ci-dessous empêche la vue de collection de passer sous la navigation

    self.edgesForExtendedLayout = UIRectEdge.bottom

mais j'aime utiliser la logique et le code ci-dessous pour UICollectionView

Les valeurs d'encart d'arête sont appliquées à un rectangle pour réduire ou agrandir la zone représentée par ce rectangle. En règle générale, les encarts d'arête sont utilisés lors de la disposition de la vue pour modifier le cadre de la vue. Les valeurs positives entraînent l'insertion (ou la réduction) du cadre de la quantité spécifiée. Les valeurs négatives provoquent le début (ou l'extension) du cadre de la quantité spécifiée.

collectionView.contentInset = UIEdgeInsets(top: -30, left: 0, bottom: 0, right: 0)
//tableView.contentInset = UIEdgeInsets(top: -30, left: 0, bottom: 0, right: 0)

Le meilleur moyen pour UICollectionView

func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: -30, left: 0, bottom: 0, right: 0)
}
Nazmul Hasan
la source
0

Supprimer ce code fonctionne pour moi

self.edgesForExtendedLayout = UIRectEdgeNone
weiminghuaa
la source
0
  if #available(iOS 11, *) {
        self.edgesForExtendedLayout = UIRectEdge.bottom
  }

J'utilisais UISearchController avec des resultsControllers personnalisés qui ont une vue de table. Le fait d'appuyer sur un nouveau contrôleur sur le contrôleur de résultats a entraîné la recherche de tableview.

Le code listé ci-dessus a totalement résolu le problème

Vitalii Shvetsov
la source