38 votes

UIScrollView Zoom ne fonctionne pas avec Autolayout

Le zoom avec UIScrollView dans un environnement strictement autolayout ne semble pas fonctionner.

C'est d'autant plus frustrant que les notes de mise à jour d'iOS 6 m'ont certainement laissé penser qu'il en serait ainsi lorsqu'il est question d'une "approche de mise en page automatique pure", ici http://developer.apple.com/library/ios/#releasenotes/General/RN-iOSSDK-6_0/_index.html

J'ai consulté les diapositives de la WWDC 2012 pour les sessions 202, 228 et 232 et je n'ai pas trouvé de réponse à cette question.

La seule question que j'ai vue sur Internet concernant spécifiquement ce problème est la suivante Zoom de UIScrollView avec Auto Layout mais il ne fournit pas le code du problème et il n'y a pas de réponse.

Cet utilisateur https://stackoverflow.com/users/341994/matt a donné de nombreuses et excellentes réponses aux questions relatives à l'autolayout de UIScrollView et a même créé un lien vers le code sur le hub git, mais je n'ai pas été en mesure de trouver quelque chose qui réponde à cette question.

J'ai tenté de réduire cette question au strict minimum pour qu'elle soit claire.

J'ai créé une nouvelle application à vue unique avec un storyboard, et je n'ai fait aucun changement dans le constructeur d'interface.

J'ai ajouté un grand fichier image au projet "pic.jpg".

SVFViewController.h

#import <UIKit/UIKit.h> 
@interface SVFViewController : UIViewController <UIScrollViewDelegate>
@property (nonatomic) UIImageView *imageViewPointer;
@end

SVFViewController.m

#import "SVFViewController.h"

@interface SVFViewController ()

@end

