Zentrieren Sie den Inhalt von UIScrollView, wenn er kleiner ist

140

Ich habe eine UIImageViewInnenseite, UIScrollViewdie ich zum Zoomen und Scrollen verwende. Wenn das Bild / der Inhalt der Bildlaufansicht größer als die Bildlaufansicht ist, funktioniert alles einwandfrei. Wenn das Bild jedoch kleiner als die Bildlaufansicht wird, bleibt es in der oberen linken Ecke der Bildlaufansicht. Ich möchte es zentriert halten, wie die Fotos-App.

Irgendwelche Ideen oder Beispiele, wie man den Inhalt des UIScrollViewZentrums beibehalten kann, wenn er kleiner ist?

Ich arbeite mit iPhone 3.0.

Der folgende Code funktioniert fast. Das Bild kehrt in die obere linke Ecke zurück, wenn ich es nach Erreichen der minimalen Zoomstufe kneife.

- (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
quelle
Haben Sie dieses Problem jemals vollständig gelöst? Ich kämpfe mit dem gleichen Problem.
Jonah
1
Achtung: Verwenden Sie den Wert initialZoom, um den Einschub zu berechnen, wenn er NICHT eins ist (kein Zoomen). Verwenden Sie beispielsweise diese Zeilen: float topInset = (maxSize.height - imageSize.height * initialZoom) / 2.0; float sideInset = (maxSize.width - imageSize.width * initialZoom) / 2.0; und schließlich den anfänglichen Zoomwert einstellen [imageScrollView setZoomScale: initialZoom];
Felix

Antworten:

228

Ich habe eine sehr einfache Lösung! Sie müssen lediglich die Mitte Ihrer Unteransicht (Bildansicht) aktualisieren, während Sie das ScrollViewDelegate vergrößern. Wenn das gezoomte Bild kleiner als die Bildlaufansicht ist, passen Sie die Unteransicht an. Das Zentrum ist sonst (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
quelle
5
Für mich ist diese Lösung ruckeliger als die Verwendung von Liams NYOBetterZoom. Vielleicht hängt es von der Bildgröße usw. ab. Die Moral; Verwenden Sie die Lösung, die Ihren Anforderungen am besten entspricht
wuf810
2
Stackoverflow GOLD STAR dafür. Ich hatte damit zu kämpfen, aber die Lösung ist so einfach.
n13
2
um ein bisschen zu vereinfachen:CGFloat offsetX = MAX((scrollView.bounds.size.width - scrollView.contentSize.width) * 0.5, 0.0);
Marek R
6
Diese Anpassung half mir, als die Bildlaufansicht Einfügungen hatte: 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
Mir ist aufgefallen, dass dies, wie viele andere Zentriertechniken, bei der Verwendung unter Problemen zu leiden scheint zoomToRect:. Die Verwendung des contentInsetAnsatzes funktioniert besser, wenn Sie diese Funktionalität benötigen. Weitere Informationen finden Sie unter petersteinberger.com/blog/2013/how-to-center-uiscrollview .
Matej Bukovinski
51

@ EvelynCordners Antwort war die, die in meiner App am besten funktioniert hat. Viel weniger Code als die anderen Optionen.

Hier ist die Swift-Version, falls jemand sie benötigt:

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.
quelle
4
Dieser funktioniert super! Die Ansicht wurde nach dem Verkleinern sogar richtig animiert.
Carter Medlin
4
Ich habe diesen Code in eine Funktion eingefügt und ihn auch in viewDidLayoutSubviews aufgerufen , um sicherzustellen, dass er ursprünglich ordnungsgemäß eingerichtet wurde.
Carter Medlin
Guter Anruf @CarterMedlin hat mir beim ersten Laden von Swift 3 sehr geholfen.
AJ Hernandez
Dies scheint zu funktionieren, aber ich kann nicht verstehen, warum, da immer die gleichen Einfügungen berechnet werden: Die Grenzen der Bildlaufansicht sind festgelegt, ebenso wie die Inhaltsgröße.
Vaddadi Kartick
contentSize ändert sich beim Zoomen von scrollView Größe ist nur fest
Michał Ziobro
25

Okay, ich habe in den letzten zwei Tagen immer wieder dagegen gekämpft und war endlich zu einer ziemlich zuverlässigen (bisher ...) Lösung gekommen. Ich dachte, ich sollte sie teilen und anderen etwas Schmerz ersparen. :) Wenn Sie ein Problem mit dieser Lösung finden, rufen Sie bitte!

Ich habe im Grunde genommen alles durchgesehen, was alle anderen haben: StackOverflow durchsuchen, die Apple Developer Forums durchsuchen, den Code nach three20, ScrollingMadness, ScrollTestSuite usw. durchsuchen. Ich habe versucht, den UIImageView-Frame zu vergrößern und mit dem Offset und / oder den Insets von UIScrollView zu spielen vom ViewController usw., aber nichts hat großartig funktioniert (wie alle anderen auch herausgefunden haben).

Nachdem ich darauf geschlafen hatte, versuchte ich ein paar alternative Blickwinkel:

  1. Wenn Sie UIImageView so unterordnen, dass es seine eigene Größe dynamisch ändert, hat dies überhaupt nicht gut funktioniert.
  2. Wenn Sie das UIScrollView so unterordnen, dass es seinen eigenen contentOffset dynamisch ändert, scheint dies für mich ein Gewinner zu sein.

Mit dieser UIScrollView-Methode der Unterklasse überschreibe ich den Mutator contentOffset, damit nicht {0,0} festgelegt wird, wenn das Bild kleiner als das Ansichtsfenster skaliert wird. Stattdessen wird der Versatz so festgelegt, dass das Bild im Ansichtsfenster zentriert bleibt. Bisher scheint es immer zu funktionieren. Ich habe es mit breiten, großen, winzigen und großen Bildern überprüft und habe nicht das Problem "funktioniert, aber bei minimalem Zoom zu kneifen bricht es".

Ich habe ein Beispielprojekt auf github hochgeladen, das diese Lösung verwendet. Sie finden es hier: http://github.com/nyoron/NYOBetterZoom

Liam Jones
quelle
2
Für Interessierte - Ich habe das oben verlinkte Projekt aktualisiert, damit es weniger davon abhängt, dass der ViewController "das Richtige" tut. Das benutzerdefinierte UIScrollView selbst kümmert sich um mehr Details.
Liam Jones
2
Liam, du rockst. Ich sage dies als Autor von ScrollingMadness. Übrigens auf dem iPad ab Version 3.2 contentInset in scrollViewDidZoom (eine neue Delegierungsmethode ab Version 3.2) funktioniert einfach.
Andrey Tarantsov
Sehr ordentliches Stück Code. Ich habe mir damit eine Weile am Kopf gekratzt. Das Projekt wurde zu Handyiphonecode.com hinzugefügt
Samvermette
Das ist toll. Danke dir. Es war kein Ausgleich dafür, dass meine UIStatusBar versteckt war, also änderte ich die Zeile anOffset.y = -(scrollViewSize.height - zoomViewSize.height) / 2.0inanOffset.y = (-(scrollViewSize.height - zoomViewSize.height) / 2.0) + 10;
Gordon Fontenot
Die Lösung bei Bi Erdemus ist meiner Meinung nach viel einfacher.
Gyozo Kudor
23

Dieser Code sollte auf den meisten Versionen von iOS funktionieren (und wurde getestet, um auf 3.1 oder höher zu funktionieren).

Es basiert auf dem Apple WWDC-Code für den Photoscoller.

Fügen Sie Ihrer 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;
}
JosephH
quelle
3
Dies sollte die akzeptierte Antwort sein, da es einfacher ist. Vielen Dank. Du hast mein Leben gerettet!!!!!! : D
Ente
Nach dem Entfernen aller API-Aufrufe für iOS 3.2+ scheint die Zentrierungslogik nur auf Geräten mit iOS 3.2+ und nicht auf 3.1.3 zu funktionieren (nervös, flackernd, wird zufällig versetzt). Ich habe die Ausgaben für Frame-Ursprung und -Größe zwischen 3.1.3 und 3.2+ verglichen, und obwohl sie aus irgendeinem Grund übereinstimmen, wird die untergeordnete Ansicht immer noch falsch positioniert. Sehr komisch. Nur Liam Jones Antwort hat für mich geklappt.
David H
1
Sowohl @Erdemus- als auch @JosephH-Lösungen funktionieren, aber der UIScrollViewUnterklassenansatz scheint vorzuziehen: Er wird aufgerufen, wenn die Ansicht zum ersten Mal + kontinuierlich angezeigt wird, während das Zoomen ausgeführt wird (während sie scrollViewDidZoomnur einmal pro Bildlauf und nachträglich aufgerufen wird)
SwiftArchitect
22

