wake-up-neo.net

UIScrollView mit zentrierter UIImageView, wie Fotos-App

Ich möchte eine Bildlaufansicht mit einer Bildinhaltsansicht haben. Das Bild ist eigentlich eine Karte, die viel größer als der Bildschirm ist. Die Karte sollte sich anfänglich in der Mitte der Bildlaufansicht befinden, z. B. Fotos in der Foto-App, wenn Sie das iPhone in Querformat ausrichten.

alt text

Ich habe es nicht geschafft, die Karte in der Mitte mit korrektem Zoomen und Scrollen gleichzeitig zu haben. Falls das Kartenbild oben im Bildschirm (in Hochformat) beginnt, sieht der Code wie folgt aus:

- (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;
}

Wenn ich den Bildrahmen in die Mitte bewege, wird das Bild nur vom oberen Rand des Bildrahmens nach unten angezeigt. Ich habe versucht, mit mapView transform herumzuspielen, wobei sich der Frame des imageView dynamisch ändert. Für mich funktioniert bisher nichts.

37
Martin Ludvik

Dieser Code sollte auf den meisten iOS-Versionen funktionieren (und wurde getestet, um ab 3.1 zu funktionieren).

Es basiert auf dem Apple WWDC-Code, der in Jonahs Antwort erwähnt wird.

Fügen Sie der Unterklasse von UIScrollView Folgendes hinzu und ersetzen Sie tileContainerView durch die Ansicht, die Ihr Bild oder Ihre Kacheln enthält:

- (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

Hier ist, was ich in Betracht ziehen würde, die Lösung wie sie sich genau wie Apples Foto-App verhält. Ich hatte Lösungen verwendet, die verwendet haben:

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

neu zu zentrieren, aber ich mochte diese Lösung nicht, weil das Zoomen nach dem Zoomen abprallte und dann schnell in die Mitte sprang, was sehr un-sexy war. Es stellt sich heraus, wenn Sie genau dieselbe Logik verwenden, jedoch in dieser Delegiertenfunktion:

-(void)scrollViewDidZoom:(UIScrollView *)pScrollView

es beginnt sowohl zentriert als auch wenn es herausgezoomt wird bleibt zentriert:

-(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;
}

Dabei ist 'imageView' der von Ihnen verwendete UIImageView.

23
Shizam

Apple hat die Videos der WWDC-Sitzung 2010 für alle Mitglieder des iPhone-Entwicklerprogramms veröffentlicht. Eines der behandelten Themen ist, wie sie die Foto-App erstellt haben !!! Sie bauen eine sehr ähnliche App Schritt für Schritt auf und haben den gesamten Code kostenlos zur Verfügung gestellt.

Es verwendet auch keine private API. Ich kann den Code wegen der Geheimhaltungsvereinbarung hier nicht einfügen, aber hier ist ein Link zum Beispielcode-Download. Sie müssen sich wahrscheinlich anmelden, um Zugriff zu erhalten.

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

Und hier ist ein Link zur iTunes WWDC-Seite:

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

Ich vermute, dass Sie die UIScrollView der contentOffset setzen müssen.

2
Rog

Das hat für mich funktioniert:

- (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);

}

dabei ist 160 die Hälfte der Breite/Höhe Ihrer UIScrollView.

Später habe ich den Contentoffset auf den hier erfassten gesetzt.

1
Puppet

Ich wünschte, es wäre so einfach. Ich recherchierte im Internet und stellte fest, dass es nicht nur mein Problem ist, sondern viele Leute kämpfen mit dem gleichen Problem nicht nur auf dem iPhone, sondern auch auf Apples Desktop Cocoa. __ Siehe folgende Links:

http://www.iphonedevsdk.com/forum/iphone-sdk-development/5740-uiimageview-uiscrollview.html
Die beschriebene Lösung basiert auf der Eigenschaft UIViewContentModeScaleAspectFit des Bildes, funktioniert aber leider nicht sehr gut. Das Bild ist zentriert und wächst ordentlich, aber der Sprungbereich scheint viel größer als das Bild zu sein.