@implementation SVFViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    UIScrollView *scrollView = [[UIScrollView alloc] init];
    UIImageView *imageView = [[UIImageView alloc] init];
    [imageView setImage:[UIImage imageNamed:@"pic.jpg"]];
    [self.view addSubview:scrollView];
    [scrollView addSubview:imageView];

    scrollView.translatesAutoresizingMaskIntoConstraints = NO;
    imageView.translatesAutoresizingMaskIntoConstraints = NO;
    self.imageViewPointer = imageView;
    scrollView.maximumZoomScale = 2;
    scrollView.minimumZoomScale = .5;
    scrollView.delegate = self;

    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(scrollView,imageView);
    NSLog(@"Current views dictionary: %@", viewsDictionary);
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
    [scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[imageView]|" options:0 metrics: 0 views:viewsDictionary]];
    [scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[imageView]|" options:0 metrics: 0 views:viewsDictionary]];
}

-(UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView{
    return self.imageViewPointer;
}

@end

Remarquez que j'ai fait un effort particulier pour que ce code ressemble le plus possible à l'exemple de code fourni dans les notes de mise à jour d'iOS 6, en faisant juste le strict minimum pour implémenter le zoom.

Alors, le problème ?

Lorsque vous exécutez cette application et que vous effectuez un panoramique dans la vue défilante, tout est bon. Mais lorsque vous effectuez un zoom, le problème est évident : l'image vacille d'avant en arrière et son positionnement dans la vue défilante devient de plus en plus incorrect à chaque zoom.

Il semble qu'il y ait une bataille en cours pour le décalage du contenu de l'imageView, il semble qu'il soit défini à des valeurs différentes par deux choses différentes à chaque "zoom". (un NSLog de la propriété "content offset" de l'imageView semble le confirmer).

Qu'est-ce que je fais de mal ici ? Quelqu'un sait-il comment mettre en œuvre le zoom dans un UIScrollView dans un environnement purement autolayout ? Existe-t-il un exemple de cela quelque part ?

Aidez-moi, s'il vous plaît.

0 votes

Je suis tombé sur ce ( developer.apple.com/library/ios/#samplecode/PhotoScroller/ ), ce qui me préoccupe plus que de répondre à ma question. Oui, il implémente le zoom UIScrollView en utilisant autolayout, mais il le fait en sous-classant UIScrollView d'une manière compliquée que je ne peux pas suivre. Il doit y avoir un moyen plus simple de le faire...

1 votes

J'ai le même problème. L'intégration d'un UIImageView à l'intérieur d'un UIScrollView n'est pas aussi simple qu'elle ne l'était auparavant avec autolayout.

14voto

Mark Kryzhanouski Points 4165

Encore une fois, en relisant le Notes de publication du SDK iOS 6.0 J'ai trouvé ça :

Notez que vous pouvez faire en sorte qu'une vue secondaire de la vue défilante semble flotter (et non défiler) au-dessus de l'autre contenu défilant en créant des contraintes entre la vue et une vue située en dehors du sous-arbre de la vue défilante, telle que la vue supérieure de la vue défilante.

Solution

Connectez votre vue secondaire à la vue extérieure. En d'autres termes, à la vue dans laquelle scrollview est intégrée.

Et en appliquant les contraintes de la manière suivante, j'ai réussi à le faire fonctionner :

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[scrollView]|" options:0 metrics: 0 views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[imageView(width)]" options:0 metrics:@{@"width":@(self.imageViewPointer.image.size.width)} views:viewsDictionary]];
[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[imageView(height)]" options:0 metrics:@{@"height":@(self.imageViewPointer.image.size.height)} views:viewsDictionary]];

0 votes

J'ai essayé de mettre en œuvre cette solution, mais en zoomant, j'obtiens un message "Impossible de satisfaire simultanément les contraintes". J'aimerais pouvoir déboguer davantage, mais malheureusement je ne comprends pas votre approche.

0 votes

Merci pour vos commentaires. J'ai mis à jour la réponse. En effet, une contrainte ('|') était redondante.

0 votes

Oui, ce n'est pas logique et difficile à comprendre mais ça marche. Il semble que ce soit une sorte de bug d'Apple. J'espère qu'ils changeront ce comportement de UIScrollView à l'avenir.

10voto

Brett Points 293

Le problème qui se pose est le changement d'emplacement de la vue de l'image pendant le processus de zoom. L'emplacement d'origine de la vue de l'image devient une valeur négative pendant le zoom. Je pense que c'est la raison pour laquelle le mouvement saccadé se produit. De plus, une fois le zoom terminé, l'imageView se trouve toujours au mauvais endroit, ce qui signifie que les défilements semblent décalés.

Si vous mettez en œuvre -(void) scrollViewDidZoom:(UIScrollView *)scrollView et enregistrez le cadre de l'UIImageView pendant ce temps, vous pouvez voir son origine changer.

J'ai fini par arranger les choses en mettant en place une stratégie du genre ce

Et en plus, changer le cadre de la ContentView pendant le zoom.

-(void) scrollViewDidZoom:(UIScrollView *)scrollView {
    CGRect cFrame = self.contentView.frame;
    cFrame.origin = CGPointZero;
    self.contentView.frame = cFrame;
}

0 votes

J'apprécie votre commentaire ! Malheureusement, je n'ai toujours pas réussi à faire fonctionner mon exemple de photo simple documenté ci-dessus sur la base de la stratégie que vous avez indiquée.

3 votes

FYI - le lien github est cassé.

4voto

Pascal Points 8222

Ces solutions fonctionnent toutes à peu près. Voici ce que j'ai fait avec cette configuration, sans avoir besoin de hacks ou de sous-classes :

[view]
  [scrollView]
    [container]
      [imageView]
      [imageView2]
  1. Dans l'IB, accrochez le haut, l'avant, le bas et l'arrière de l'appareil. scrollView à view .
  2. Accrocher le haut, l'avant, le bas et l'arrière de container à scrollView .
  3. Accrocher le centre-x et le centre-y de container au centre-x et au centre-y de scrollView et le marquer comme supprimer le temps de construction . Cela n'est nécessaire que pour faire taire les avertissements de l'IB.
  4. Mettre en œuvre viewForZoomingInScrollView: dans le contrôleur de vue, qui devrait être le délégué de scrollView, et à partir de cette méthode, renvoyer container .
  5. Lors du réglage de la imageView déterminez l'échelle de zoom minimale (pour l'instant, l'image sera affichée à sa taille native) et définissez-la :

    CGSize mySize = self.view.bounds.size;
    CGSize imgSize = _imageView.image.size;
    CGFloat scale = fminf(mySize.width / imgSize.width,
                          mySize.height / imgSize.height);
    _scrollView.minimumZoomScale = scale;
    _scrollView.zoomScale = scale;
    _scrollView.maximumZoomScale = 4 * scale;

Cela fonctionne pour moi, lors du réglage de l'image, la vue défilante s'agrandit pour montrer l'image entière et permet de zoomer jusqu'à 4x la taille initiale.

0 votes

Hm... J'aime votre approche. Mais dis-moi, pourquoi as-tu 2 imageViews à l'intérieur du conteneur ? De plus, ne devez-vous pas les relier à quelque chose ? Quelles sont les contraintes pour eux ?

0 votes

C'est juste un exemple, vous pouvez ajouter n'importe quoi à container (puisque c'est la vue qui est zoomée). Les contraintes sur ces vues d'image sont celles dont vous avez besoin pour que tous les côtés soient connectés.

0 votes

Oui, mais je n'arrive pas à le faire fonctionner sur iOS7 ou iOS8. Quelque chose est bizarre et l'image n'est pas centrée dans l'UIScrollView. De plus, lors des zooms avant et arrière, les choses se passent complètement mal... Est-ce que ça marche pour vous ?

1voto

Hizabr Points 38

Disons que vous avez dans le storyboard " UIImageView " à l'intérieur " UIScrollView " à l'intérieur " UIView ".

Lier toutes les contraintes en " UIScrollView "avec le contrôleur de vue + les deux contraintes dans UIView ( Horizontal Space - Scroll View - View & Horizontal Space - Scroll View - View ).

définir le contrôleur de vue AS délégué pour le " UIScrollView ".

Ensuite, mettez en œuvre ce code :

@interface VC () <UIScrollViewDelegate>
@property (weak, nonatomic) IBOutlet UIScrollView *scrollView;
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@property (strong, nonatomic) IBOutletCollection(NSLayoutConstraint) NSArray* constraints;
@end

@implementation FamilyTreeImageVC

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.scrollView removeConstraints:self.constraints];
}

- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return self.imageView;
}

-(void) scrollViewDidZoom:(UIScrollView *)scrollView {
    CGRect cFrame = self.imageView.frame;
    cFrame.origin = CGPointZero;
    self.imageView.frame = cFrame;
}

0 votes

Dans mon cas, je n'avais pas besoin de lier l'espace horizontal dans l'UIView, mais le reste l'a réparé !

0 votes

@Hizabr Pourquoi supprimez-vous les contraintes dans viewDidLoad ?

0voto

Tim Points 433

J'ai eu le même problème en essayant d'implémenter le zoom à partir d'un projet storyboardé en utilisant uniquement scrollView.

Je l'ai corrigé en ajoutant une reconnaissance séparée des gestes de pincement. Je l'ai simplement fait glisser de la boîte à outils vers ma scène. Je l'ai ensuite connecté à une action appelée "doPinch" qui implémente le zoom. Je l'ai connecté à une sortie appelée "pinchRecognizer" afin de pouvoir accéder à sa propriété d'échelle. Cela semble remplacer le zoom intégré de la scrollView et les sauts disparaissent. Peut-être qu'il ne fait pas la même erreur avec les origines, ou qu'il gère cela de manière plus gracieuse. C'est très peu de travail en plus de la mise en page dans IB.

Dès que vous introduisez la reconnaissance des gestes de pincement dans la scène, vous avez besoin des méthodes action et viewForZoomingInScrollView. Si vous oubliez l'une ou l'autre, le zoom ne fonctionne plus.

Le code dans mon contrôleur de vue est le suivant :

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.zoomableImage;
}

- (IBAction)doPinch:(id)sender

{
NSLog(@"In the pinch action now with scale: %f", self.pinchRecognizer.scale);
[scrollView setZoomScale:self.pinchRecognizer.scale animated:NO];
}

Cette implémentation très basique a un effet secondaire : lorsque vous revenez à une image zoomée et que vous zoomez encore, la valeur de l'échelle est de 1.0f, ce qui fait qu'elle revient à l'échelle originale.

Vous pouvez résoudre ce problème en introduisant une propriété "currentScale" pour suivre l'échelle et définir l'échelle de la reconnaissance gestuelle du pincement lorsque vous recommencez à zoomer. Vous devez utiliser la propriété "state" de la reconnaissance des gestes :

- (IBAction)doPinch:(id)sender
{
NSLog(@"In the pinch action now with scale: %f", self.pinchRecognizer.scale);
NSLog(@"Gesture recognizer state is: %d", self.pinchRecognizer.state);

switch (self.pinchRecognizer.state)
{
    case 1:
        NSLog(@"Zoom begins, with current scale set to: %f", self.currentScale);
        [self.pinchRecognizer setScale:self.currentScale];
        break;
    case 3:
        NSLog(@"Zoom ends, with pinch recognizer scale set to: %f", self.pinchRecognizer.scale);
        self.currentScale = self.pinchRecognizer.scale;
    default:
        break;
}

[scrollView setZoomScale:self.pinchRecognizer.scale animated:NO];
}

0 votes

A quoi est attachée exactement la pince ? Le ScrollView ? L'ImageView ? La vue globale ?

0 votes

Désolé pour le retard dans la réponse - je n'avais pas repéré la boîte de réception ! Le détecteur de gestes de pincement est ajouté à la scène dans le storyboard. Il semble se situer au niveau supérieur et détecte les pincements sur toute vue visible dans cette scène.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X