Verwenden Sie für eine Lösung, die besser für Bildlaufansichten mit automatischem Layout geeignet ist, Inhaltseinfügungen der Bildlaufansicht, anstatt die Frames der Unteransichten Ihrer Bildlaufansicht zu aktualisieren.

- (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
quelle
1
Dies hat bei mir funktioniert, aber wenn das Bild zunächst kleiner ist, hat dieser Code (wenn er direkt aufgerufen wird) die Bildlaufansicht nicht aktualisiert. Was ich brauchte , todo wurde zuerst das setzen , UIViewdass die ist innen UIScrollviewmit dem gleichen Mittelpunkt ( view.center = scrollview.center;) und dann in der scrollViewDidZoomdie Menge view.frame.origin xund yzu 0wieder.
Morksinaanab
Funktioniert auch gut für mich, aber ich habe eine dispatch_asyncan die Hauptwarteschlange gesendet, in der viewWillAppearich scrollViewDidZoom:meine Hauptscrollansicht aufrufe. Dadurch wird die Ansicht mit zentrierten Bildern angezeigt.
Pascal
Rufen Sie diesen Code auf viewDidLayoutSubviews, um sicherzustellen, dass er ordnungsgemäß eingerichtet ist, wenn die Ansicht zum ersten Mal angezeigt wird.
Carter Medlin
21

Derzeit bin ich Unterklasse UIScrollViewund überschreibe setContentOffset:, um den Versatz basierend auf anzupassen contentSize. Es funktioniert sowohl mit Prise als auch mit programmgesteuertem Zoomen.

@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

Dieser Code ist nicht nur kurz und bündig, sondern erzeugt auch einen viel weicheren Zoom als die @ Erdemus-Lösung. Sie können es in der RMGallery- Demo in Aktion sehen .

hpique
quelle
6
Sie müssen UIScrollView nicht unterklassifizieren, um diese Methode zu implementieren. Mit Apple können Sie Bildlauf- und Zoomereignisse in den Delegatenmethoden anzeigen. Insbesondere: - (void) scrollViewDidZoom: (UIScrollView *) scrollView;
Rog
1
Beste Lösung, die ich bisher gesehen habe (funktioniert auch bei Verwendung von Einschränkungen).
Frizlab
12

Ich habe einen Tag lang mit diesem Problem gekämpft und am Ende das scrollViewDidEndZooming: withView: atScale: wie folgt implementiert :

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

Dies stellt sicher, dass, wenn das Bild kleiner als der Bildschirm ist, immer noch genügend Platz um das Bild herum vorhanden ist, damit Sie es genau an der gewünschten Stelle positionieren können.

(vorausgesetzt, Ihre UIScrollView enthält eine UIImageView, um das Bild aufzunehmen)

Im Wesentlichen wird dabei überprüft, ob die Breite / Höhe Ihrer Bildansicht kleiner als die Breite / Höhe des Bildschirms ist, und in diesem Fall ein Einschub mit der Hälfte der Breite / Höhe des Bildschirms erstellt (Sie können diesen wahrscheinlich vergrößern, wenn Sie das Bild möchten die Bildschirmgrenzen verlassen).

Da es sich um eine UIScrollViewDelegate- Methode handelt, vergessen Sie nicht, sie der Deklaration Ihres View Controllers hinzuzufügen, um ein Build-Problem zu vermeiden.

Vicarius
quelle
7

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

Es wird auch keine private API verwendet. Ich kann wegen der Geheimhaltungsvereinbarung keinen Code hier 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

Jona
quelle
1
Das fragliche Beispiel ist MyImagePicker und es zeigt komischerweise das gleiche Problem.
Colin Barrett
1
Ich hätte klarer sein sollen. Das fragliche Beispiel ist tatsächlich "PhotoScroller", nicht "MyImagePicker". Sie haben Recht, dass "MyImagePicker" nicht richtig funktioniert. "PhotoScroller" jedoch. Versuch es.
Jonah
Erinnerst du dich vielleicht an den Titel des WWDC-Videos, in dem der Photoscroller besprochen wird?
Grzegorz Adam Hankiewicz
1
Es heißt "Entwerfen von Apps mit Bildlaufansichten".
Jonah
2

Ok, diese Lösung funktioniert bei mir. Ich habe eine Unterklasse UIScrollViewmit einem Verweis auf die UIImageViewangezeigte. Bei UIScrollViewjedem Zoomen wird die contentSize-Eigenschaft angepasst. Im Setter skaliere ich das UIImageViewentsprechend und stelle auch seine Mittelstellung ein.

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

Über die Zeit habe ich das Bild so eingestellt, dass UIScrollViewich die Größe ändere . Die UIScrollViewin der Bildlaufansicht ist auch eine benutzerdefinierte Klasse, die ich erstellt habe.

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

Mit diesen beiden Methoden kann ich eine Ansicht erstellen, die sich genau wie die Foto-App verhält.

AHA
quelle
Dies scheint den Trick zu tun, und es ist eine ziemlich einfache Lösung. Einige Zoomübergänge sind nicht perfekt, aber ich glaube, dass sie behoben werden können. Ich werde noch mehr experimentieren.
Hpique
Die Lösung war vor dem Update klar. Jetzt ist es ein bisschen verwirrend. Könntest Du das erläutern?
Hpique
2

Ich habe dies getan, um der Hierarchie eine zusätzliche Ansicht hinzuzufügen:

UIScrollView -> UIView -> UIImageView

Geben Sie Ihr UIViewSeitenverhältnis an UIScrollViewund zentrieren Sie UIImageViewes.

Hutmütze
quelle
Danke, Hatfinch. Ich werde es auch versuchen. Können Sie Beispielcode veröffentlichen oder meinen Beispielcode ändern, um zu zeigen, wie Sie die Ansichtshierarchie erstellen?
Hpique
Diese Art von Arbeiten. Abgesehen von der Tatsache, dass Sie, da das UIView die Größe des UIScrollView hat, einen Teil des Bildes vom Bildschirm scrollen können, wenn das Bild kleiner ist (dh Querformat anstelle von Hochformat). Die Foto-App erlaubt dies nicht und sieht viel schöner aus.
Jonah
2

Ich weiß, dass einige der obigen Antworten richtig sind, aber ich möchte meine Antwort nur mit einer Erklärung geben. Die Kommentare werden Ihnen verständlich machen, warum uns das gefällt.

Wenn ich die scrollView zum ersten Mal lade, schreibe ich den folgenden Code, um sie zu zentrieren. Bitte beachten Sie, dass wir dann contentOffsetzuerst festlegencontentInset

    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)

