Centrer le contenu de UIScrollView lorsqu'il est plus petit

140

J'ai un UIImageViewintérieur UIScrollViewque j'utilise pour le zoom et le défilement. Si l'image / le contenu de la vue de défilement est plus grand que la vue de défilement, tout fonctionne bien. Cependant, lorsque l'image devient plus petite que la vue de défilement, elle reste dans le coin supérieur gauche de la vue de défilement. Je souhaite rester centré, comme l'application Photos.

Des idées ou des exemples pour garder le contenu du UIScrollViewcentré quand il est plus petit?

Je travaille avec l'iPhone 3.0.

Le code suivant fonctionne presque. L'image revient dans le coin supérieur gauche si je la pince après avoir atteint le niveau de zoom minimum.

- (void)loadView {
    [super loadView];

    // set up main scroll view
    imageScrollView = [[UIScrollView alloc] initWithFrame:[[self view] bounds]];
    [imageScrollView setBackgroundColor:[UIColor blackColor]];
    [imageScrollView setDelegate:self];
    [imageScrollView setBouncesZoom:YES];
    [[self view] addSubview:imageScrollView];

    UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"WeCanDoIt.png"]];
    [imageView setTag:ZOOM_VIEW_TAG];
    [imageScrollView setContentSize:[imageView frame].size];
    [imageScrollView addSubview:imageView];

    CGSize imageSize = imageView.image.size;
    [imageView release];

    CGSize maxSize = imageScrollView.frame.size;
    CGFloat widthRatio = maxSize.width / imageSize.width;
    CGFloat heightRatio = maxSize.height / imageSize.height;
    CGFloat initialZoom = (widthRatio > heightRatio) ? heightRatio : widthRatio;

    [imageScrollView setMinimumZoomScale:initialZoom];
    [imageScrollView setZoomScale:1];

    float topInset = (maxSize.height - imageSize.height) / 2.0;
    float sideInset = (maxSize.width - imageSize.width) / 2.0;
    if (topInset < 0.0) topInset = 0.0;
    if (sideInset < 0.0) sideInset = 0.0;
    [imageScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
}

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return [imageScrollView viewWithTag:ZOOM_VIEW_TAG];
}

/************************************** NOTE **************************************/
/* The following delegate method works around a known bug in zoomToRect:animated: */
/* In the next release after 3.0 this workaround will no longer be necessary      */
/**********************************************************************************/
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
    [scrollView setZoomScale:scale+0.01 animated:NO];
    [scrollView setZoomScale:scale animated:NO];
    // END Bug workaround

    CGSize maxSize = imageScrollView.frame.size;
    CGSize viewSize = view.frame.size;
    float topInset = (maxSize.height - viewSize.height) / 2.0;
    float sideInset = (maxSize.width - viewSize.width) / 2.0;
    if (topInset < 0.0) topInset = 0.0;
    if (sideInset < 0.0) sideInset = 0.0;
    [imageScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
}
hpique
la source
Avez-vous déjà résolu ce problème complètement? Je lutte avec le même problème.
Jonah
1
Attention: utilisez la valeur initialZoom pour calculer l'encart s'il n'en est PAS un (pas de zoom). Par exemple, utilisez ces lignes: float topInset = (maxSize.height - imageSize.height * initialZoom) / 2.0; float sideInset = (maxSize.width - imageSize.width * initialZoom) / 2.0; et définissez enfin la valeur initiale du zoom [imageScrollView setZoomScale: initialZoom];
Felix

Réponses:

228

J'ai une solution très simple! Tout ce dont vous avez besoin est de mettre à jour le centre de votre sous-vue (imageview) tout en zoomant dans ScrollViewDelegate. Si l'image agrandie est plus petite que scrollview, ajustez subview.center sinon le centre est (0,0).

- (void)scrollViewDidZoom:(UIScrollView *)scrollView 
{
    UIView *subView = [scrollView.subviews objectAtIndex:0];

    CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
    CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);

    subView.center = CGPointMake(scrollView.contentSize.width * 0.5 + offsetX, 
                                 scrollView.contentSize.height * 0.5 + offsetY);
}
Erdemus
la source
5
Pour moi, cette solution est plus saccadée que d'utiliser NYOBetterZoom de Liam. Cela dépend peut-être de la taille de l'image, etc. La morale; utilisez la solution qui correspond le mieux à vos besoins
wuf810
2
Stackoverflow GOLD STAR pour cela. J'avais du mal avec cela, mais la solution est si simple.
n13
2
pour simplifier un peu:CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
Marek R
6
Cet ajustement m'a aidé lorsque la vue de défilement avait des encarts: CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentInset.left - scrollView.contentInset.right - scrollView.contentSize.width) * 0.5, 0.0); CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentInset.top - scrollView.contentInset.bottom - scrollView.contentSize.height) * 0.5, 0.0);
Kieran Harper
3
J'ai remarqué que cela, comme beaucoup d'autres techniques de centrage, semble souffrir de problèmes lors de l'utilisation zoomToRect:. L'utilisation de l' contentInsetapproche fonctionne mieux si vous avez besoin de cette fonctionnalité. Voir petersteinberger.com/blog/2013/how-to-center-uiscrollview pour plus de détails.
Matej Bukovinski
52

