Wie kann ich eine UIScrollView automatisch an ihren Inhalt anpassen?


130

Gibt es eine Möglichkeit, eine UIScrollViewautomatische Anpassung an die Höhe (oder Breite) des Inhalts vorzunehmen , durch den gescrollt wird?

Etwas wie:

[scrollView setContentSize:(CGSizeMake(320, content.height))];

Antworten:


306

Die beste Methode, die ich je gefunden habe, um die Inhaltsgröße von a UIScrollViewbasierend auf den enthaltenen Unteransichten zu aktualisieren:

Ziel c

CGRect contentRect = CGRectZero;

for (UIView *view in self.scrollView.subviews) {
    contentRect = CGRectUnion(contentRect, view.frame);
}
self.scrollView.contentSize = contentRect.size;

Schnell

let contentRect: CGRect = scrollView.subviews.reduce(into: .zero) { rect, view in
    rect = rect.union(view.frame)
}
scrollView.contentSize = contentRect.size

9
Ich habe dies ausgeführt, viewDidLayoutSubviewsdamit das Autolayout beendet wird. In iOS7 hat es gut funktioniert, aber das Testen des Autolayouts unter iOS6 hat die Arbeit aus irgendeinem Grund nicht beendet, sodass ich einige falsche Höhenwerte hatte viewDidAppear. nur um darauf hinzuweisen, dass vielleicht jemand dies brauchen würde. danke
Hatem Alimam

1
Mit iOS6 iPad gab es unten rechts eine mysteriöse UIImageView (7x7). Ich habe das weggefiltert, indem ich nur (sichtbare && Alpha) UI-Komponenten akzeptiert habe, dann hat es funktioniert. Ich frage mich, ob diese imageView mit scrollBars zusammenhängt, definitiv nicht mit meinem Code.
JOM

5
Bitte seien Sie vorsichtig, wenn die Bildlaufanzeigen aktiviert sind! Für jedes SDII wird automatisch eine UIImageView erstellt. Sie müssen dies berücksichtigen, sonst ist Ihre Inhaltsgröße falsch. (siehe stackoverflow.com/questions/5388703/… )
AmitP

Kann bestätigen, dass diese Lösung perfekt für mich in Swift 4
Ethan Humphries

Tatsächlich können wir die Vereinigung nicht über CGRect.zero starten. Dies funktioniert nur, wenn Sie einige Unteransichten in negativen Koordinaten anzeigen. Sie sollten Union-Unteransichtsrahmen von der ersten Unteransicht aus starten, nicht von Null. Sie haben beispielsweise nur eine Unteransicht, nämlich eine UIView mit Frame (50, 50, 100, 100). Ihre Inhaltsgröße ist (0, 0, 150, 150), nicht (50, 50, 100, 100).
Evgeny

74

UIScrollView kennt die Höhe seines Inhalts nicht automatisch. Sie müssen die Höhe und Breite selbst berechnen

Mach es mit so etwas wie

CGFloat scrollViewHeight = 0.0f;
for (UIView* view in scrollView.subviews)
{
   scrollViewHeight += view.frame.size.height;
}

[scrollView setContentSize:(CGSizeMake(320, scrollViewHeight))];

Dies funktioniert jedoch nur, wenn die Ansichten untereinander liegen. Wenn Sie eine Ansicht nebeneinander haben, müssen Sie nur die Höhe einer Ansicht hinzufügen, wenn Sie den Inhalt des Scrollers nicht größer einstellen möchten, als er tatsächlich ist.


Glaubst du, so etwas würde auch funktionieren? CGFloat scrollViewHeight = scrollView.contentSize.height; [scrollView setContentSize: (CGSizeMake (320, scrollViewHeight))]; Übrigens, vergessen Sie nicht, nach der Größe und dann nach der Höhe zu fragen. scrollViewHeight + = view.frame.size.height
jwerre

Durch das Initialisieren von scrollViewHeight auf die Höhe wird der Scroller größer als sein Inhalt. Wenn Sie dies tun, fügen Sie nicht die Höhe der Ansichten hinzu, die auf der Anfangshöhe des Scroller-Inhalts sichtbar sind. Obwohl Sie vielleicht eine anfängliche Lücke oder etwas anderes brauchen.
emenegro

21
Oder machen Sie einfach:int y = CGRectGetMaxY(((UIView*)[_scrollView.subviews lastObject]).frame); [_scrollView setContentSize:(CGSizeMake(CGRectGetWidth(_scrollView.frame), y))];
Gal

