web-dev-qa-db-fra.com

L'origine d'UIScrollView change après le retour à UIViewController

J'ai une sous-classe UIViewController en tant que scène dans le storyboard qui contient un UIScrollView contenant diverses sous-vues. L'une des sous-vues est une UIButton qui enchaîne dans une autre scène UIViewController sous-classe. Quand je reviens de la vue (faites sortir le UIViewController de la pile du contrôleur de navigation), je trouve que l'origine de la vue de défilement a changé, bien que les contentsize et contentoffset semblent correct.

Ce qui est également intéressant, c'est que l'application a une barre d'onglets, et lorsque je tabule et reviens à cette vue, la vue de défilement est correctement réglée avec un décalage à (0, 0).

Il n'y a fondamentalement aucun code impliqué dans ce processus, car il est à peu près tout dans le storyboard. Comme je suis assez nouveau dans l'utilisation du storyboard, je pense que je fais quelque chose de mal, même si je ne sais pas quoi. Des idées sur ce que cela peut être? Peut-être des problèmes ou des contraintes de dimensionnement?

57
Peter Jacobs

En fait, je mets cette ligne de code dans viewDidDisappear, et pour qu'elle se souvienne du décalage lorsque la vue réapparaît, j'ai ajouté cette ligne avant

 self.contentOffset = self.scrollView.contentOffset;

aussi bien que

 - (void)viewDidLayoutSubviews { 
       self.scrollView.contentOffset = self.contentOffset; 
 }
11
Peter Jacobs

Dans iOS 7/8/9 simple self.automaticallyAdjustsScrollViewInsets = NO; a résolu le problème dans mon cas.

92
Alex

Essayez ceci dans viewWillAppear du contrôleur de vue dans lequel vous revenez:

 self.scrollView.contentOffset = CGPointMake(0, 0);

Edit: Lorsque vous ajoutez également le code de Peter, vous obtenez les meilleurs résultats avec:

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:YES];
    self.scrollView.contentOffset = CGPointMake(0, 0);
}

plus

- (void)viewWillDisappear:(BOOL)animated {  
    self.recentContentOffset = self.scrollView.contentOffset;
    [super viewWillDisappear:animated];
}

et

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    self.scrollView.contentOffset = CGPointMake(0, self.recentContentOffset.y);
}

Vous revenez à la position de défilement d'origine, n'avez aucun effet secondaire visuel et la vue de défilement elle-même est correctement positionnée dans sa vue d'ensemble (ce qui était un problème) après son retour.

30
MacMark

j'ai eu un problème similaire, après avoir rejeté un viewController, le contentOffset de ma tableView a été changé en (0, -64).

ma solution était un peu bizarre, j'ai essayé toutes les autres réponses mais sans succès, la seule chose qui a résolu mon problème était de changer la position tableView dans l'arborescence des contrôles du .xib

c'était le premier contrôle dans la vue parent comme ceci:

before

J'ai déplacé la tableView juste après l'ImageView et cela a fonctionné:

after

il semble que placer la vue de table dans la première position était à l'origine du problème, et déplacer la vue de table vers une autre position a résolu le problème.

P.D. Je n'utilise ni la mise en page automatique ni les storyboards

j'espère que cela peut aider quelqu'un!

5
Chuy47

J'utilise une collectionView et j'ai eu un problème similaire. Pour iOS 11: dans l'inspecteur de taille, il y a "encart de contenu". Réglez cela sur "Jamais". Cela a résolu le problème pour moi. J'espère que ça aidera quelqu'un.

Objectif c:

if (@available(iOS 11, *)) {
   [UIScrollView appearance].contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}

Rapide:

if #available(iOS 11, *) {
UIScrollView.appearance().contentInsetAdjustmentBehavior = .never
}
5
Joseph Francis

Dans iOS 11, j'ai rencontré un problème similaire: lorsque je reviens après avoir fait éclater un contrôleur de vue, la vue de table dans le contrôleur de vue précédent utilisé pour ajuster automatiquement son encart de contenu, ce qui a entraîné le saut brutal du contenu de la vue de dessus. La solution suivante a fonctionné pour moi dans iOS 11.

Dans Storyboard, sélectionnez la table et accédez à l'inspecteur d'attributs et décochez "Automatique" pour les champs Hauteur de la ligne et Estimation. Modifiez également les encarts de contenu de "Automatique" à "Jamais".

[Table view]

2
Nithin

Malheureusement, les suggestions de Peter et MacMark n'ont pas fonctionné pour moi (Xcode 5 avec mise en page automatique). La solution consistait à accéder au storyboard, à sélectionner le contrôleur de vue et à Reset to Suggested Constraints in View Controller.

enter image description here

1
Kyle Clegg