La réponse de @ EvelynCordner était celle qui fonctionnait le mieux dans mon application. Beaucoup moins de code que les autres options aussi.

Voici la version Swift si quelqu'un en a besoin:

func scrollViewDidZoom(_ scrollView: UIScrollView) {
    let offsetX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
    let offsetY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
    scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0, 0)
}
William T.
la source
4
Celui-ci fonctionne très bien! La vue s'est même animée correctement après la réduction.
Carter Medlin
4
J'ai mis ce code dans un func et l' ai également appelé dans viewDidLayoutSubviews pour m'assurer qu'il était correctement configuré au départ.
Carter Medlin
Un bon appel @CarterMedlin m'a tellement aidé avec mon chargement initial Swift 3.
AJ Hernandez
Cela semble fonctionner, mais je ne comprends pas pourquoi, car il semble toujours calculer les mêmes inserts: les limites de la vue de défilement sont fixes, tout comme la taille du contenu.
Vaddadi Kartick
contentSize changes when zooming scrollView size is only fixed
Michał Ziobro
25

D'accord, je me suis battu contre ça ces deux derniers jours de temps en temps et étant finalement arrivé à une solution assez fiable (jusqu'à présent ...), j'ai pensé que je devrais la partager et éviter aux autres de souffrir. :) Si vous trouvez un problème avec cette solution, criez!

J'ai essentiellement parcouru ce que tout le monde a: en recherchant StackOverflow, les forums de développeurs Apple, j'ai regardé le code pour three20, ScrollingMadness, ScrollTestSuite, etc. du ViewController, etc. mais rien n'a fonctionné à merveille (comme tout le monde l'a découvert aussi).

Après avoir dormi dessus, j'ai essayé quelques angles alternatifs:

  1. Sous-classer l'UIImageView pour qu'il modifie sa propre taille de manière dynamique - cela ne fonctionnait pas du tout.
  2. Sous-classer le UIScrollView pour qu'il modifie son propre contentOffset dynamiquement - c'est celui qui semble être un gagnant pour moi.

Avec cette méthode UIScrollView de sous-classification, je remplace le mutateur contentOffset afin qu'il ne définisse pas {0,0} lorsque l'image est redimensionnée plus petite que la fenêtre - au lieu de cela, il définit le décalage de sorte que l'image reste centrée dans la fenêtre. Jusqu'à présent, cela semble toujours fonctionner. Je l'ai vérifié avec des images larges, hautes, minuscules et grandes et je n'ai pas le problème "fonctionne mais le pincement au zoom minimum le casse".

J'ai téléchargé un exemple de projet sur github qui utilise cette solution, vous pouvez le trouver ici: http://github.com/nyoron/NYOBetterZoom

Liam Jones
la source
2
Pour ceux qui sont intéressés - j'ai mis à jour le projet lié ci-dessus afin qu'il soit un peu moins dépendant du ViewController qui fait `` la bonne chose '', le UIScrollView personnalisé lui-même s'occupe de plus de détails.
Liam Jones
2
Liam, tu es rock. Je dis cela en tant qu'auteur de ScrollingMadness. BTW sur iPad dans 3.2+ réglage contentInset dans scrollViewDidZoom (une nouvelle méthode déléguée 3.2+) Just Works.
Andrey Tarantsov
Morceau de code très soigné. Je me gratte la tête avec ça depuis un moment. Ajout du projet à handyiphonecode.com
samvermette
C'est bien. Je vous remercie. Cela ne compensait pas le fait que mon UIStatusBar soit masqué, alors j'ai changé la ligne anOffset.y = -(scrollViewSize.height - zoomViewSize.height) / 2.0enanOffset.y = (-(scrollViewSize.height - zoomViewSize.height) / 2.0) + 10;
Gordon Fontenot
La solution donnée bi Erdemus est beaucoup plus simple à mon avis.
gyozo kudor
23

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 WWDC d'Apple pour le photoscoller.

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