Wenn ich dann vContent kneife, schreibe ich den folgenden Code, um ihn zu zentrieren.

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
quelle
2

Wenn contentInsetes für nichts anderes benötigt wird, kann es verwendet werden, um den Inhalt der Bildlaufansicht zu zentrieren.

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)
    }
}

Vorteil dieses Ansatzes ist, dass Sie weiterhin contentLayoutGuideInhalte in der Bildlaufansicht platzieren können

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)
])

oder ziehen Sie den Inhalt einfach per Drag & Drop in den Interface Builder von Xcode.

Wojciech Nagrodzki
quelle
1

Sie können die contentSizeEigenschaft von UIScrollView (mithilfe von Schlüsselwertbeobachtung oder ähnlichem) beobachten und automatisch anpassen, contentInsetwenn die contentSizeÄnderungen kleiner als die Größe der Bildlaufansicht sind.

Tim
quelle
Werde versuchen. Ist dies mit den UIScrollViewDelegate-Methoden möglich, anstatt contentSize zu beobachten?
Hpique
Höchstwahrscheinlich; Sie würden verwenden - (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale(beschrieben unter developer.apple.com/iphone/library/documentation/UIKit/… ), aber ich würde es vorziehen, dies zu beobachten, contentSizeda Sie sonst die neue Zoomskala verwerfen und sie trotzdem aus der Ansicht finden. (Es sei denn, Sie können einige großartige Berechnungen basierend auf den relativen Größen Ihrer Ansichten durchführen.)
Tim
Danke, Tim. scrollViewDidEndZooming ist das, was ich benutze. Siehe fraglichen Code (ich habe ihn nach Ihrer ersten Antwort hinzugefügt). Es funktioniert fast. Das einzige Problem, das bestehen bleibt, wenn ich das Bild nach Erreichen von MinimumZoomScale kneife, kehrt es in die obere linke Ecke zurück.
hpique
Meinen Sie damit, dass es funktioniert (zentriert das Bild), um von einer anderen Skala als der minimalen Zoomskala zu kneifen, und nur dann bricht, wenn Sie versuchen, es erneut zu kneifen, sobald Sie die minimale Skala erreicht haben? Oder funktioniert es überhaupt nicht, um auf die minimale Skala zu kneifen?
Tim
Der erste. Es zentriert das Bild zum Einklemmen von einer anderen Skala als der minimalen Zoomskala und bricht ab, wenn Sie versuchen, es erneut einzuklemmen, sobald Sie die minimale Skala erreicht haben. Wenn beim ersten Mal die minimale Zoomskala erreicht wird, wird der Inhalt kurz wie von oben animiert, was einen kurzen seltsamen Effekt verursacht. Dies geschieht zumindest auf dem 3.0-Simulator.
Hpique
1

Eine elegante Möglichkeit, den Inhalt von zu zentrieren, UISCrollViewist diese.

Fügen Sie einen Beobachter zur Inhaltsgröße von hinzu UIScrollView, sodass diese Methode jedes Mal aufgerufen wird, wenn sich der Inhalt ändert ...

[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 das ändern [pointer viewWithTag:100]. Durch Ihre Inhaltsansicht ersetzen UIView.

    • Ändern Sie auch das imageGalleryZeigen auf Ihre Fenstergröße.

Dadurch wird die Mitte des Inhalts jedes Mal korrigiert, wenn sich seine Größe ändert.

HINWEIS: Der einzige Weg, wie dieser Inhalt nicht sehr gut funktioniert, ist die Standard-Zoomfunktion des UIScrollView.

SEQOY Entwicklungsteam
quelle
Konnte das nicht zum Laufen bringen. Anscheinend zentrieren Sie die scrollView und nicht ihren Inhalt. Warum ist die Fenstergröße wichtig? Sollte der Code nicht auch die x-Position korrigieren?
Hpique
1

Dies ist meine Lösung für dieses Problem, das für jede Art von Ansicht in einer Bildlaufansicht ziemlich gut funktioniert.

-(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];
  }
}
beat843796
quelle
1