J'ai récemment posté une question sur un problème similaire, mais dans un contexte différent: le cadre ne reflète pas les contraintes de mise en page automatique après la fermeture du contrôleur de vue modale

Dans mon cas, l'origine d'une vue de conteneur personnalisée à l'intérieur de la vue de défilement est déplacée, plutôt que l'origine de la vue de défilement elle-même. En ce qui concerne la disposition automatique, la vue de conteneur personnalisée est épinglée sur les quatre côtés de la vue de défilement.

La vue du conteneur est créée et configurée par programme plutôt que dans IB. Bien que les contraintes de la vue de conteneur soient inchangées, l'origine de la vue de conteneur est déplacée après la fermeture d'un contrôleur de vue modale. Cette déconnexion entre contraintes et trames est inexplicable.

Ce qui est encore plus remarquable, c'est que le problème de déplacement d'origine est resté après que j'ai supprimé et rajouté toutes les contraintes associées.

J'ai trouvé une solution similaire: enregistrez la valeur du décalage de contenu actuel, définissez le décalage de contenu sur "zéro", laissez le système échanger vos vues, puis restaurez le décalage de contenu (c'est-à-dire une danse de décalage de contenu).

Dans mon cas, cependant, j'ai dû prendre des mesures supplémentaires pour résoudre le problème. Ma vue de défilement est une vue de défilement de pagination qui obtient ses sous-vues à partir d'une méthode tilePages (démontrée dans une ancienne vidéo WWDC). La modification du décalage de contenu de la vue de défilement déclenche la méthode tilePages via la méthode scrollViewDidScroll: de UIScrollViewDelegate. J'ai dû définir un indicateur pour désactiver tilePages pendant que je faisais la danse avec décalage de contenu, sinon l'application se bloquerait.

J'espère que je pourrai supprimer le code de la danse à contenu décalé lorsque je passerai à iOS 7.

0
bilobatum
- (void)viewDidDisappear:(BOOL)animated {
    [super viewDidDisappear:animated];

    if ([[[UIDevice currentDevice] systemVersion] floatValue] < 7) {
        _isRecoredforios6 = YES;
        _recordedOffsetforios6 = _scrollView.contentOffset;
        _recordedSizeforios6 = _scrollView.contentSize;
        _scrollView.contentOffset = CGPointZero;
    }

}

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    if (_isRecoredforios6) {
        _isRecoredforios6 = NO;
        _scrollView.contentSize = _recordedSizeforios6;
        _scrollView.contentOffset = _recordedOffsetforios6;
    }
}

J'ai corrigé ce bogue ios6, peut être résolu en utilisant ces codes. J'ai résolu un bug survenu dans Scrollview. Merci avant tout mes amis!

0
J.Mi

J'ai utilisé une combinaison des différentes solutions publiées partout sur SO, et j'ai trouvé cette sous-classe:

// Keeps track of the recent content offset to be able to restore the
// scroll position when a modal viewcontroller is dismissed
class ScrollViewWithPersistentScrollPosition: UIScrollView {

    // The recent content offset for restoration.
    private var recentContentOffset: CGPoint = CGPoint(x: 0, y: 0)

    override func willMove(toWindow newWindow: UIWindow?) {
        if newWindow != nil {
            // save the scroll offset.
            self.recentContentOffset = self.contentOffset
        }
        super.willMove(toWindow: newWindow)
    }

    override func didMoveToWindow() {
        if self.window != nil {
            // restore the offset.
            DispatchQueue.main.async(execute: {
                // restore it
                self.contentOffset = self.recentContentOffset
            })
        }
        super.didMoveToWindow()
    }
 }

Testé sur iOS 11.2/Xcode 9.2.

0
KBog

Problème continu lorsque vous suivez les réponses actuelles:

La deuxième tentative pour ouvrir le contrôleur de vue présenté, sans avoir quitté le contrôleur de vue présenté, le problème est resté. C'est pourquoi je poste le étapes exactes qui a abouti à ma solution.

  1. Donc, je réinitialiser les contraintes de collectionView dans le Storyboard, en veillant à ce qu'elles soient épinglées à la vue principale de ViewController.

  2. ajouté: self.view.translatesAutoresizingMaskIntoConstraints = YES; à l'intérieur du viewDidLoad du contrôleur de vue de présentation.

  3. Et stocké, en privé, le contentOffset de la vue de collection avant l'apparition du contrôleur de vue présenté de façon modale:

    (void)viewWillDisappear:(BOOL)animated
    {
        [super viewWillDisappear:animated];
    
        self.contentOffset = self.collectionView.contentOffset;
        self.collectionView.contentOffset = CGPointZero;
    }
    
    - (void)viewDidLayoutSubviews {
         [super viewDidLayoutSubviews];
         self.collectionView.contentOffset = self.contentOffset;
    }
    