@ Saintjab, danke, ich habe es gerade als formelle Antwort hinzugefügt :)
Gal

39

Lösung, wenn Sie das automatische Layout verwenden:

  • Stellen Sie translatesAutoresizingMaskIntoConstraintsauf NOauf allen beteiligten Ansichten.

  • Positionieren und skalieren Sie Ihre Bildlaufansicht mit Einschränkungen außerhalb der Bildlaufansicht.

  • Verwenden Sie Einschränkungen, um die Unteransichten in der Bildlaufansicht anzuordnen. Achten Sie dabei darauf, dass die Einschränkungen an alle vier Kanten der Bildlaufansicht gebunden sind und sich nicht auf die Bildlaufansicht verlassen, um ihre Größe zu ermitteln.

Quelle: https://developer.apple.com/library/ios/technotes/tn2154/_index.html


9
Imho ist dies die echte Lösung in der Welt, in der alles mit automatischem Layout passiert. Zu den anderen Lösungen: Was zum ... meinen Sie ernst mit der Berechnung der Höhe und manchmal der Breite jeder Ansicht?
Fawkes

Vielen Dank für das Teilen! Das sollte die akzeptierte Antwort sein.
SePröbläm

2
Dies funktioniert nicht, wenn die Höhe der Bildlaufansicht auf der Höhe des Inhalts basieren soll. (Zum Beispiel ein Popup in der Mitte des Bildschirms, in dem Sie erst nach einer bestimmten Menge an Inhalten scrollen möchten).
LeGom

Dies beantwortet die Frage nicht
Igor Vasilev

Tolle Lösung. Ich würde jedoch noch eine Kugel hinzufügen ... "Beschränken Sie die Höhe einer untergeordneten Stapelansicht nicht, wenn sie vertikal wachsen soll. Stecken Sie sie einfach an die Ränder der Bildlaufansicht."
Andrew Kirna

35

Ich habe dies zu Espuz und JCCs Antwort hinzugefügt. Es verwendet die y-Position der Unteransichten und enthält keine Bildlaufleisten. Bearbeiten Verwendet den unteren Rand der untersten sichtbaren Unteransicht.

+ (CGFloat) bottomOfLowestContent:(UIView*) view
{
    CGFloat lowestPoint = 0.0;

    BOOL restoreHorizontal = NO;
    BOOL restoreVertical = NO;

    if ([view respondsToSelector:@selector(setShowsHorizontalScrollIndicator:)] && [view respondsToSelector:@selector(setShowsVerticalScrollIndicator:)])
    {
        if ([(UIScrollView*)view showsHorizontalScrollIndicator])
        {
            restoreHorizontal = YES;
            [(UIScrollView*)view setShowsHorizontalScrollIndicator:NO];
        }
        if ([(UIScrollView*)view showsVerticalScrollIndicator])
        {
            restoreVertical = YES;
            [(UIScrollView*)view setShowsVerticalScrollIndicator:NO];
        }
    }
    for (UIView *subView in view.subviews)
    {
        if (!subView.hidden)
        {
            CGFloat maxY = CGRectGetMaxY(subView.frame);
            if (maxY > lowestPoint)
            {
                lowestPoint = maxY;
            }
        }
    }
    if ([view respondsToSelector:@selector(setShowsHorizontalScrollIndicator:)] && [view respondsToSelector:@selector(setShowsVerticalScrollIndicator:)])
    {
        if (restoreHorizontal)
        {
            [(UIScrollView*)view setShowsHorizontalScrollIndicator:YES];
        }
        if (restoreVertical)
        {
            [(UIScrollView*)view setShowsVerticalScrollIndicator:YES];
        }
    }

    return lowestPoint;
}

1
Wunderbar! Genau das, was ich brauchte.
Richard

2
Ich bin überrascht, dass eine Methode wie diese nicht Teil der Klasse ist.
João Portela

Warum haben Sie self.scrollView.showsHorizontalScrollIndicator = NO; self.scrollView.showsVerticalScrollIndicator = NO;am Anfang und self.scrollView.showsHorizontalScrollIndicator = YES; self.scrollView.showsVerticalScrollIndicator = YES;am Ende der Methode gearbeitet?
João Portela

Danke Richy :) +1 für die Antwort.
Shraddha