- (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;
}
JosephH
la source
3
cela devrait être la réponse acceptée, car elle est plus simple. Merci. Tu m'as sauvé la vie!!!!!! : D
Canard
Après avoir supprimé tous les appels d'API iOS 3.2+, la logique de centrage ne semble fonctionner que sur les appareils avec iOS 3.2+ et non 3.1.3 (nerveux, scintillant, obtient un décalage aléatoire). J'ai comparé les sorties de l'origine et de la taille du cadre entre les versions 3.1.3 et 3.2+ et même si elles correspondent, pour une raison quelconque, la vue enfant est toujours mal positionnée. Très étrange. Seule la réponse de Liam Jone a fonctionné pour moi.
David H
1
Les solutions @Erdemus et @JosephH fonctionnent, mais l' UIScrollViewapproche de sous - classe semble préférable: elle est invoquée lorsque la vue est affichée pour la première fois + en continu pendant que le zoom est en cours (alors qu'elle scrollViewDidZoomn'est invoquée qu'une seule fois par scroll et après le fait)
SwiftArchitect
22

Pour une solution mieux adaptée aux vues de défilement qui utilisent la disposition automatique, utilisez des encarts de contenu de la vue de défilement plutôt que de mettre à jour les cadres des sous-vues de votre vue de défilement.

- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
    CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
    CGFloat offsetY = MAX((scrollView.bounds.size.height - scrollView.contentSize.height) * 0.5, 0.0);

    self.scrollView.contentInset = UIEdgeInsetsMake(offsetY, offsetX, 0.f, 0.f);
}
Evelyn Cordner
la source
1
Cela a fonctionné pour moi, mais si l'image est plus petite au départ, ce code (lorsqu'il est appelé directement) n'a pas mis à jour le scrollview. Ce que je devais faire, c'était d'abord mettre le UIViewqui se trouve à l'intérieur du UIScrollviewau même centre ( view.center = scrollview.center;), puis dans l' scrollViewDidZoomensemble le view.frame.origin xet yà 0nouveau.
morksinaanab
Fonctionne bien pour moi aussi, mais j'ai fait un dispatch_asyncdans la file d'attente principale dans viewWillAppearlaquelle j'appelle scrollViewDidZoom:ma vue de défilement principale. Cela fait apparaître la vue avec des images centrées.
Pascal
Appelez ce code viewDidLayoutSubviewspour vous assurer qu'il est correctement configuré lorsque la vue s'affiche pour la première fois.
Carter Medlin
21

Actuellement, je sous UIScrollView- classe et remplace setContentOffset:pour ajuster le décalage en fonction de contentSize. Il fonctionne à la fois avec le zoom par pincement et programmatique.

@implementation HPCenteringScrollView

- (void)setContentOffset:(CGPoint)contentOffset
{
    const CGSize contentSize = self.contentSize;
    const CGSize scrollViewSize = self.bounds.size;

    if (contentSize.width < scrollViewSize.width)
    {
        contentOffset.x = -(scrollViewSize.width - contentSize.width) / 2.0;
    }

    if (contentSize.height < scrollViewSize.height)
    {
        contentOffset.y = -(scrollViewSize.height - contentSize.height) / 2.0;
    }

    [super setContentOffset:contentOffset];
}

@end

En plus d'être court et doux, ce code produit un zoom beaucoup plus fluide que la solution @Erdemus. Vous pouvez le voir en action dans la démo RMGallery .

hpique
la source
6
Vous n'avez pas besoin de sous-classe UIScrollView pour implémenter cette méthode. Apple vous permet de voir les événements de défilement et de zoom dans les méthodes de délégué. Plus précisément: - (void) scrollViewDidZoom: (UIScrollView *) scrollView;
Rog
1
Meilleure solution que j'ai encore vue (fonctionne même avec des contraintes).
Frizlab
12

J'ai passé une journée à me battre avec ce problème et j'ai fini par implémenter le scrollViewDidEndZooming: withView: atScale: comme suit:

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
    CGFloat screenWidth = [[UIScreen mainScreen] bounds].size.width;
    CGFloat screenHeight = [[UIScreen mainScreen] bounds].size.height;
    CGFloat viewWidth = view.frame.size.width;
    CGFloat viewHeight = view.frame.size.height;

    CGFloat x = 0;
    CGFloat y = 0;

    if(viewWidth < screenWidth) {
        x = screenWidth / 2;
    }
    if(viewHeight < screenHeight) {
        y = screenHeight / 2 ;
    }

    self.scrollView.contentInset = UIEdgeInsetsMake(y, x, y, x);
}

Cela garantit que lorsque l'image est plus petite que l'écran, il y a encore suffisamment d'espace autour d'elle pour que vous puissiez la positionner à l'endroit exact que vous voulez.

