Question

Un graphique est dessiné dans un UIScrollView . Il s’agit d’un UIView de grande taille qui utilise une sous-classe personnalisée de CATiledLayer en tant que couche.

Lorsque je fais un zoom avant et arrière sur UIScrollView , je souhaite que le graphique soit redimensionné de façon dynamique, comme il le ferait lorsque je renverrai le graphique à partir de viewForZoomingInScrollView . Toutefois, le graphique se redessine au nouveau niveau de zoom et je souhaite réinitialiser l'échelle de transformation à 1x1 afin que, lors du prochain zoom, la transformation commence à partir de la vue actuelle. Si je réinitialise la transformation en identité dans scrollViewDidEndZooming , cela fonctionne dans le simulateur, mais jette un EXC_BAD_ACCSES sur le périphérique.

Cela ne résout même pas entièrement le problème sur le simulateur, car la prochaine fois que l'utilisateur effectue un zoom, la transformation se réinitialise sur le niveau de zoom auquel elle était, ce qui donne l'impression que si je fais un zoom 2x, par exemple, soudainement à 4x. Lorsque je termine le zoom, il se termine à la bonne échelle, mais le zoom réel semble mauvais.

Alors tout d’abord: comment permettre au graphique de se redessiner à l’échelle standard de 1x1 après le zoom et comment puis-je utiliser un zoom continu?

Modifier: Nouvelles découvertes L'erreur semble être " [CALayer retentionCount]: message envoyé à l'instance désallouée "

.

Je ne désalloue jamais aucune couche moi-même. Avant, je ne supprimais même pas de vues ou quoi que ce soit. Cette erreur était en cours de zoom et de rotation. Si je supprime l'objet avant la rotation et le rajoute après, il ne lève pas l'exception. Ce n'est pas une option pour zoomer.

Était-ce utile?

La solution

Je ne peux pas vous aider avec le plantage, si ce n’est vous dire de vérifier et de vous assurer que vous n'êtes pas involontairement en libérant automatiquement une vue ou une couche quelque part dans votre code. J'ai vu le simulateur gérer le minutage des autoreleases différemment de celui utilisé sur le périphérique (le plus souvent lorsque des threads sont impliqués).

La mise à l'échelle de la vue est un problème rencontré avec UIScrollView . Au cours d'un événement de pincement au zoom, UIScrollView reprend la vue que vous avez spécifiée dans la méthode déléguée viewForZoomingInScrollView: et lui applique une transformation. Cette transformation permet une mise à l'échelle en douceur de la vue sans avoir à la redessiner à chaque image. À la fin de l'opération de zoom, votre méthode de délégation scrollViewDidEndZooming: withView: atScale: sera appelée et vous permettra d'effectuer un rendu de meilleure qualité de votre vue avec le nouveau facteur d'échelle. En règle générale, il est suggéré de réinitialiser la transformation sur votre vue pour qu'elle soit CGAffineTransformIdentity , puis de redessiner manuellement votre vue à la nouvelle échelle de taille.

Toutefois, cela pose un problème car UIScrollView ne semble pas surveiller la transformation de la vue contenu. Par conséquent, lors de la prochaine opération de zoom, il définit la transformation de la vue contenu sur le facteur d'échelle global. . Puisque vous avez redessiné manuellement votre vue au dernier facteur d’échelle, la composition est redimensionnée, comme vous le voyez.

Pour contourner le problème, j'utilise une sous-classe UIView pour ma vue de contenu avec les méthodes suivantes définies:

- (void)setTransformWithoutScaling:(CGAffineTransform)newTransform;
{
    [super setTransform:newTransform];
}

- (void)setTransform:(CGAffineTransform)newValue;
{
    [super setTransform:CGAffineTransformScale(newValue, 1.0f / previousScale, 1.0f / previousScale)];
}

où previousScale est une variable d'instance float de la vue. J'implémente ensuite la méthode de délégation de zoom comme suit:

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale;
{
    [contentView setTransformWithoutScaling:CGAffineTransformIdentity];
// Code to manually redraw view at new scale here
    contentView.previousScale = scale;
    scrollView.contentSize = contentView.frame.size;
}

Ce faisant, les transformations envoyées à la vue de contenu sont ajustées en fonction de l'échelle à laquelle la vue a été redessinée pour la dernière fois. Lorsque le zoom par pincement est terminé, la transformation est réinitialisée à une échelle de 1.0 en ignorant l'ajustement dans la méthode normale setTransform: . Cela semble fournir le comportement de mise à l’échelle correct tout en vous permettant de dessiner une vue nette à la fin d’un zoom.

UPDATE (23/07/2010): iPhone OS 3.2 et versions ultérieures ont modifié le comportement des vues de défilement en ce qui concerne le zoom. Maintenant, un UIScrollView respectera la transformation d'identité que vous appliquez à une vue de contenu et fournira uniquement le facteur d'échelle relatif dans -scrollViewDidEndZooming: withView: atScale: . Par conséquent, le code ci-dessus pour une sous-classe UIView n'est nécessaire que pour les appareils exécutant des versions iPhone OS antérieures à la version 3.2.

Autres conseils