1
@richy du hast recht Bildlaufindikatoren sind Unteransichten, wenn sie sichtbar sind, aber das Ein- und Ausblenden der Logik ist falsch. Sie müssen zwei BOOL-Locals hinzufügen: restoreHorizontal = NO, restoreVertical = NO. Wenn showHorizontal angezeigt wird, blenden Sie es aus und setzen Sie restoreHorizontal auf YES. Gleiches gilt für die Vertikale. Als nächstes setzen Sie nach der for / in-Schleife die if-Anweisung ein: Wenn restoreHorizontal, setzen Sie sie so, dass sie angezeigt wird, dasselbe gilt für vertikal. Ihr Code wird nur zwingen, beide Indikatoren anzuzeigen
illegaler Einwanderer

13

Hier ist die akzeptierte Antwort in Kürze für alle, die zu faul sind, um sie zu konvertieren :)

var contentRect = CGRectZero
for view in self.scrollView.subviews {
    contentRect = CGRectUnion(contentRect, view.frame)
}
self.scrollView.contentSize = contentRect.size

und aktualisiert für Swift3: var contentRect = CGRect.zero für die Ansicht in self.scrollView.subviews {contentRect = contentRect.union (view.frame)} self.scrollView.contentSize = contentRect.size
zeichnete ..

Muss dies im Hauptthread aufgerufen werden? und in didLayoutSubViews?
Maksim Kniazev

8

Hier ist eine Swift 3-Adaption von @ leviatans Antwort:

ERWEITERUNG

import UIKit


extension UIScrollView {

    func resizeScrollViewContentSize() {

        var contentRect = CGRect.zero

        for view in self.subviews {

            contentRect = contentRect.union(view.frame)

        }

        self.contentSize = contentRect.size

    }

}

VERWENDUNG

scrollView.resizeScrollViewContentSize()

Sehr einfach zu bedienen!


Sehr hilfreich. Nach so vielen Iterationen habe ich diese Lösung gefunden und sie ist perfekt.
Hardik Shah

Schön! Einfach implementiert.
Arvidurs

7

Die folgende Erweiterung wäre in Swift hilfreich .

extension UIScrollView{
    func setContentViewSize(offset:CGFloat = 0.0) {
        // dont show scroll indicators
        showsHorizontalScrollIndicator = false
        showsVerticalScrollIndicator = false

        var maxHeight : CGFloat = 0
        for view in subviews {
            if view.isHidden {
                continue
            }
            let newHeight = view.frame.origin.y + view.frame.height
            if newHeight > maxHeight {
                maxHeight = newHeight
            }
        }
        // set content size
        contentSize = CGSize(width: contentSize.width, height: maxHeight + offset)
        // show scroll indicators
        showsHorizontalScrollIndicator = true
        showsVerticalScrollIndicator = true
    }
}

Die Logik ist bei den gegebenen Antworten dieselbe. Es werden jedoch versteckte Ansichten darin weggelassenUIScrollView und die Berechnung wird durchgeführt, nachdem die Bildlaufindikatoren ausgeblendet wurden.

Außerdem gibt es einen optionalen Funktionsparameter, und Sie können einen Versatzwert hinzufügen, indem Sie den Parameter an die Funktion übergeben.


Diese Lösung enthält zu viele Annahmen. Warum werden die Bildlaufindikatoren in einer Methode angezeigt, mit der die Inhaltsgröße festgelegt wird?
Kappe

5

Tolle und beste Lösung von @leviathan. Einfach mit dem FP-Ansatz (Functional Programming) schnell umsetzen.

self.scrollView.contentSize = self.scrollView.subviews.reduce(CGRect(), { 
  CGRectUnion($0, $1.frame) 
}.size

Wenn Sie versteckte Ansichten ignorieren möchten, können Sie Unteransichten vor dem Übergeben filtern, um sie zu reduzieren.
Andy

Aktualisiert für Swift 3:self.contentSize = self.subviews.reduce(CGRect(), { $0.union($1.frame) }).size
John Rogers

4

Sie können die Höhe des Inhalts in UIScrollView ermitteln, indem Sie berechnen, welches Kind "weiter kommt". Um dies zu berechnen, müssen Sie Herkunft Y (Start) und Artikelhöhe berücksichtigen.

float maxHeight = 0;
for (UIView *child in scrollView.subviews) {
    float childHeight = child.frame.origin.y + child.frame.size.height;
    //if child spans more than current maxHeight then make it a new maxHeight
    if (childHeight > maxHeight)
        maxHeight = childHeight;
}
//set content size
[scrollView setContentSize:(CGSizeMake(320, maxHeight))];

Auf diese Weise müssen Elemente (Unteransichten) nicht direkt untereinander gestapelt werden.


Sie können diesen einen Liner auch innerhalb der Schleife verwenden: maxHeight = MAX (maxHeight, child.frame.origin.y + child.frame.size.height) ;)
Thomas CG de Vilhena

