[RESOLU]Centre et taille de la ScrollView Xcode 6

MayerickMayerick Membre
octobre 2014 modifié dans Dev. iOS, watchOS, tvOS #1
Dans un projet actuel, j'ai une scrollView qui contient une imageView que j'aimerais centrer si la taille de l'imageView * zoomScale < scrollView.frame ou ajuster dans le coin supérieur gauche si la taille est supérieure. Sous Xcode 5 je m'arrangeais avec une série de contraintes que j'ajoutais à  la méthode updateViewConstraint. Avec Xcode 6 je n'ai plus besoin de ces contraintes pour qu'autolayout gère certaines choses qui posaient problème avant.


Le problème maintenant c'est que même en mettant le contentMode de la scrollView et de l'imageView en modeCenter, la vue ne se centre pas. Quand je surcharge la méthode updateViewConstraint, il tasse deux fois dedans quand la vue se charge, et les deux fois la frame de ma scrollView change, ce qui fait que mon image apparaà®t dans une position puis se déplace finalement au centre de la vue lorsque la frame de la scrollView change pour devenir la bonne et définitive.


un bout de code pour illustrer tout ça :
-(void)updateViewConstraints    {        CGFloat sHeight =[UIScreen mainScreen].bounds.size.height ;        CGFloat sWidht = [UIScreen mainScreen].bounds.size.width;                CGSize size = imagesView.image.size;                CGFloat height = size.height * VuePrincipaleScrollView.zoomScale;        CGFloat width = size.width *VuePrincipaleScrollView.zoomScale;                        //hauteur inférieur, largeur, aucun ?        // Si l'image est moins large et moins haute  que l'écran        if (width < sWidht && height < sHeight) {                                    //on centre l'image                    herosView.center = VuePrincipaleScrollView.center;                     if (herosView.frame.origin.x < 0) {                herosView.frame = CGRectMake(0, herosView.frame.origin.y, herosView.frame.size.width, herosView.frame.size.height);            }            if (herosView.frame.origin.y < 0) {                herosView.frame = CGRectMake(herosView.frame.origin.x,0 , herosView.frame.size.width, herosView.frame.size.height);            }                        NSLog(@centre de la scrollView = x %0.f y %0.f, VuePrincipaleScrollView.center.x, VuePrincipaleScrollView.center.y);                        NSLog(@vuePrinc frame = %0.f et %0.f, VuePrincipaleScrollView.frame.size.width, VuePrincipaleScrollView.frame.size.height);}[super ...]        }

quand la page se charge j'obtiens les logs suivant :
2014-10-10 21:40:25.891 Architecture Plan[14928:266715] centre de la scrollView: x 160 y 1862014-10-10 21:40:25.891 Architecture Plan[14928:266715] vuePrinc frame = 320 et 3722014-10-10 21:40:26.495 Architecture Plan[14928:266715] centre de la scrollView: x 160 y 2522014-10-10 21:40:26.495 Architecture Plan[14928:266715] vuePrinc frame = 320 et 504

Je constate donc qu'on passe deux fois dans updateViewConstraints et que la taille de la scrollView change d'une fois à  l'autre. Si cela ne produisait pas un effet graphique désagréable, cela pourrait passer, mais là  l'image apparait d'abord puis se repositionne, ça ne fait pas propre et ça fait quelque temps que je ne trouve pas pourquoi. Je ne fais d'appel à  updtateViewConstraints dans le viewDidLoad ni nul part ailleurs lors du chargement de la vue. Les seules contraintes que j'ai sur la scrollView sont leading/trailing space to margin et bottom/top layout space to layout guide, elle devrait donc s'adapter automatiquement à  la taille de l'écran. J'utilise une navigation bar et j'ai l'impression que le décalage correspond à  la taille de la navigation bar. Enfin dans storyboard j'ai décoché les cases under top bars et under bottom bars.


Une idée sur le pourquoi du comment ? Je ne comprend vraiment pas pourquoi la taille de cette scrollView change ! !
Mots clés:

Réponses

  • Bon ben j'ai trouvé, c'est à  cause de la toolBar en fait. J'expliquerai en détail demain matin. Je suis un peu fatigué là . Désolé pour le post inutile !! ^^


  • AliGatorAliGator Membre, Modérateur
    Moi tout ce que je vois dans ton code c'est que :

    - Tu ne respectes pas les conventions de nommage (ça aide clairement pas à  lire et pire ça fait comprendre de travers ton code)

    - Tu implémentes "updateLayoutConstaints" qui est une méthode qui, comme son nom l'indique re permet de mettre à  jour des LayoutConstraints... sauf que dans t'ce code tu ne modifies, ajoutes ou supprimes AUCUNE contrainte ! Encore tu utiliserais layoutSubviews ou la méthode adaptée je dis pas mais là ...

    - En + tu fais des choses pas logiques qui laissent présager que tu n'as pas compris comment marchent les frames/bounds/center. Quand je vois "herosView.center = VuePrincipaleScrollView.center" alors que chacune de ces 2 vues est dans un référentiel different je me dis qu'il y a un problème de compréhension qqpart... pour rappel uneVue.center c'est les coordonnées de uneVue dans sa superview...
  • MayerickMayerick Membre
    octobre 2014 modifié #4
    Oups tu as raison, ça ne me ressemble pas trop pour les conventions de nommage, c'est une étourderie que je ne reproduirai plus.. Pour ma défense c'est la seule variable a ne pas être camelCase. :/ Puis pour le nom des objets c'est vrai que j'ai un peu craqué sur ce projet, qui était à  la base un test sensé être vite fait, mais qui s'est allongé sans reprendre les bonnes habitudes, on en a déjà  parlé sur le forum (mais c'était après le début de ce projet que j'ai lu ça), les prototypes et les tests à  l'arrache c'est le mal, j'ai bien compris.


    Ok ok, alors en fait comme j'expliquais j'ajoutais des contraintes et je modifiais des contraintes existantes et crée par le code dans Xcode 5, alors que dans Xcode 6 j'ai pu toute les supprimer. J'avais besoin de repositionner l'image en fonction de sa taille et donc d'ajouter ou supprimer des contraintes. Du coup quand j'ai modifié le code je l'ai laissé dans updateViewConstraint. Je sais que dans XCode 5 on ne pouvait pas modifier la frame ou l'origine d'un objet dans updateViewConstraint, il fallait passer par les contraintes. C'est vrai que je n'ai pas réfléchis s'il fallait placer ce code ailleurs. Disons que j'en ai besoin quand rotateToInterface et scrollViewDidEndZooming sont appelé, et updateViewConstaints semblait pas mal. Je vais donc essayer de voir avec layoutSubviews ou la méthode adaptée (que je vais chercher car je ne vois pas de laquelle tu parles), merci pour le tuyau en tout cas.


    Je comprends pas trop pourquoi tu dis que les vues ne sont pas dans le même référentiel. Sans doute que j'ai mal expliqué, ou alors en effet je n'ai pas compris quelque chose. J'ai ma self.view qui contient une scrollView, cette scrollView contient une imageView (herosView) cette imageView contient une image. Je veux que l'imageView soit toujours centré dans la scrollView tant que la taille de l'image est inférieure à  celle de l'écran. Si je ne met pas herosView.center = vuePrinciapeScrollView.center, alors l'image se met en haut à  gauche, et ce même si tout est réglé en contentModeCenter. Je veux donc bien que le centre de mon imageView soit le centre de ma scrollView, scrollView qui est bien la superView de l'imageView. Mais j'admets que mon premier post était carrément brouillon et plutôt désespéré, donc désolé pour mon imprécision. De plus je ne suis pas l'abri de ne pas avoir compris quelque chose.


    Merci en tout cas pour ton éclairage et tes conseils.
  • AliGatorAliGator Membre, Modérateur
    octobre 2014 modifié #5
    "herosView.center = VuePrincipaleScrollView.center"


    "Les coordonnées du centre de herosView dans le référentiel de sa vue parente doivent être égaux aux coordonnees du centre de la vue parente dans sa superview à  elle"


    T'a comme un problème...

    Si la scrollView fait disons 200x200 et qu'elle est placée en (x,y)=(50,50) dans sa vue parente alors son centre à  elle sera en (150,150) dans le referentiel de sa superview. Et toi avec ton code tu es donc en train de demander que le centre de ton imageView soit la même valeur (150,150) mais donc dans le referentiel de la superview de ton imageView donc dans le referentiel de la scrollview !


    C'est un peu comme si tu regardais l'heure d'une réunion qui se passe à  New York, tu vois que c'est 08:30, et tu appliques la même valeur 08:30 à  une autre réunion à  Paris en te disant "comme ça elles seront à  la même heure" bah oui mais non, si tu prends les heures sans trop réfléchir au referentiel (fuseau horaire) dans lequel tu les interprètes, ça va pas marcher, car il y a un décalage horaire entre Paris et NewYork... comme il y a un décalage entrz le referentiel de ta scrollView et celui de sa superview (et ce décalage depend de la position de la scrollView dans sa superview)



    Remplace dans ta tête la propriété "center" par un nom qui serait plus verbeux mais plus explicite à  savoir "coordinateOfCenterInItsSuperview"
  • AliGatorAliGator Membre, Modérateur
    En gros toi ce que tu veux c'est :


    "coordonnées du centre de herosView exprimées dans le référentiel la scrollView = coordonnées du centre de la scrollView... exprimées dans le même référentiel (c'est à  dire, aussi dans le référentiel de la scrollView elle-même)"


    Mais ce que ton code fait c'est "coordonnees du centre de herosView dans un referentiel = coordonnees du centre d'une autre vue dans un referentiel different", c'est comme comparer des patates de des carottes ou comparer des heures dans des fuseaux horaires différents.
  • MayerickMayerick Membre
    octobre 2014 modifié #7

    D'accord, je pense comprendre un peu mieux, merci pour toutes ces images ! 


     


    Donc première chose que je comprend, c'est qui si mon code fonctionne c'est juste par un heureux hasard (hasard qui est que la taille de la scrollView est celle de sa superView /*édit : en fait non quelque soit la taille de la scrollView dans sa superview, herosView reste centré dans la scrollView et le code fonctionne*/). 


     


    Je perçois l'erreur de compréhension que j'ai faite. Comme on insère les objets dans une hiérarchie, je pensais naà¯vement que pour faire appel au référentiel de la scrollView et fallait préciser scrollView.superview.center. Mais non, je pense avoir compris. L'idée de remplacer herosView.center par une périphrase est très malin, d'autant que je me servait parfois de noms à  rallonge pour les variables au début pour être le plus explicite possible, donc merci ça aide à  comprendre !


     


    Par contre j'enfonce le clou une dernière fois car peut-être que j'ai pas été clair (ou que j'insiste pour rien, mais j'ai peur qu'un quiproquo te fasse perdre ton temps), mais ma scrollView est crée avec storyBoard, puis dans le viewDidLoad je crée herosVIew et je fais [vuePrincipaleScrollView addSubview: herosVIew]. Donc par défaut herosView se trouve à  [0;0] dans vuePrincipaleScrollView.


     


    Donc pour en revenir à  herosView, pour le moment si j'ai bien compris, mon code dit :


     


    Le centre de herosView dans sa superview (vuePrincipaleScrollVIew) doit être égal au centre de vuePrincpialeScrollView dans sa superview (self.view);


     


    Je vois bien que ce n'est pas dans le même référentiel, mais comment faire pour indiquer à  herosView le centre de la scrollView et pourquoi cela fonctionne quand même quelque soit la taille de la scrollView ? Car du coup le code : herosView.center = herosView.superview.center revient exactement au même que le code précédent. A moins que pour avoir le référentiel de la scrollView il faille que je calcule le centre x et y de la scrollview puis que je fasse herosView.center = CGPointMake(x, y); ? Comme ça je ne compare plus des choux et des carottes. :)


  • AliGatorAliGator Membre, Modérateur
    Quelles que soient les tailles de ta scrollView et sa superview, ton code va functionner si ta scrollView est en (0,0) dans sa superview car dans ce cas les référentiels correspondent. Mais pas si elle est en (50,50) par exemple.


    Pour calculer les coordonnées du centre de la scrollView dans son propre référentiel tu as plusieurs façons de faire :


    1) Calculer le milieu des bounds de la scrollView. Il faut bien prendre les bounds et non la frame bien sûr, encore une fois pour etre dans le bon référentiel :
    CGFloat cx = CGRectGetMidX(scrollView.bounds);
    CGFloat cy = CGRectGetMidY(scrollView.bounds);
    herosView.center = CGPointMake(cx,cy);
    (pour rappel frame = dans le référentiel de la vue parente et bounds = dans le référentiel de la vue elle-même. Si tu utilisais CGRectGetMid...(scrollView.frame) ça reviendrait à  retomber sur la valeur de scrollView.center !)


    2) Utiliser les méthodes de conversion de coordonnées d'une vue vers l'autre : -[UIView convertPoint:fromView:] ou -[UIView convertPoint:toView:] :
    CGPoint scrollviewCenterInItself = [self.scrollView.superview convertPoint:self.scrollView.center toView:self.scrollView];
  • MayerickMayerick Membre
    octobre 2014 modifié #9

    C'est parfaitement clair, merci pour les précisions sur le pourquoi du comment. C'est donc bien un heureux hasard si mon code fonctionnait, merci d'avoir mis ça en lumière. Je vais chercher les autres endroits où j'aurais pu faire ça dans d'autres vues ou app et prendre l'habitude de me questionner un peu plus sur le référentiel dans lequel je travaille.


     


    Les deux exemples sont super clair aussi, le premier semble plus explicite au premier abord mais le deuxième est en fait tout aussi intéressant.


     


    Par contre il y a quelque chose que je ne comprends pas, quand la vue apparait, la valeur retournée est la bonne, mais quand je zoom dans la scrollView les valeurs ne sont plus exactement les bonnes, et l'image se décale du centre au fur et a mesure que je zoom.


     


    Au chargement de la vue tout roule quand je log cx et cy



    centre de herosView : cx 160 cy 186
    2014-10-11 15:46:14.184 Architecture Plan[21856:425817] vue principaleScrolView.bounds = 320, 372


     par contre quand je zoom j'obtiens ça : 



    centre de herosView: cx 170 cy 198
    2014-10-11 15:48:21.186 Architecture Plan[21856:425817] vue principaleScrollView.bounds = 320, 372

    pourquoi les valeurs changent alors que je ne touche à  rien ? le centre est censé rester le même non ? 


  • AliGatorAliGator Membre, Modérateur
    Si, tu touches à  quelquechose puisque tu zoomes : regarde les bounds en entier et pas juste leur size.
  • MayerickMayerick Membre
    octobre 2014 modifié #12

    ok, je vois où tu veux en venir, vuePrincipaleScrollView.bounds.origine est le coupable puisqu'il change quand je zoom, et change pile de la valeur qui est modifié, logique puisque quand je zoom une partie de la scrollView passe forcément hors/dans l'écran.. Donc pour le moment j'ai fixé avec ce code dans le viewWillLayoutSubviews et j'ai abandonné updateViewConstraints comme tu le soulignais : 



    if (vuePrincipaleScrollView.bounds.origin.x != 0) {
    vuePrincipaleScrollView.bounds = CGRectMake(0,vuePrincipaleScrollView.bounds.origin.y, vuePrincipaleScrollView.bounds.size.width, vuePrincipaleScrollView.bounds.size.height);
    }

    if (vuePrincipaleScrollView.bounds.origine.y != 0) {
    vuePrincipaleScrollView.bounds = CGRectMake(vuePrincipaleScrollView.bounds.origin.x, 0 , vuePrincipaleScrollView.bounds.size.width, vuePrincipaleScrollView.bounds.size.height);
    }

    CGFloat cx = CGRectGetMidX(vuePrincipaleScrollView.bounds);
    CGFloat cy = CGRectGetMidY(vuePrincipaleScrollView.bounds);

    herosView.center = CGPointMake(cx, cy);

    Mais je me doute que ça doit être dégueulasse comme solution pour toi, je vais donc chercher à  améliorer ce code..


    Je sais que frame, bounds et center sont des entités relatives et liés, je vais donc relire le guide que tu m'indiques puis voir si ce soir ça va mieux. Merci pour tout.


  • Pour infos il y a 6 heures de décalage entre Paris (heure d'été) et New York !
  • MayerickMayerick Membre
    octobre 2014 modifié #14

    J'ai bien potassé le guide que tu m'as indiqué, ce n'est pas celui que j'avais lu donc c'est toujours chouette. Mais quelle désarrois en me rendant compte que les super méthodes setBoundsOrigin et setBoundsSize n'étaient pas disponibles sur iOs. ^^


     


    Donc je comprends encore un peu mieux frame, bounds et les "subtilités" de modifications possibles, même si je n'ai pas trouvé mention du centre dans ce guide, c'est quand même super utile à  savoir.


     


    Par contre pour le code, j'ai beau réfléchir, même si je me doute que ce n'est pas propre de modifier le bounds.origin à  chaque fois, mais je ne vois pas comment garder mon image au centre tant que la taille de mon image est inférieure à  celle de l'écran. Je suis toujours preneur de conseils !



    -(void)viewWillLayoutSubviews
    {
    CGFloat sHeight =[UIScreen mainScreen].bounds.size.height ;
    CGFloat sWidht = [UIScreen mainScreen].bounds.size.width;

    CGSize size = imagesView.image.size;

    CGFloat height = size.height * vuePrincipaleScrollView.zoomScale;
    CGFloat width = size.width *vuePrincipaleScrollView.zoomScale;

    // Si l'image est moins large et moins haute que l'écran
    if (width < sWidht && height < sHeight)
    {
    CGPoint origine = vuePrincipaleScrollView.bounds.origin;
    if (origine.x != 0 || origine.y != 0)
    {
    [vuePrincipaleScrollView setBounds:CGRectMake(0, 0, vuePrincipaleScrollView.bounds.size.width, vuePrincipaleScrollView.bounds.size.height)];
    }
    CGFloat cx = CGRectGetMidX(vuePrincipaleScrollView.bounds);
    CGFloat cy = CGRectGetMidY(vuePrincipaleScrollView.bounds);

    herosView.center = CGPointMake(cx, cy);
    }

    //si l'image est moins large, mais plus haute, :
    else if (...)
    /* tout roule */

    ....

    }

    En relisant mon code je remarque que j'ai fais une petite erreur d'explication...  mais qui n'as peut-être pas beaucoup d'importance.. En fait herosView est une UIView qui sert de containerView à  imagesView qui est bien l'imageVIew qui est affiché. J'avais du ajouter cette UIView pour mettre en place le centrage de l'image avec des NSLayoutConstraints lors du zoom. Quand j'ai trouvé qu'une containerView pouvait m'aider à  faire ce que je voulais, j'en ai fais ma herosView. Voici pour les explications inutiles. ^^


Connectez-vous ou Inscrivez-vous pour répondre.