0
ChrisHaze

Avez-vous essayé de vérifier l'option Étendre les bords sous les barres opaques? Il peut être trouvé dans votre inspecteur d'attributs de contrôleur à l'intérieur du storyboard.

Cela a fait l'affaire pour moi en utilisant XCode 9 iOS 11.

enter image description here

0
Swift Rabbit

récemment, j'ai rencontré ce bug, peut être résolu en utilisant ces codes:

// fix ios 6 bug begin
- (void)viewDidDisappear:(BOOL)animated
{
    [super viewDidDisappear:animated];

    if ([[[UIDevice currentDevice] systemVersion] floatValue] < 7) {
        isRecoredforios6 = YES;
        recordedOffsetforios6 = self.tableView.contentOffset;
        recordedSizeforios6 = self.tableView.contentSize;
    }
}

- (void)viewDidLayoutSubviews
{
    [super viewDidLayoutSubviews];
    if (isRecoredforios6) {
        isRecoredforios6 = NO;
        self.tableView.contentSize = recordedSizeforios6;
        self.tableView.contentOffset = recordedOffsetforios6;
    }
}
// fix ios 6 bug end

merci Peter Jacobs!

0
Trouble maker

Rien de ce qui précède n'a fonctionné pour moi, j'ai réussi à faire ma propre animation push/pull personnalisée à la place et cela fonctionne comme un charme.

Tout d'abord, ajoutez cette classe qui implémente le scénario Push

class PushAnimator: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return 0.5
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

    // get reference to our fromView, toView and the container view that we should perform the transition in
    let container = transitionContext.containerView
    let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
    let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!

    // start the toView to the right of the screen
    var frame = toView.frame
    frame.Origin.x = container.frame.width
    toView.frame = frame

    // add the both views to our view controller
    container.addSubview(toView)
    container.addSubview(fromView)

    // get the duration of the animation
    let duration = self.transitionDuration(using: transitionContext)

    // perform the animation!
    UIView.animate(withDuration: duration, animations: {

        var frame = fromView.frame
        frame.Origin.x = -container.frame.width
        fromView.frame = frame

        toView.frame = container.bounds

    }, completion: { _ in
        // tell our transitionContext object that we've finished animating
        transitionContext.completeTransition(true)

    })
}
}

Ajoutez ensuite cette classe qui implémente le scénario pop

import Foundation
import UIKit

class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
    return 0.5
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
    // get reference to our fromView, toView and the container view that we should perform the transition in
    let container = transitionContext.containerView
    let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)!
    let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)!

    // set up from 2D transforms that we'll use in the animation
    let offScreenRight = CGAffineTransform(translationX: container.frame.width, y: 0)

    // start the toView to the right of the screen
    var frame = toView.frame
    frame.Origin.x = -container.frame.width
    toView.frame = frame

    // add the both views to our view controller
    container.addSubview(toView)
    container.addSubview(fromView)

    // get the duration of the animation
    let duration = self.transitionDuration(using: transitionContext)

    // perform the animation!
    UIView.animate(withDuration: duration, animations: {

        fromView.transform = offScreenRight
        toView.frame = container.bounds

    }, completion: { _ in
        // tell our transitionContext object that we've finished animating
        transitionContext.completeTransition(true)

    })
}
}

Ajoutez ensuite cette ligne à votre méthode viewDidLoad pour modifier le délégué par défaut NavigationController

self.navigationController?.delegate = self

Et voyez la magie :)

0
Elsammak

Juste au cas où rien de ce qui précède n'a aidé, j'ai eu ce problème ainsi que le problème pour moi était que j'avais le code suivant ..

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    tableView.reloadData()
}

Commentant cette ligne reloadData () a résolu le problème pour moi. Je n'avais pas besoin de cette ligne, donc je ne sais pas pourquoi je l'ai même ajoutée!

Espérons que cette solution aide quelqu'un d'autre.

0
teh_raab

Après avoir essayé beaucoup de choses:

  • Définissez automatiquementAdjustsScrollViewInsets sur false pour voir si le problème venait de la barre de navigation.
  • Jouer avec des hauteurs de cellule fixes ...
  • Mise en cache des hauteurs de cellule ...
  • Conserver une propriété avec le décalage de vue de table, la mettre en cache et la définir sur viewWillAppear ...
  • etc.

J'ai réalisé que le problème concernait la valeur estimatedRowHeight, qui était définie sur 50 pixels, alors qu'elle devrait être ~ 150 pixels. La mise à jour de la valeur a résolu le problème et maintenant la vue de table conserve le même décalage.

0
jomafer