3

Ich habe eine andere Lösung gefunden, die auf der Lösung von @ emenegro basiert

NSInteger maxY = 0;
for (UIView* subview in scrollView.subviews)
{
    if (CGRectGetMaxY(subview.frame) > maxY)
    {
        maxY = CGRectGetMaxY(subview.frame);
    }
}
maxY += 10;
[scrollView setContentSize:CGSizeMake(scrollView.frame.size.width, maxY)];

Grundsätzlich ermitteln wir, welches Element in der Ansicht am weitesten unten liegt, und fügen unten eine 10-Pixel-Polsterung hinzu


3

Da eine scrollView andere scrollViews oder einen anderen inDepth-SubViews-Baum haben kann, ist es vorzuziehen, die Tiefe rekursiv auszuführen. Geben Sie hier die Bildbeschreibung ein

Swift 2

extension UIScrollView {
    //it will block the mainThread
    func recalculateVerticalContentSize_synchronous () {
        let unionCalculatedTotalRect = recursiveUnionInDepthFor(self)
        self.contentSize = CGRectMake(0, 0, self.frame.width, unionCalculatedTotalRect.height).size;
    }

    private func recursiveUnionInDepthFor (view: UIView) -> CGRect {
        var totalRect = CGRectZero
        //calculate recursevly for every subView
        for subView in view.subviews {
            totalRect =  CGRectUnion(totalRect, recursiveUnionInDepthFor(subView))
        }
        //return the totalCalculated for all in depth subViews.
        return CGRectUnion(totalRect, view.frame)
    }
}

Verwendung

scrollView.recalculateVerticalContentSize_synchronous()

2

Oder einfach:

int y = CGRectGetMaxY(((UIView*)[_scrollView.subviews lastObject]).frame); [_scrollView setContentSize:(CGSizeMake(CGRectGetWidth(_scrollView.frame), y))];

(Diese Lösung wurde von mir als Kommentar auf dieser Seite hinzugefügt. Nachdem ich 19 Stimmen für diesen Kommentar erhalten habe, habe ich beschlossen, diese Lösung als formelle Antwort zum Nutzen der Community hinzuzufügen!)


2

Für swift4 mit reduzieren:

self.scrollView.contentSize = self.scrollView.subviews.reduce(CGRect.zero, {
   return $0.union($1.frame)
}).size

Es lohnt sich auch zu überprüfen, ob mindestens eine Unteransicht origin == CGPoint.zero hat, oder einfach den Ursprung einer bestimmten (zum Beispiel größten) Unteransicht Null zuzuweisen.
Paul B

Dies funktioniert nur für Personen, die ihre Ansichten mit CGRect () platzieren. Wenn Sie Einschränkungen verwenden, funktioniert dies nicht.
linus_hologram

1

Die Größe hängt vom darin geladenen Inhalt und den Beschneidungsoptionen ab. Wenn es sich um eine Textansicht handelt, hängt dies auch vom Umbruch, der Anzahl der Textzeilen, der Schriftgröße usw. ab. Fast unmöglich für Sie, sich selbst zu berechnen. Die gute Nachricht ist, dass sie nach dem Laden der Ansicht und in viewWillAppear berechnet wird. Vorher ist alles unbekannt und die Inhaltsgröße entspricht der Frame-Größe. In der viewWillAppear-Methode und danach (z. B. viewDidAppear) ist die Inhaltsgröße jedoch die tatsächliche.


1

Um den Code von Richy zu verpacken Ich habe eine benutzerdefinierte UIScrollView-Klasse erstellt, die die Größenänderung von Inhalten vollständig automatisiert!

SBScrollView.h

@interface SBScrollView : UIScrollView
@end

SBScrollView.m:

@implementation SBScrollView
- (void) layoutSubviews
{
    CGFloat scrollViewHeight = 0.0f;
    self.showsHorizontalScrollIndicator = NO;
    self.showsVerticalScrollIndicator = NO;
    for (UIView* view in self.subviews)
    {
        if (!view.hidden)
        {
            CGFloat y = view.frame.origin.y;
            CGFloat h = view.frame.size.height;
            if (y + h > scrollViewHeight)
            {
                scrollViewHeight = h + y;
            }
        }
    }
    self.showsHorizontalScrollIndicator = YES;
    self.showsVerticalScrollIndicator = YES;
    [self setContentSize:(CGSizeMake(self.frame.size.width, scrollViewHeight))];
}
@end