Dieser Typ bekam auch keine Antwort:
http://discussions.Apple.com/thread.jspa?messageID=8322675

Und schließlich das gleiche Problem auf Apples Desktop Cocoa:
http://www.cocoadev.com/index.pl?CenteringInsideNSScrollView
Ich nehme an, die Lösung funktioniert, aber sie basiert auf dem NSClipView, das nicht auf dem iPhone ist ...

Hat jemand eine Lösung für das iPhone? 

1
Martin Ludvik

Hinweis: Diese Methode funktioniert. Wenn das Bild kleiner ist als die Bildansicht, wird es teilweise vom Bildschirm verschoben. Keine große Sache, aber auch nicht so schön wie die Foto-App.

Zunächst ist es wichtig zu verstehen, dass es sich um 2 Ansichten handelt, die Bildansicht mit dem Bild darin und die Bildlaufansicht mit der Bildansicht .. _ Stellen Sie zunächst die Bildansicht auf die Größe des Bildschirms:

 [myImageView setFrame:self.view.frame];

Dann zentrieren Sie Ihr Bild in der Bildansicht:

 myImageView.contentMode = UIViewContentModeCenter;

Hier ist mein gesamter 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

Okay, ich glaube, ich habe eine ziemlich gute Lösung für dieses Problem gefunden. Der Trick besteht darin, den Frame der imageView ständig neu anzupassen. Ich finde, dass dies viel besser funktioniert, als die contentInsets oder contentOffSets ständig anzupassen. Ich musste ein wenig zusätzlichen Code hinzufügen, um sowohl Hoch- als auch Landschaftsbilder aufnehmen zu können.

Hier ist der 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

In Monotouch hat das für mich funktioniert.

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

Eine elegante Möglichkeit, den Inhalt von UISCrollView zu zentrieren, ist dies.

Fügen Sie dem contentSize Ihrer UIScrollView einen Beobachter hinzu, damit diese Methode bei jeder Inhaltsänderung aufgerufen wird.

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

Nun zu Ihrer Beobachtermethode:

- (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 ); }
  • Sie sollten den [pointer viewWithTag:100] ändern. Ersetzen Sie durch Ihre Inhaltsansicht UIView.

    • Ändern Sie auch imageGallery und zeigen Sie auf Ihre Fenstergröße.

Dadurch wird die Mitte des Inhalts bei jeder Größenänderung korrigiert. 

HINWEIS: Der einzige Weg, an dem dieser Inhalt nicht sehr gut funktioniert, ist die standardmäßige Zoomfunktion der UIScrollView

Hier ist eine alternative Lösung, ähnlich der Antwort von @ JosephH, die jedoch die tatsächlichen Abmessungen des Bildes berücksichtigt. Wenn der Benutzer schwenkt/zoomt, haben Sie niemals mehr Leerzeichen auf dem Bildschirm als erforderlich. Dies ist ein häufiges Problem, wenn Sie beispielsweise ein Querformat auf einem Hochformatbildschirm anzeigen. Es wird Leerzeichen über und unter dem Bild angezeigt, wenn das gesamte Bild auf dem Bildschirm angezeigt wird (Aspect Fit). Beim Vergrößern wird der Whitespace dann für andere Lösungen als Teil des Bildes betrachtet, da er sich in der imageView befindet. Damit können Sie den Großteil des Bildes außerhalb des Bildschirms schwenken und nur den Leerraum sichtbar lassen. Das sieht für den Benutzer schlecht aus.

Bei dieser Klasse müssen Sie das ImageView übergeben, mit dem es arbeitet. Ich war versucht, es automatisch erkennen zu lassen, aber das ist schneller und Sie wollen alle Geschwindigkeit, die Sie mit der layoutSubviews-Methode erreichen können.

Hinweis: Dies setzt voraus, dass AutoLayout für die Bildlaufansicht nicht aktiviert ist.

//
//  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