(en supposant que votre UIScrollView contient un UIImageView pour contenir l'image)

Essentiellement, cela vérifie si la largeur / hauteur de votre image est plus petite que la largeur / hauteur de l'écran, et si c'est le cas, créez un encart de la moitié de la largeur / hauteur de l'écran (vous pourriez probablement l'agrandir si vous voulez que l'image sortir des limites de l’écran).

Notez que puisqu'il s'agit d'une méthode UIScrollViewDelegate , n'oubliez pas de l'ajouter à la déclaration de votre contrôleur de vue, afin d'éviter d'avoir un problème de construction.

Vicaire
la source
7

Apple a publié les vidéos de la session 2010 de la WWDC à tous les membres du programme de développement iPhone. L'un des sujets abordés est la façon dont ils ont créé l'application photos !!! Ils créent une application très similaire étape par étape et ont rendu tout le code disponible gratuitement.

Il n'utilise pas non plus l'API privée. Je ne peux mettre aucun code ici en raison de l'accord de non-divulgation, mais voici un lien vers l'exemple de téléchargement de code. Vous devrez probablement vous connecter pour y accéder.

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

Jonas
la source
1
L'exemple en question est MyImagePicker et il présente, de manière hilarante, ce même problème.
Colin Barrett
1
J'aurais dû être plus clair. L'exemple en question est en fait "PhotoScroller" et non "MyImagePicker". Vous avez raison, "MyImagePicker" ne fonctionne pas correctement. Mais, "PhotoScroller" le fait. Essayez-le.
Jonah
Vous souvenez-vous peut-être du titre de la vidéo de la WWDC où ils parlent du Photoscroller?
Grzegorz Adam Hankiewicz
1
Cela s'appelle «Conception d'applications avec des vues de défilement».
Jonah
2

Ok, cette solution fonctionne pour moi. J'ai une sous-classe de UIScrollViewavec une référence à celle UIImageViewqu'il affiche. Chaque fois que les UIScrollViewzooms, la propriété contentSize est ajustée. C'est dans le setter que je mets à l'échelle de UIImageViewmanière appropriée et ajuste également sa position centrale.

-(void) setContentSize:(CGSize) size{
CGSize lSelfSize = self.frame.size;
CGPoint mid;
if(self.zoomScale >= self.minimumZoomScale){
    CGSize lImageSize = cachedImageView.initialSize;
    float newHeight = lImageSize.height * self.zoomScale;

    if (newHeight < lSelfSize.height ) {
        newHeight = lSelfSize.height;
    }
    size.height = newHeight;

    float newWidth = lImageSize.width * self.zoomScale;
    if (newWidth < lSelfSize.width ) {
        newWidth = lSelfSize.width;
    }
    size.width = newWidth;
    mid = CGPointMake(size.width/2, size.height/2);

}
else {
    mid = CGPointMake(lSelfSize.width/2, lSelfSize.height/2);
}

cachedImageView.center = mid;
[super  setContentSize:size];
[self printLocations];
NSLog(@"zoom %f setting size %f x %f",self.zoomScale,size.width,size.height);
}

Evertime je place l'image sur le UIScrollViewje la redimensionne. Le UIScrollViewdans la vue de défilement est également une classe personnalisée que j'ai créée.

-(void) resetSize{
    if (!scrollView){//scroll view is view containing imageview
        return;
    }

    CGSize lSize = scrollView.frame.size;

    CGSize lSelfSize = self.image.size; 
    float lWidth = lSize.width/lSelfSize.width;
    float lHeight = lSize.height/lSelfSize.height;

    // choose minimum scale so image width fits screen
    float factor  = (lWidth<lHeight)?lWidth:lHeight;

    initialSize.height = lSelfSize.height  * factor;
    initialSize.width = lSelfSize.width  * factor;

    [scrollView setContentSize:lSize];
    [scrollView setContentOffset:CGPointZero];
    scrollView.userInteractionEnabled = YES;
}

Avec ces deux méthodes, je peux avoir une vue qui se comporte comme l'application de photos.

AHA
la source
Cela semble faire l'affaire, et c'est une solution assez simple. Certaines transitions de zoom ne sont pas parfaites, mais je pense que cela peut être corrigé. J'expérimenterai encore plus.
hpique
La solution était claire avant la mise à jour. Maintenant, c'est un peu déroutant. Pouvez-vous clarifier?
hpique
2

La façon dont j'ai fait cela est d'ajouter une vue supplémentaire dans la hiérarchie:

UIScrollView -> UIView -> UIImageView

Donnez-vous UIViewle même rapport hauteur / largeur que le vôtre UIScrollViewet centrez-le UIImageViewsur cela.

hatfinch
la source
Merci, hatfinch. J'essaierai cela aussi. Pouvez-vous publier un exemple de code ou modifier mon exemple de code pour montrer comment vous construisez la hiérarchie des vues?
hpique
Ce genre de travaux. À l'exception du fait que, comme UIView est de la taille de UIScrollView, si l'image est plus petite (c'est-à-dire paysage au lieu de portrait), vous pouvez faire défiler une partie de l'image hors de l'écran. L'application photos ne le permet pas et semble beaucoup plus jolie.
Jonah
2

Je sais que certaines réponses ci-dessus sont exactes, mais je veux juste donner ma réponse avec quelques explications, les commentaires vous feront comprendre pourquoi nous aimons ça.

Lorsque je charge le scrollView pour la première fois, j'écris le code suivant pour le centrer, veuillez noter que nous définissons d' contentOffsetabord, puiscontentInset

    scrollView.maximumZoomScale = 8
    scrollView.minimumZoomScale = 1

    // set vContent frame
    vContent.frame = CGRect(x: 0,
                            y: 0  ,
                            width: vContentWidth,
                            height: vContentWidth)
    // set scrollView.contentSize
    scrollView.contentSize = vContent.frame.size

    //on the X direction, if contentSize.width > scrollView.bounds.with, move scrollView from 0 to offsetX to make it center(using `scrollView.contentOffset`)
    // if not, don't need to set offset, but we need to set contentInset to make it center.(using `scrollView.contentInset`)
    // so does the Y direction.
    let offsetX = max((scrollView.contentSize.width - scrollView.bounds.width) * 0.5, 0)
    let offsetY = max((scrollView.contentSize.height - scrollView.bounds.height) * 0.5, 0)
    scrollView.contentOffset = CGPoint(x: offsetX, y: offsetY)

    let topX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
    let topY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
    scrollView.contentInset = UIEdgeInsets(top: topY, left: topX, bottom: 0, right: 0)

Ensuite, lorsque je pince vContent, j'écris le code suivant pour le centrer.

func scrollViewDidZoom(_ scrollView: UIScrollView) {
    //we just need to ensure that the content is in the center when the contentSize is less than scrollView.size.
    let topX = max((scrollView.bounds.width - scrollView.contentSize.width) * 0.5, 0)
    let topY = max((scrollView.bounds.height - scrollView.contentSize.height) * 0.5, 0)
    scrollView.contentInset = UIEdgeInsets(top: topY, left: topX, bottom: 0, right: 0)
}
Changwei
la source
2

S'il contentInsetn'est pas nécessaire pour autre chose, il peut être utilisé pour centrer le contenu de scrollview.

class ContentCenteringScrollView: UIScrollView {

    override var bounds: CGRect {
        didSet { updateContentInset() }
    }

    override var contentSize: CGSize {
        didSet { updateContentInset() }
    }

    private func updateContentInset() {
        var top = CGFloat(0)
        var left = CGFloat(0)
        if contentSize.width < bounds.width {
            left = (bounds.width - contentSize.width) / 2
        }
        if contentSize.height < bounds.height {
            top = (bounds.height - contentSize.height) / 2
        }
        contentInset = UIEdgeInsets(top: top, left: left, bottom: top, right: left)
    }
}

Avantage si cette approche est que vous pouvez toujours utiliser contentLayoutGuidepour placer du contenu dans scrollview

scrollView.addSubview(imageView)
imageView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    imageView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
    imageView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
    imageView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
    imageView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor)
])