Verwendung:
Importieren Sie einfach die .h-Datei in Ihren View Controller und deklarieren Sie eine SBScrollView-Instanz anstelle der normalen UIScrollView-Instanz.


2
layoutSubviews scheint der richtige Ort für solchen layoutbezogenen Code zu sein. In einem UIScrollView-Layout wirdubview jedoch jedes Mal aufgerufen, wenn der Inhaltsversatz beim Scrollen aktualisiert wird. Und da sich die Inhaltsgröße beim Scrollen normalerweise nicht ändert, ist dies ziemlich verschwenderisch.
Sven

Das ist richtig. Eine Möglichkeit, diese Ineffizienz zu vermeiden, besteht darin, [self isDragging] und [self isDecelerating] zu überprüfen und das Layout nur durchzuführen, wenn beide falsch sind.
Greg Brown

1

Stellen Sie die Größe des dynamischen Inhalts wie folgt ein.

 self.scroll_view.contentSize = CGSizeMake(screen_width,CGRectGetMaxY(self.controlname.frame)+20);

0

es hängt wirklich vom Inhalt ab: content.frame.height könnte Ihnen geben, was Sie wollen? Kommt darauf an, ob Inhalt eine einzelne Sache oder eine Sammlung von Dingen ist.


0

Ich fand auch Leviathans Antwort, um am besten zu funktionieren. Es wurde jedoch eine seltsame Höhe berechnet. Wenn beim Durchlaufen der Unteransichten die Bildlaufansicht so eingestellt ist, dass Bildlaufindikatoren angezeigt werden, befinden sich diese im Array der Unteransichten. In diesem Fall besteht die Lösung darin, die Bildlaufanzeigen vor dem Schleifen vorübergehend zu deaktivieren und dann ihre vorherige Sichtbarkeitseinstellung wiederherzustellen.

-(void)adjustContentSizeToFit ist eine öffentliche Methode für eine benutzerdefinierte Unterklasse von UIScrollView.

-(void)awakeFromNib {    
    dispatch_async(dispatch_get_main_queue(), ^{
        [self adjustContentSizeToFit];
    });
}

-(void)adjustContentSizeToFit {

    BOOL showsVerticalScrollIndicator = self.showsVerticalScrollIndicator;
    BOOL showsHorizontalScrollIndicator = self.showsHorizontalScrollIndicator;

    self.showsVerticalScrollIndicator = NO;
    self.showsHorizontalScrollIndicator = NO;

    CGRect contentRect = CGRectZero;
    for (UIView *view in self.subviews) {
        contentRect = CGRectUnion(contentRect, view.frame);
    }
    self.contentSize = contentRect.size;

    self.showsVerticalScrollIndicator = showsVerticalScrollIndicator;
    self.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator;
}

0

Ich denke, dies kann eine gute Möglichkeit sein, die Größe der Inhaltsansicht von UIScrollView zu aktualisieren.

extension UIScrollView {
    func updateContentViewSize() {
        var newHeight: CGFloat = 0
        for view in subviews {
            let ref = view.frame.origin.y + view.frame.height
            if ref > newHeight {
                newHeight = ref
            }
        }
        let oldSize = contentSize
        let newSize = CGSize(width: oldSize.width, height: newHeight + 20)
        contentSize = newSize
    }
}

0

warum nicht einzelne Codezeile?

_yourScrollView.contentSize = CGSizeMake(0, _lastView.frame.origin.y + _lastView.frame.size.height);

0
import UIKit

class DynamicSizeScrollView: UIScrollView {

    var maxHeight: CGFloat = UIScreen.main.bounds.size.height
    var maxWidth: CGFloat = UIScreen.main.bounds.size.width

    override func layoutSubviews() {
        super.layoutSubviews()
        if !__CGSizeEqualToSize(bounds.size,self.intrinsicContentSize){
            self.invalidateIntrinsicContentSize()
        }
    }

    override var intrinsicContentSize: CGSize {
        let height = min(contentSize.height, maxHeight)
        let width = min(contentSize.height, maxWidth)
        return CGSize(width: width, height: height)
    }

}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.