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))];
jwerre
quelle

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
Leviathan
quelle
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.

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

Adam Waite
quelle
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;
}
reich
quelle
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
Josh
quelle
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!

fredericdnd
quelle
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.

Gokhanakkurt
quelle
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
othierry42
quelle
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.

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

Chris
quelle
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()
iluvatar_GR
quelle
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!)

Gal
quelle
2

Für swift4 mit reduzieren:

self.scrollView.contentSize = self.scrollView.subviews.reduce(CGRect.zero, {
   return $0.union($1.frame)
}).size
mm282
quelle
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.

Papa Schlumpf
quelle
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.

AmitP
quelle
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);
Monika Patel
quelle
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.

Andiih
quelle
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;
}
Clayzar
quelle
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
    }
}
Furqan Khan
quelle
0

warum nicht einzelne Codezeile?

_yourScrollView.contentSize = CGSizeMake(0, _lastView.frame.origin.y + _lastView.frame.size.height);
Ali
quelle
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)
    }

}
user6127890
quelle