ou faites simplement glisser et déposez le contenu dans l'interface Builder de Xcode.

Wojciech Nagrodzki
la source
1

Vous pouvez observer la contentSizepropriété de UIScrollView (à l'aide de l'observation de valeur-clé ou similaire) et ajuster automatiquement contentInsetchaque fois que les contentSizemodifications sont inférieures à la taille de la vue de défilement.

Tim
la source
J'essaierai. Est-ce possible de faire avec les méthodes UIScrollViewDelegate au lieu d'observer contentSize?
hpique
Probablement; vous utiliseriez - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale(décrit à developer.apple.com/iphone/library/documentation/UIKit/… ), mais je préférerais observer contentSizeparce que sinon vous finirez par abandonner la nouvelle échelle de zoom et la trouver de toute façon de la vue. (Sauf si vous pouvez réaliser des calculs géniaux basés sur la taille relative de vos vues.)
Tim
Merci, Tim. scrollViewDidEndZooming est ce que j'utilise. Voir le code en question (je l'ai ajouté après votre première réponse). Cela fonctionne presque. Le seul problème qui reste si que si je pince l'image après que minimumZoomScale a été atteint, elle retourne dans le coin supérieur gauche.
hpique
Voulez-vous dire que cela fonctionne (centre l'image) pour le pincement à partir de toute échelle autre que l'échelle de zoom minimale, et ne casse que si vous essayez de la pincer à nouveau une fois que vous êtes à l'échelle minimale? Ou cela ne fonctionne-t-il pas du tout pour le pincement à l'échelle minimale?
Tim
Le premier. Il centre l'image pour la pincer à partir de n'importe quelle échelle autre que l'échelle de zoom minimale, et se brise si vous essayez de la pincer à nouveau une fois que vous êtes à l'échelle minimale. De plus, lorsqu'il atteint l'échelle de zoom minimale la première fois, le contenu est brièvement animé comme venant du haut, ce qui provoque un bref effet étrange. Cela se produit au moins sur le simulateur 3.0.
hpique
1

Voici une manière élégante de centrer le contenu de UISCrollView.

Ajoutez un observateur au contentSize de votre UIScrollView, donc cette méthode sera appelée à chaque fois que le contenu change ...

[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 vue de contenu UIView.

    • Changez également en imageGalleryindiquant la taille de votre fenêtre.

Cela corrigera le centre du contenu à chaque fois que sa taille changera.

REMARQUE: la seule façon dont ce contenu ne fonctionne pas très bien est avec la fonctionnalité de zoom standard du UIScrollView.

Équipe de développement SEQOY
la source
Impossible de faire fonctionner cela. Il semble que vous centrez le scrollView et non son contenu. Pourquoi la taille de la fenêtre est-elle importante? Le code ne devrait-il pas également corriger la position x?
hpique
1

C'est ma solution à ce problème qui fonctionne assez bien pour tout type de vue à l'intérieur d'un scrollview.

-(void)scrollViewDidZoom:(__unused UIScrollView *)scrollView 
    {
    CGFloat top;
    CGFloat left;
    CGFloat bottom;
    CGFloat right;

    if (_scrollView.contentSize.width < scrollView.bounds.size.width) {
        DDLogInfo(@"contentSize %@",NSStringFromCGSize(_scrollView.contentSize));

        CGFloat width = (_scrollView.bounds.size.width-_scrollView.contentSize.width)/2.0;

        left = width;
        right = width;


    }else {
        left = kInset;
        right = kInset;
    }

    if (_scrollView.contentSize.height < scrollView.bounds.size.height) {

        CGFloat height = (_scrollView.bounds.size.height-_scrollView.contentSize.height)/2.0;

        top = height;
        bottom = height;

    }else {
        top = kInset;
        right = kInset;
    }

    _scrollView.contentInset = UIEdgeInsetsMake(top, left, bottom, right);



  if ([self.tiledScrollViewDelegate respondsToSelector:@selector(tiledScrollViewDidZoom:)])
  {
        [self.tiledScrollViewDelegate tiledScrollViewDidZoom:self];
  }
}
battre843796
la source
1

Il y a beaucoup de solutions ici, mais je risquerais de mettre ici les miennes. C'est bon pour deux raisons: cela ne gâche pas l'expérience de zoom, comme le ferait la mise à jour du cadre de vue d'image en cours, et il respecte également les encarts de vue de défilement d'origine (par exemple, définis dans xib ou storyboard pour une gestion gracieuse des barres d'outils semi-transparentes, etc.) .

Tout d'abord, définissez un petit assistant:

CGSize CGSizeWithAspectFit(CGSize containerSize, CGSize contentSize) {
    CGFloat containerAspect = containerSize.width / containerSize.height,
            contentAspect = contentSize.width / contentSize.height;

    CGFloat scale = containerAspect > contentAspect
                    ? containerSize.height / contentSize.height
                    : containerSize.width / contentSize.width;

    return CGSizeMake(contentSize.width * scale, contentSize.height * scale);
}

Pour conserver les encarts d'origine, champ défini:

UIEdgeInsets originalScrollViewInsets;

Et quelque part dans viewDidLoad, remplissez-le:

originalScrollViewInsets = self.scrollView.contentInset;

Pour placer UIImageView dans UIScrollView (en supposant que UIImage lui-même est dans la variable sharedImage):

CGSize containerSize = self.scrollView.bounds.size;
containerSize.height -= originalScrollViewInsets.top + originalScrollViewInsets.bottom;
containerSize.width -= originalScrollViewInsets.left + originalScrollViewInsets.right;

CGSize contentSize = CGSizeWithAspectFit(containerSize, loadedImage.size);

UIImageView *imageView = [[UIImageView alloc] initWithFrame:(CGRect) { CGPointZero, contentSize }];
imageView.autoresizingMask = UIViewAutoresizingNone;
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.image = loadedImage;

[self.scrollView addSubview:imageView];
self.scrollView.contentSize = contentSize;

[self centerImageViewInScrollView];

scrollViewDidZoom: à partir de UIScrollViewDelegate pour cette vue de défilement:

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
    if (scrollView == self.scrollView) {
        [self centerImageViewInScrollView];
    }
}

Un enfin, se centrant:

- (void)centerImageViewInScrollView {
    CGFloat excessiveWidth = MAX(0.0, self.scrollView.bounds.size.width - self.scrollView.contentSize.width),
            excessiveHeight = MAX(0.0, self.scrollView.bounds.size.height - self.scrollView.contentSize.height),
            insetX = excessiveWidth / 2.0,
            insetY = excessiveHeight / 2.0;

    self.scrollView.contentInset = UIEdgeInsetsMake(
            MAX(insetY, originalScrollViewInsets.top),
            MAX(insetX, originalScrollViewInsets.left),
            MAX(insetY, originalScrollViewInsets.bottom),
            MAX(insetX, originalScrollViewInsets.right)
    );
}

Je n'ai pas encore testé le changement d'orientation (c'est-à-dire la réaction appropriée pour redimensionner UIScrollView lui-même), mais le corriger devrait être relativement facile.

jazzcat
la source
1

Vous constaterez que la solution publiée par Erdemus fonctionne, mais… Il y a des cas où la méthode scrollViewDidZoom n'est pas invoquée et votre image est bloquée dans le coin supérieur gauche. Une solution simple consiste à invoquer explicitement la méthode lorsque vous affichez initialement une image, comme ceci:

[self scrollViewDidZoom: scrollView];

Dans de nombreux cas, vous pouvez invoquer cette méthode deux fois, mais il s'agit d'une solution plus propre que certaines des autres réponses de cette rubrique.

russes
la source
1

L'exemple de défilement photo d'Apple fait exactement ce que vous recherchez. Mettez ceci dans votre sous-classe UIScrollView et changez _zoomView en votre UIImageView.

-(void)layoutSubviews{
  [super layoutSubviews];
  // center the zoom view as it becomes smaller than the size of the screen
  CGSize boundsSize = self.bounds.size;
  CGRect frameToCenter = self.imageView.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;
  }
  self.imageView.frame = frameToCenter; 
}

Exemple de code de défilement photo d'Apple

Korey Hinton
la source
1

Pour que l'animation se déroule correctement, définissez

self.scrollview.bouncesZoom = NO;

et utilisez cette fonction (trouver le centre en utilisant la méthode à cette réponse )

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {
    [UIView animateWithDuration:0.2 animations:^{
        float offsetX = MAX((scrollView.bounds.size.width-scrollView.contentSize.width)/2, 0);
        float offsetY = MAX((scrollView.bounds.size.height-scrollView.contentSize.height)/2, 0);
        self.imageCoverView.center = CGPointMake(scrollView.contentSize.width*0.5+offsetX, scrollView.contentSize.height*0.5+offsetY);
    }];
}

Cela crée un effet de rebond mais n'implique aucun mouvement brusque au préalable.

ldanilek
la source
1

Juste la réponse approuvée en rapide, mais sans sous-classement en utilisant le délégué

func centerScrollViewContents(scrollView: UIScrollView) {
    let contentSize = scrollView.contentSize
    let scrollViewSize = scrollView.frame.size;
    var contentOffset = scrollView.contentOffset;

    if (contentSize.width < scrollViewSize.width) {
        contentOffset.x = -(scrollViewSize.width - contentSize.width) / 2.0
    }

    if (contentSize.height < scrollViewSize.height) {
        contentOffset.y = -(scrollViewSize.height - contentSize.height) / 2.0
    }

    scrollView.setContentOffset(contentOffset, animated: false)
}

// UIScrollViewDelegate    
func scrollViewDidZoom(scrollView: UIScrollView) {
    centerScrollViewContents(scrollView)
}
Homme léger
la source
1

Dans le cas où votre imageView interne a une largeur spécifique initiale (par exemple 300) et que vous souhaitez simplement centrer sa largeur uniquement sur un zoom plus petit que sa largeur initiale, cela peut également vous aider.

 func scrollViewDidZoom(scrollView: UIScrollView){
    if imageView.frame.size.width < 300{
        imageView.center.x = self.view.frame.width/2
    }
  }
dejix
la source
0

Voici la façon dont je fais ce travail actuellement. C'est mieux mais toujours pas parfait. Essayez de régler:

 myScrollView.bouncesZoom = YES; 

pour résoudre le problème avec la vue ne se centrant pas à minZoomScale.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
CGSize screenSize = [[self view] bounds].size;//[[UIScreen mainScreen] bounds].size;//
CGSize photoSize = [yourImage size];
CGFloat topInset = (screenSize.height - photoSize.height * [myScrollView zoomScale]) / 2.0;
CGFloat sideInset = (screenSize.width - photoSize.width * [myScrollView zoomScale]) / 2.0;

if (topInset < 0.0)
{ topInset = 0.0; }
if (sideInset < 0.0)
{ sideInset = 0.0; } 
[myScrollView setContentInset:UIEdgeInsetsMake(topInset, sideInset, -topInset, -sideInset)];
ApplicationDelegate *appDelegate = (ApplicationDelegate *)[[UIApplication sharedApplication] delegate];

CGFloat scrollViewHeight; //Used later to calculate the height of the scrollView
if (appDelegate.navigationController.navigationBar.hidden == YES) //If the NavBar is Hidden, set scrollViewHeight to 480
{ scrollViewHeight = 480; }
if (appDelegate.navigationController.navigationBar.hidden == NO) //If the NavBar not Hidden, set scrollViewHeight to 360
{ scrollViewHeight = 368; }

imageView.frame = CGRectMake(0, 0, CGImageGetWidth(yourImage)* [myScrollView zoomScale], CGImageGetHeight(yourImage)* [myScrollView zoomScale]);

[imageView setContentMode:UIViewContentModeCenter];
}

De plus, je fais ce qui suit pour empêcher l'image de coller sur le côté après un zoom arrière.

- (void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale {
myScrollView.frame = CGRectMake(0, 0, 320, 420);
 //put the correct parameters for your scroll view width and height above
}
Jonas
la source
Salut Jonah. Il semble que nous nous occupions du même problème depuis un certain temps. Vérifiera vos deux solutions et répondra bientôt.
hpique
Salut Jonah, j'ai essayé votre dernière solution. Cependant, qu'est-ce que temporaireImage? J'ai essayé de mettre temporaireImage = imageView.image; cependant, une fois que je fais un zoom, l'image disparaît. Merci, Pannag
psanketi
Pannag, image temporaire était mal nommé. Il devrait s'appeler myImage car il s'agit de n'importe quelle image que vous utilisez. Désolé pour la confusion.
Jonah
0

D'accord, je pense avoir trouvé une assez bonne solution à ce problème. L'astuce consiste à réajuster constamment le imageView'scadre. Je trouve que cela fonctionne beaucoup mieux que d'ajuster constamment le contentInsetsou contentOffSets. J'ai dû ajouter un peu de code supplémentaire pour accueillir 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];
}
Jonas
la source
0

Désactivez simplement la pagination pour que cela fonctionne correctement:

scrollview.pagingEnabled = NO;
Nagaraj
la source
0

J'ai eu exactement le même problème. Voici comment j'ai résolu

Ce code devrait être appelé à la suite de scrollView:DidScroll:

CGFloat imageHeight = self.imageView.frame.size.width * self.imageView.image.size.height / self.imageView.image.size.width;
BOOL imageSmallerThanContent = (imageHeight < self.scrollview.frame.size.height) ? YES : NO;
CGFloat topOffset = (self.imageView.frame.size.height - imageHeight) / 2;

// If image is not large enough setup content offset in a way that image is centered and not vertically scrollable
if (imageSmallerThanContent) {
     topOffset = topOffset - ((self.scrollview.frame.size.height - imageHeight)/2);
}

self.scrollview.contentInset = UIEdgeInsetsMake(topOffset * -1, 0, topOffset * -1, 0);
aryaxt
la source
0

Bien que la question soit un peu ancienne, le problème existe toujours. Je l'ai résolu dans Xcode 7 en faisant la contrainte d'espace vertical de l'élément le plus haut (dans ce cas le topLabel) aux superViews (le scrollView) en haut IBOutlet, puis en recalculant sa constante chaque fois que le contenu change en fonction de la hauteur des sous-vues de scrollView ( topLabelet bottomLabel).

class MyViewController: UIViewController {

    @IBOutlet weak var scrollView: UIScrollView!
    @IBOutlet weak var topLabel: UILabel!
    @IBOutlet weak var bottomLabel: UILabel!
    @IBOutlet weak var toTopConstraint: NSLayoutConstraint!

    override func viewDidLayoutSubviews() {
        let heightOfScrollViewContents = (topLabel.frame.origin.y + topLabel.frame.size.height - bottomLabel.frame.origin.y)
        // In my case abs() delivers the perfect result, but you could also check if the heightOfScrollViewContents is greater than 0.
        toTopConstraint.constant = abs((scrollView.frame.height - heightOfScrollViewContents) / 2)
    }

    func refreshContents() {
        // Set the label's text …

        self.view.layoutIfNeeded()
    }
}
Nick Podratz
la source