web-dev-qa-db-fra.com

UIScrollView avec UIImageView centré, comme pour l'application Photos

Je voudrais avoir la vue de défilement avec une vue de contenu d'image. L'image est en réalité une carte beaucoup plus grande que l'écran. La carte doit être initialement située au centre de la vue de défilement, comme pour les photos dans l'application Photos lorsque vous convertissez l'iPhone en orientation Paysage.

alt text

Je n'ai pas réussi à placer la carte au centre avec un zoom et un défilement corrects en même temps . Si l'image de la carte commence en haut de l'écran (en orientation portrait), le code ressemble à ceci:

- (void)loadView {
    mapView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"map.jpg"]];
    CGFloat mapHeight = MAP_HEIGHT * SCREEN_WIDTH / MAP_WIDTH;
    mapView.frame = CGRectMake(0, 0, SCREEN_WIDTH, mapHeight);
    scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT)];
    scrollView.delegate = self;
    scrollView.contentSize = mapView.frame.size;
    scrollView.maximumZoomScale = MAP_WIDTH / SCREEN_WIDTH;
    scrollView.minimumZoomScale = 1;
    [scrollView addSubview:mapView];
    self.view = scrollView;
}

Lorsque je déplace le cadre de l'image au centre, l'image ne grossit que du haut vers le bas. J'ai essayé de jouer avec la transformation mapView, avec un cadre changeant dynamiquement de l'imageView. Rien ne fonctionne pour moi jusqu'à présent.

37
Martin Ludvik

Ce code devrait fonctionner sur la plupart des versions d'iOS (et a été testé pour fonctionner à partir de la version 3.1).

Il est basé sur le code Apple WWDC mentionné dans la réponse de Jonah.

Ajoutez ce qui suit à votre sous-classe de UIScrollView et remplacez tileContainerView par la vue contenant votre image ou vos tuiles:

- (void)layoutSubviews {
    [super layoutSubviews];

    // center the image as it becomes smaller than the size of the screen
    CGSize boundsSize = self.bounds.size;
    CGRect frameToCenter = tileContainerView.frame;

    // center horizontally
    if (frameToCenter.size.width < boundsSize.width)
        frameToCenter.Origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
    else
        frameToCenter.Origin.x = 0;

    // center vertically
    if (frameToCenter.size.height < boundsSize.height)
        frameToCenter.Origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
    else
        frameToCenter.Origin.y = 0;

    tileContainerView.frame = frameToCenter;
}
40
JosephH

Voici ce que je considérerais, la solution car elle se comporte exactement comme l'application photo d'Apple. J'avais utilisé des solutions qui utilisaient:

-(void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale

récemment, mais je n’aimais pas cette solution car après le zoom, elle rebondissait, puis «sautait» rapidement au centre, ce qui était très peu sexy. Il s'avère que si vous faites à peu près exactement la même logique mais dans cette fonction de délégué:

-(void)scrollViewDidZoom:(UIScrollView *)pScrollView

il commence à être décentré et lorsque vous effectuez un zoom arrière reste centré:

-(void)scrollViewDidZoom:(UIScrollView *)pScrollView {
CGRect innerFrame = imageView.frame;
CGRect scrollerBounds = pScrollView.bounds;

if ( ( innerFrame.size.width < scrollerBounds.size.width ) || ( innerFrame.size.height < scrollerBounds.size.height ) )
{
    CGFloat tempx = imageView.center.x - ( scrollerBounds.size.width / 2 );
    CGFloat tempy = imageView.center.y - ( scrollerBounds.size.height / 2 );
    CGPoint myScrollViewOffset = CGPointMake( tempx, tempy);

    pScrollView.contentOffset = myScrollViewOffset;

}

UIEdgeInsets anEdgeInset = { 0, 0, 0, 0};
if ( scrollerBounds.size.width > innerFrame.size.width )
{
    anEdgeInset.left = (scrollerBounds.size.width - innerFrame.size.width) / 2;
    anEdgeInset.right = -anEdgeInset.left;  // I don't know why this needs to be negative, but that's what works
}
if ( scrollerBounds.size.height > innerFrame.size.height )
{
    anEdgeInset.top = (scrollerBounds.size.height - innerFrame.size.height) / 2;
    anEdgeInset.bottom = -anEdgeInset.top;  // I don't know why this needs to be negative, but that's what works
}
pScrollView.contentInset = anEdgeInset;
}

Où 'imageView' est la UIImageView que vous utilisez.

23
Shizam

Apple a publié les vidéos de session WWDC 2010 à tous les membres du programme pour développeurs iphone. L'un des sujets abordés est la création de l'application de photos !!! Ils construisent pas à pas une application très similaire et ont mis tout le code à disposition gratuitement.

Il n'utilise pas non plus d'api privé. Je ne peux mettre aucun code ici à cause de l'accord de non-divulgation, mais voici un lien vers l'exemple de téléchargement de code. Vous aurez probablement besoin de vous connecter pour avoir accès.

http://connect.Apple.com/cgi-bin/WebObjects/MemberSite.woa/wa/getSoftware?code=y&source=x&bundleID=20645

Et, voici un lien vers la page iTunes WWDC:

http://insideapple.Apple.com/redir/cbx-cgi.do?v=2&la=en&lc=&a=kGSol9sgPHP%2BtlWtLp%2BEP%2FnxnZarjWJglPBZRHd3oDbACudP51JNGS8KlsFgxZto9X%2BTsnqSbeUSWX0doe%2Fzv%2FN5XV55%2FomsyfRgFBysOnIVggO%2Fn2p%2BiweDK%2F% 2FmsIXj

7
Jonah

Je suppose que vous devez définir la variable UIScrollView 's contentOffset.

2
Rog

Cela a fonctionné pour moi:

- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
CGFloat tempx = view.center.x-160;
CGFloat tempy = view.center.y-160;
myScrollViewOffset = CGPointMake(tempx,tempy);

}

où 160 est la moitié de la largeur/hauteur de votre UIScrollView.

Puis plus tard, je règle le contentoffset à celui capturé ici.

1
Puppet

J'aimerais que ce soit aussi simple. J'ai fait des recherches sur le net et constaté qu'il ne s'agissait pas que de mon problème, mais que de nombreuses personnes étaient aux prises avec le même problème, pas seulement sur iPhone, mais également sur le bureau d'Apple, Cocoa ... .. Voir les liens suivants:

http://www.iphonedevsdk.com/forum/iphone-sdk-development/5740-uiimageview-uiscrollview.html
La solution décrite est basée sur la propriété UIViewContentModeScaleAspectFit de l’image mais, malheureusement, elle ne fonctionne pas très bien. L’image est centrée et s’agrandit correctement, mais la zone de rebond semble beaucoup plus grande que l’image.

Ce gars n'a pas eu la réponse non plus:
http://discussions.Apple.com/thread.jspa?messageID=8322675

Et enfin, le même problème sur le bureau Apple de Cocoa:
http://www.cocoadev.com/index.pl?CenteringInsideNSScrollView
Je suppose que la solution fonctionne, mais elle est basée sur le NSClipView, qui n'est pas sur l'iPhone ...

Quelqu'un a une solution qui fonctionne sur iPhone? 

1
Martin Ludvik

Remarque: cette méthode fonctionne en quelque sorte. Si l'image est plus petite que l'imageView, elle défilera partiellement à l'écran. Pas très grave, mais pas aussi gentil que l'application de photos.

Premièrement, il est important de comprendre que nous avons affaire à 2 vues, la vue image avec l’image qu’elle contient et la vue défilement avec la vue image dedans .

 [myImageView setFrame:self.view.frame];

Ensuite, centrez votre image dans la vue image:

 myImageView.contentMode = UIViewContentModeCenter;

Voici tout mon code:

- (void)viewDidLoad {
AppDelegate *appDelegate = (pAppDelegate *)[[UIApplication sharedApplication] delegate];
 [super viewDidLoad];
 NSString *Path = [[NSBundle mainBundle] bundlePath];
 NSString *ImagePath = [Path stringByAppendingPathComponent:(@"data: %@", appDelegate.MainImageName)];
 UIImage *tempImg = [[UIImage alloc] initWithContentsOfFile:ImagePath];
 [imgView setImage:tempImg];

 myScrollView = [[UIScrollView alloc] initWithFrame:[[self view] bounds]];
 [myScrollView addSubview:myImageView];

//Set ScrollView Appearance
 [myScrollView setBackgroundColor:[UIColor blackColor]];
 myScrollView.indicatorStyle = UIScrollViewIndicatorStyleWhite;

//Set Scrolling Prefs
 myScrollView.bounces = YES;
 myScrollView.delegate = self;
 myScrollView.clipsToBounds = YES; // default is NO, we want to restrict drawing within our scrollview
 [myScrollView setCanCancelContentTouches:NO];
 [myScrollView setScrollEnabled:YES];


//Set Zooming Prefs
 myScrollView.maximumZoomScale = 3.0;
 myScrollView.minimumZoomScale = CGImageGetWidth(tempImg.CGImage)/320;
 myScrollView.zoomScale = 1.01; //Added the .01 to enable scrolling immediately upon view load.
 myScrollView.bouncesZoom = YES;


 [myImageView setFrame:self.view.frame];//rect];// .frame.size.height = imageHeight;
 myImageView.contentMode = UIViewContentModeCenter;
 self.view = myScrollView;
 [tempImg release];
 }
0
Jonah

D'accord, je pense avoir trouvé une très bonne solution à ce problème. L'astuce consiste à réajuster constamment le cadre de l'imageView. Je trouve que cela fonctionne beaucoup mieux que d'ajuster constamment les contentInsets ou contentOffSets. J'ai dû ajouter un peu de code supplémentaire pour prendre en charge les images portrait et paysage.

Voici le code:

- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {

CGSize screenSize = [[self view] bounds].size;

if (myScrollView.zoomScale <= initialZoom +0.01) //This resolves a problem with the code not working correctly when zooming all the way out.
{
    imageView.frame = [[self view] bounds];
    [myScrollView setZoomScale:myScrollView.zoomScale +0.01];
}

if (myScrollView.zoomScale > initialZoom)
{
    if (CGImageGetWidth(temporaryImage.CGImage) > CGImageGetHeight(temporaryImage.CGImage)) //If the image is wider than tall, do the following...
    {
            if (screenSize.height >= CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the height of the screen is greater than the zoomed height of the image do the following...
            {
                    imageView.frame = CGRectMake(0, 0, 320*(myScrollView.zoomScale), 368);
            }
            if (screenSize.height < CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the height of the screen is less than the zoomed height of the image do the following...
            {
                    imageView.frame = CGRectMake(0, 0, 320*(myScrollView.zoomScale), CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale]);
            }
    }
    if (CGImageGetWidth(temporaryImage.CGImage) < CGImageGetHeight(temporaryImage.CGImage)) //If the image is taller than wide, do the following...
    {
            CGFloat portraitHeight;
            if (CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale] < 368)
            { portraitHeight = 368;}
            else {portraitHeight = CGImageGetHeight(temporaryImage.CGImage) * [myScrollView zoomScale];}

            if (screenSize.width >= CGImageGetWidth(temporaryImage.CGImage) * [myScrollView zoomScale]) //If the width of the screen is greater than the zoomed width of the image do the following...
            {
                    imageView.frame = CGRectMake(0, 0, 320, portraitHeight);
            }
            if (screenSize.width < CGImageGetWidth (temporaryImage.CGImage) * [myScrollView zoomScale]) //If the width of the screen is less than the zoomed width of the image do the following...
            {
                    imageView.frame = CGRectMake(0, 0, CGImageGetWidth(temporaryImage.CGImage) * [myScrollView zoomScale], portraitHeight);
            }
    }
    [myScrollView setZoomScale:myScrollView.zoomScale -0.01];
}
0
Jonah

Dans Monotouch, cela a fonctionné pour moi.

this._scroll.ScrollRectToVisible(new RectangleF(_scroll.ContentSize.Width/2, _scroll.ContentSize.Height/2,1,1),false);
0
ozan.aksu

Voici un moyen élégant de centrer le contenu de UISCrollView.

Ajoutez un observateur à contentSize de votre UIScrollView. Cette méthode sera donc appelée à chaque changement de contenu ...

[myScrollView addObserver:delegate 
               forKeyPath:@"contentSize"
                  options:(NSKeyValueObservingOptionNew) 
                  context:NULL];

Maintenant sur votre méthode d'observateur:

- (void)observeValueForKeyPath:(NSString *)keyPath   ofObject:(id)object   change:(NSDictionary *)change   context:(void *)context { 

    // Correct Object Class.
    UIScrollView *pointer = object;

    // Calculate Center.
    CGFloat topCorrect = ([pointer bounds].size.height - [pointer viewWithTag:100].bounds.size.height * [pointer zoomScale])  / 2.0 ;
            topCorrect = ( topCorrect < 0.0 ? 0.0 : topCorrect );

    topCorrect = topCorrect - (  pointer.frame.Origin.y - imageGallery.frame.Origin.y );

    // Apply Correct Center.
    pointer.center = CGPointMake(pointer.center.x,
                                 pointer.center.y + topCorrect ); }
  • Vous devriez changer le [pointer viewWithTag:100]. Remplacez par votre Content view UIView.

    • Changez également imageGallery en pointant sur la taille de votre fenêtre.

Cela corrigera le centre du contenu à chaque changement de taille. 

REMARQUE: La seule manière dont ce contenu ne fonctionne pas très bien est avec la fonctionnalité de zoom standard de la UIScrollView

Voici une solution alternative, similaire à celle de @ JosephH, mais celle-ci prend en compte les dimensions réelles de l'image. Ainsi, lorsque l'utilisateur effectue un panoramique/zoom, vous n'avez jamais plus d'espace à l'écran que nécessaire. Ceci est un problème courant, par exemple lors de l'affichage d'une image de paysage sur un écran portrait. Il y aura un espace au-dessus et au-dessous de l'image lorsque toute l'image est affichée (ajustement du format). Ensuite, lors du zoom avant, les autres solutions considèrent que les espaces blancs font partie de l'image, car ils se trouvent dans la vue image. Ils vous laisseront faire un panoramique de la majorité de l'image hors de l'écran, ne laissant que l'espace blanc visible. Cela semble mauvais pour l'utilisateur.

Avec cette classe, vous devez lui transmettre l'imageView avec lequel elle travaille. J'ai été tenté de le faire détecter automatiquement, mais c'est plus rapide et vous voulez toute la vitesse que vous pouvez obtenir dans la méthode layoutSubviews.

Remarque: En l'état, cela nécessite que la mise en forme automatique ne soit pas activée pour scrollView.

//
//  CentringScrollView.Swift
//  Cerebral Gardens
//
//  Created by Dave Wood
//  Copyright © 2016 Cerebral Gardens Inc. All rights reserved.
//

import UIKit

class CentringScrollView: UIScrollView {

    var imageView: UIImageView?

    override func layoutSubviews() {
        super.layoutSubviews()

        guard let superview = superview else { return }
        guard let imageView = imageView else { return }
        guard let image = imageView.image else { return }

        var frameToCentre = imageView.frame

        let imageWidth = image.size.width
        let imageHeight = image.size.height

        let widthRatio = superview.bounds.size.width / imageWidth
        let heightRatio = superview.bounds.size.height / imageHeight

        let minRatio = min(widthRatio, heightRatio, 1.0)

        let effectiveImageWidth = minRatio * imageWidth * zoomScale
        let effectiveImageHeight = minRatio * imageHeight * zoomScale

        contentSize = CGSize(width: max(effectiveImageWidth, bounds.size.width), height: max(effectiveImageHeight, bounds.size.height))

        frameToCentre.Origin.x = (contentSize.width - frameToCentre.size.width) / 2
        frameToCentre.Origin.y = (contentSize.height - frameToCentre.size.height) / 2

        imageView.frame = frameToCentre
    }
}
0
Dave Wood