Merci à toutes les réponses précédentes et voici ma solution.
Implémentez les méthodes UIScrollViewDelegate :

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    return tmv;
}

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
    CGPoint contentOffset = [tmvScrollView contentOffset];   
    CGSize  contentSize   = [tmvScrollView contentSize];
     CGSize  containerSize = [tmv frame].size;

    tmvScrollView.maximumZoomScale = tmvScrollView.maximumZoomScale / scale;
    tmvScrollView.minimumZoomScale = tmvScrollView.minimumZoomScale / scale;

    previousScale *= scale;

    [tmvScrollView setZoomScale:1.0f];

    [tmvScrollView setContentOffset:contentOffset];
    [tmvScrollView setContentSize:contentSize];
    [tmv setFrame:CGRectMake(0, 0, containerSize.width, containerSize.height)];  

    [tmv reloadData];
}
  • tmv est ma sous-classe d'UIView
  • tmvScrollView & # 8212; sortie vers UIScrollView
  • définir maximumZoomScale et minimumZoomScale avant
  • créer une variable d'instance previousScale et définir sa valeur sur 1.0f

Fonctionne parfaitement pour moi.

BR, Eugene.

Je cherchais simplement à réinitialiser le chargement à l’échelle de zoom par défaut, car lorsque je l’agrandis, scrollview a la même échelle de zoom pour la prochaine fois jusqu’à ce qu’elle soit désallouée.

-(void) viewWillAppear:(BOOL)animated {
      [self.scrollView setZoomScale:1.0f];
}

Modification du jeu.

J'ai une discussion détaillée sur le fonctionnement (et pourquoi) du zoom UIScrollView à l'adresse github.com/andreyvit/ScrollingMadness/. .

(Le lien contient également une description de la méthode de zoom par programme pour UIScrollView, de l'émulation de la pagination + du zoom de la photothèque, d'un projet exemple et de la classe ZoomScrollView qui encapsule une partie de la magie du zoom.)

Citation:

UIScrollView n'a pas la notion de & # 8220; niveau de zoom actuel & # 8221 ;, car chaque sous-vue qu'elle contient peut avoir son propre niveau de zoom actuel. Notez qu'il n'y a pas de champ dans UIScrollView pour conserver le niveau de zoom actuel. Cependant, nous savons que quelqu'un enregistre ce niveau de zoom, car si vous pincez une sous-vue par zoom, puis réinitialisez sa transformation en CGAffineTransformIdentity, puis vous resserrez à nouveau, vous remarquerez que le niveau de zoom précédent a été restauré.

En effet, si vous regardez le désassemblage, c’est UIView qui stocke son propre niveau de zoom (dans l’objet UIGestureInfo pointé par le champ _gestureInfo). Il contient également un ensemble de méthodes non documentées telles que zoomScale et setZoomScale: animated: . (Rappelez-vous, il existe également un tas de méthodes liées à la rotation. Peut-être que nous aurons bientôt le support des gestes de rotation.)

Cependant, si nous créons un nouvel UIView juste pour le zoom et ajoutons notre véritable vue zoomable à son enfant, nous commencerons toujours par le niveau de zoom 1.0. Mon implémentation du zoom programmatique est basée sur cette astuce.

Voici une approche relativement fiable, adaptée à iOS 3.2, 4.0 et versions ultérieures. Vous devez être prêt à fournir votre vue évolutive (la sous-vue principale de la vue de défilement) dans toute échelle demandée. Ensuite, dans scrollViewDidEndZooming: , vous supprimerez la version de cette vue transformée en échelle floue et vous la remplacerez par une nouvelle vue dessinée à la nouvelle échelle.

Vous aurez besoin de:

  • Un ivar pour maintenir l’échelle précédente; appelons cela oldScale

  • Un moyen d'identifier la vue évolutive, à la fois pour viewForZoomingInScrollView: et pour scrollViewDidEndZooming: ; ici, je vais appliquer un tag (999)

  • Une méthode qui crée la vue évolutive, la balise et l'insère dans la vue de défilement (je l'appellerai ici addNewScalableViewAtScale: ). N'oubliez pas qu'il doit tout faire en fonction de son échelle: il redimensionne la vue et ses sous-vues ou dessins, et redimensionne le contenu de la taille du contenu de la vue de défilement.

  • Constantes pour minimumZoomScale et maximumZoomScale. Celles-ci sont nécessaires car, lorsque nous remplaçons la vue réduite par la version agrandie détaillée, le système va penser que l'échelle actuelle est 1, nous devons donc la tromper pour permettre à l'utilisateur de réduire la vue.

Très bien alors. Lorsque vous créez la vue de défilement, vous insérez la vue évolutive à l'échelle 1.0. Cela peut être dans votre viewDidLoad , par exemple ( sv est la vue de défilement):

[self addNewScalableViewAtScale: 1.0];
sv.minimumZoomScale = MIN;
sv.maximumZoomScale = MAX;
self->oldScale = 1.0;
sv.delegate = self;

Ensuite, dans votre scrollViewDidEndZooming: , vous faites la même chose, mais en compensant le changement d'échelle:

UIView* v = [sv viewWithTag:999];
[v removeFromSuperview];
CGFloat newscale = scale * self->oldScale;
self->oldScale = newscale;
[self addNewScalableViewAtScale:newscale];
sv.minimumZoomScale = MIN / newscale;
sv.maximumZoomScale = MAX / newscale;

Notez que la solution fournie par Brad a cependant un problème: si vous continuez à effectuer un zoom avant par programme (par exemple, en cas de double tap) et laissez l’utilisateur manuellement (pincer) un zoom arrière, après quelque temps la différence entre l’échelle vraie et l'échelle suivie par UIScrollView deviendra trop grande. Par conséquent, previousScale finira par perdre la précision de float, ce qui entraînera un comportement imprévisible

Swift 4. * et Xcode 9.3

Régler l’échelle de zoom du scrollView sur 0 réinitialisera votre scrollView à son état initial.

self.scrollView.setZoomScale(0.0, animated: true)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top