Hier gibt es viele Lösungen, aber ich würde riskieren, meine eigenen hier zu platzieren. Es ist aus zwei Gründen gut: Es beeinträchtigt nicht das Zoomen, ebenso wie das Aktualisieren des laufenden Bildansichtsrahmens, und es berücksichtigt auch die ursprünglichen Bildlaufansichten (z. B. in xib oder Storyboard definiert, um eine halbtransparente Symbolleiste usw. ordnungsgemäß zu handhaben). .

Definieren Sie zunächst einen kleinen Helfer:

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

Um die ursprünglichen Einfügungen beizubehalten, definiertes Feld:

UIEdgeInsets originalScrollViewInsets;

Und irgendwo in viewDidLoad füllen Sie es:

originalScrollViewInsets = self.scrollView.contentInset;

So platzieren Sie UIImageView in UIScrollView (vorausgesetzt, UIImage selbst befindet sich in loadImage var):

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: aus UIScrollViewDelegate für diese Bildlaufansicht:

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

Und schließlich zentriert sich:

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

Ich habe die Orientierungsänderung noch nicht getestet (dh die richtige Reaktion zum Ändern der Größe von UIScrollView selbst), aber die Lösung sollte relativ einfach sein.

Jazzkatze
quelle
1

Sie werden feststellen, dass die von Erdemus veröffentlichte Lösung funktioniert, aber… In einigen Fällen wird die scrollViewDidZoom-Methode nicht aufgerufen und Ihr Bild bleibt in der oberen linken Ecke hängen. Eine einfache Lösung besteht darin, die Methode explizit aufzurufen, wenn Sie ein Bild zum ersten Mal anzeigen, wie folgt:

[self scrollViewDidZoom: scrollView];

In vielen Fällen rufen Sie diese Methode möglicherweise zweimal auf, dies ist jedoch eine sauberere Lösung als einige der anderen Antworten in diesem Thema.

russes
quelle
1

Apples Photo Scroller-Beispiel macht genau das, wonach Sie suchen. Fügen Sie dies in Ihre UIScrollView-Unterklasse ein und ändern Sie _zoomView in Ihre 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; 
}

Beispielcode für den Apple Photo Scroller

Korey Hinton
quelle
1

Stellen Sie ein, damit die Animation gut fließt

self.scrollview.bouncesZoom = NO;

und verwenden Sie diese Funktion (Finden des Zentrums mithilfe der Methode in dieser Antwort )

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

Dies erzeugt den Bouncing-Effekt, beinhaltet jedoch keine plötzlichen Bewegungen im Voraus.

ldanilek
quelle
1

Nur die genehmigte Antwort in Kürze, aber ohne Unterklasse mit dem Delegaten

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)
}
Lichtmann
quelle
1

Wenn Ihre innere imageView eine anfängliche spezifische Breite hat (z. B. 300) und Sie ihre Breite nur auf einen Zoom zentrieren möchten, der kleiner als seine anfängliche Breite ist, kann dies ebenfalls hilfreich sein.

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

Hier ist die aktuelle Art und Weise, wie ich diese Arbeit mache. Es ist besser, aber immer noch nicht perfekt. Versuchen Sie die Einstellung:

 myScrollView.bouncesZoom = YES; 

um das Problem zu beheben, dass die Ansicht nicht zentriert ist, wenn um 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];
}

Außerdem gehe ich wie folgt vor, um zu verhindern, dass das Bild nach dem Herauszoomen an der Seite klebt.

- (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
}
Jona
quelle
Hallo Jonah. Es scheint, dass wir uns schon eine Weile mit dem gleichen Thema befasst haben. Überprüft Ihre beiden Lösungen und antwortet bald.
Hpique
Hallo Jonah, ich habe deine neueste Lösung ausprobiert. Was ist temporäres Bild? Ich habe versucht, temporalImage = imageView.image zu setzen. Sobald ich jedoch zoome, verschwindet das Bild. Vielen Dank, Pannag
Psanketi
Pannag, temporäres Bild wurde schlecht benannt. Es sollte myImage heißen, da es genau das Bild ist, das Sie verwenden. Entschuldigung für die Verwirrung.
Jonah
0

Okay, ich denke, ich habe eine ziemlich gute Lösung für dieses Problem gefunden. Der Trick besteht darin, den imageView'sRahmen ständig neu einzustellen . Ich finde, das funktioniert viel besser, als das contentInsetsoder ständig anzupassen contentOffSets. Ich musste ein bisschen zusätzlichen Code hinzufügen, um sowohl Hoch- als auch Querformatbilder aufzunehmen.

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];
}
Jona
quelle
0

Deaktivieren Sie einfach die Paginierung, damit es gut funktioniert:

scrollview.pagingEnabled = NO;
Nagaraj
quelle
0

Ich hatte genau das gleiche Problem. Hier ist, wie ich gelöst habe

Dieser Code sollte als Ergebnis von aufgerufen werden 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
quelle
0

Obwohl die Frage etwas alt ist, besteht das Problem immer noch. Ich habe es in Xcode 7 gelöst, indem ich die vertikale Platzbeschränkung des obersten Elements (in diesem Fall des topLabel) auf die SuperViews (das scrollView) oben IBOutletund dann neu festgelegt habe und ihre Konstante jedes Mal neu berechnet habe, wenn sich der Inhalt abhängig von der Höhe der Unteransichten der scrollView ( topLabelund) ändert 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
quelle