Jak automatycznie dopasować rozmiar UIScrollView do jego zawartości

131

Czy istnieje sposób na UIScrollViewautomatyczne dostosowanie wysokości (lub szerokości) przewijanej treści?

Coś jak:

[scrollView setContentSize:(CGSizeMake(320, content.height))];
jwerre
źródło

Odpowiedzi:

306

Najlepsza metoda, z jaką kiedykolwiek się spotkałem, aby zaktualizować rozmiar treści na UIScrollViewpodstawie zawartych w nim podglądów podrzędnych:

Cel C

CGRect contentRect = CGRectZero;

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

Szybki

let contentRect: CGRect = scrollView.subviews.reduce(into: .zero) { rect, view in
    rect = rect.union(view.frame)
}
scrollView.contentSize = contentRect.size
lewiatan
źródło
9
Uruchamiałem to, viewDidLayoutSubviewswięc automatyczne układanie się zakończyło, w iOS7 działało dobrze, ale testowanie systemu operacyjnego iOS6 z jakiegoś powodu nie zakończyło pracy, więc miałem złe wartości wysokości, więc przełączyłem się na viewDidAppearteraz działa dobrze. żeby tylko wskazać, że może ktoś by tego potrzebował. dzięki
Hatem Alimam
1
W przypadku iPada z iOS6 w prawym dolnym rogu pojawił się tajemniczy UIImageView (7x7). Odfiltrowałem to, akceptując tylko (widoczne i alfa) komponenty interfejsu użytkownika, a potem zadziałało. Zastanawiam się, czy ten imageView był powiązany z scrollBars, a na pewno nie z moim kodem.
JOM
5
Zachowaj ostrożność, gdy wskaźniki przewijania są włączone! UIImageView zostanie automatycznie utworzony dla każdego z nich przez SDK. musisz to uwzględnić, bo inaczej rozmiar zawartości będzie nieprawidłowy. (patrz stackoverflow.com/questions/5388703/… )
AmitP
Mogę potwierdzić, że to rozwiązanie działa idealnie dla mnie w Swift 4
Ethan Humphries
Właściwie nie możemy rozpocząć tworzenia unii od CGRect.zero, działa to tylko wtedy, gdy umieszczasz niektóre podwidoki w ujemnych współrzędnych. Należy rozpocząć sumowanie ramek widoku podrzędnego od pierwszego widoku podrzędnego, a nie od zera. Na przykład masz tylko jeden widok podrzędny, który jest widokiem UIV z ramką (50, 50, 100, 100). Twój rozmiar zawartości będzie wynosić (0, 0, 150, 150), a nie (50, 50, 100, 100).
Evgeny
74

UIScrollView nie zna automatycznie wysokości swojej zawartości. Musisz sam obliczyć wysokość i szerokość

Zrób to z czymś w rodzaju

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

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

Ale to działa tylko wtedy, gdy widoki są jeden pod drugim. Jeśli masz widok obok siebie, wystarczy dodać wysokość jednego, jeśli nie chcesz ustawiać zawartości przewijacza na większą niż jest w rzeczywistości.

czarnogóra
źródło
Czy myślisz, że coś takiego też by zadziałało? CGFloat scrollViewHeight = scrollView.contentSize.height; [scrollView setContentSize: (CGSizeMake (320, scrollViewHeight))]; Przy okazji, nie zapomnij zapytać o rozmiar, a następnie wysokość. scrollViewHeight + = view.frame.size.height
jwerre
Inicjowanie scrollViewHeight do wysokości powoduje, że przewijak jest większy niż jego zawartość. Jeśli to zrobisz, nie dodawaj wysokości widoków widocznych na początkowej wysokości zawartości scrollera. Chociaż może potrzebujesz początkowej luki lub czegoś innego.
czarnogóra
21
Lub po prostu zrób:int y = CGRectGetMaxY(((UIView*)[_scrollView.subviews lastObject]).frame); [_scrollView setContentSize:(CGSizeMake(CGRectGetWidth(_scrollView.frame), y))];
Gal.
@saintjab, dzięki, właśnie dodałem to jako formalną odpowiedź :)
Gal.
43

Rozwiązanie, jeśli używasz układu automatycznego:

  • Ustaw translatesAutoresizingMaskIntoConstraintssię NOna wszystkich widokach zaangażowanych.

  • Ustaw i zmień rozmiar widoku przewijania z ograniczeniami zewnętrznymi w stosunku do widoku przewijania.

  • Użyj ograniczeń, aby rozłożyć podglądy w widoku przewijania, upewniając się, że ograniczenia wiążą się ze wszystkimi czterema krawędziami widoku przewijania i nie polegają na widoku przewijania, aby uzyskać ich rozmiar.

Źródło: https://developer.apple.com/library/ios/technotes/tn2154/_index.html

Adam Waite
źródło
11
Imho, to prawdziwe rozwiązanie na świecie, w którym wszystko dzieje się z automatycznym układem. Odnośnie innych rozwiązań: Co ... poważnie traktujesz obliczanie wysokości i czasami szerokości każdego widoku?
Fawkes
Dziękuję za udostępnienie tego! To powinna być akceptowana odpowiedź.
SePröbläm
3
To nie działa, gdy chcesz, aby wysokość widoku przewijania była oparta na wysokości jego zawartości. (na przykład wyskakujące okienko wyśrodkowane na ekranie, w którym nie chcesz przewijać do określonej ilości treści).
To nie odpowiada na pytanie
Igor Vasilev
Świetne rozwiązanie. Jednak dodałbym jeszcze jeden punktor ... „Nie ograniczaj wysokości podrzędnego widoku stosu, jeśli powinien on rosnąć w pionie. Po prostu przypnij go do krawędzi widoku przewijania”.
Andrew Kirna,
35

Dodałem to do odpowiedzi Espuza i JCC. Używa pozycji y widoków podrzędnych i nie obejmuje pasków przewijania. Edytuj Używa dolnej części najniższego widocznego widoku podrzędnego.

+ (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;
}
Richy
źródło
1
Wspaniale! Dokładnie to, czego potrzebowałem.
Richard
2
Jestem zaskoczony, że metoda taka jak ta nie jest częścią zajęć.
João Portela,
Dlaczego zrobiłeś to self.scrollView.showsHorizontalScrollIndicator = NO; self.scrollView.showsVerticalScrollIndicator = NO;na początku i self.scrollView.showsHorizontalScrollIndicator = YES; self.scrollView.showsVerticalScrollIndicator = YES;na końcu metody?
João Portela
Dzięki richy :) +1 za odpowiedź.
Shraddha,
1
@richy masz rację wskaźniki przewijania to podglądy, jeśli są widoczne, ale logika wyświetlania / ukrywania jest nieprawidłowa. Musisz dodać dwa lokalizacje BOOL: restoreHorizontal = NO, restoreVertical = NO. Jeśli pokazuje Horizontal, ukryj go, ustaw restoreHorizontal na YES. To samo dotyczy pionu. Następnie po instrukcji for / in wstaw if: if restoreHorizontal, ustaw, aby ją pokazać, tak samo dla vertical. Twój kod po prostu wymusi pokazanie obu wskaźników
nielegalnego imigranta
14

Oto szybko akceptowana odpowiedź dla każdego, kto jest zbyt leniwy, aby ją przekonwertować :)

var contentRect = CGRectZero
for view in self.scrollView.subviews {
    contentRect = CGRectUnion(contentRect, view.frame)
}
self.scrollView.contentSize = contentRect.size
Josh
źródło
i zaktualizowano dla Swift3: var contentRect = CGRect.zero do wyświetlenia w self.scrollView.subviews {contentRect = contentRect.union (view.frame)} self.scrollView.contentSize = contentRect.size
drew ..
Czy to musi być wywołane w głównym wątku? a w didLayoutSubViews?
Maksim Kniazev
8

Oto adaptacja odpowiedzi @ leviatan w języku Swift 3:

ROZBUDOWA

import UIKit


extension UIScrollView {

    func resizeScrollViewContentSize() {

        var contentRect = CGRect.zero

        for view in self.subviews {

            contentRect = contentRect.union(view.frame)

        }

        self.contentSize = contentRect.size

    }

}

STOSOWANIE

scrollView.resizeScrollViewContentSize()

Bardzo łatwy w użyciu!

fredericdnd
źródło
Bardzo przydatne. Po tylu iteracjach znalazłem to rozwiązanie i jest idealne.
Hardik Shah
Śliczny! Właśnie to wdrożyłem.
arvidurs
7

Poniższe rozszerzenie byłoby pomocne w Swift .

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

Logika jest taka sama z podanymi odpowiedziami. Jednak pomija ukryte widoki, UIScrollViewa obliczenia są wykonywane po ustawieniu wskaźników przewijania jako ukrytych.

Istnieje również opcjonalny parametr funkcji i możesz dodać wartość przesunięcia, przekazując parametr do funkcji.

gokhanakkurt
źródło
To rozwiązanie zawiera zbyt wiele założeń, dlaczego wyświetlasz wskaźniki przewijania w metodzie, która ustawia rozmiar treści?
Kappe,
5

Świetne i najlepsze rozwiązanie od @leviathan. Po prostu przekłada się na szybkość przy użyciu podejścia FP (programowanie funkcjonalne).

self.scrollView.contentSize = self.scrollView.subviews.reduce(CGRect(), { 
  CGRectUnion($0, $1.frame) 
}.size
othierry42
źródło
Jeśli chcesz zignorować ukryte widoki, możesz przefiltrować podwidoki przed przejściem, aby zmniejszyć.
Andrew
Zaktualizowano dla Swift 3:self.contentSize = self.subviews.reduce(CGRect(), { $0.union($1.frame) }).size
John Rogers
4

Możesz uzyskać wysokość zawartości wewnątrz UIScrollView, obliczając, które dziecko „sięga dalej”. Aby to obliczyć, należy wziąć pod uwagę początek Y (początek) i wysokość przedmiotu.

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

Robiąc to w ten sposób, elementy (podglądy) nie muszą być układane bezpośrednio jeden pod drugim.

paxx
źródło
Możesz również użyć tej jednej wkładki wewnątrz pętli: maxHeight = MAX (maxHeight, child.frame.origin.y + child.frame.size.height) ;)
Thomas CG de Vilhena
3

Wymyśliłem inne rozwiązanie oparte na rozwiązaniu @ emenegro

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

Zasadniczo ustalamy, który element jest najbardziej wysunięty w dół w widoku i dodajemy wypełnienie 10 pikseli na dole

Chris
źródło
3

Ponieważ scrollView może mieć inne scrollViews lub inne drzewo subViews inDepth, preferowane jest rekurencyjne uruchamianie dogłębne. wprowadź opis obrazu tutaj

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

Stosowanie

scrollView.recalculateVerticalContentSize_synchronous()
iluvatar_GR
źródło
2

Lub po prostu zrób:

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

(To rozwiązanie zostało dodane przeze mnie jako komentarz na tej stronie. Po uzyskaniu 19 głosów pozytywnych na ten komentarz, zdecydowałem się dodać to rozwiązanie jako formalną odpowiedź dla dobra społeczności!)

Gal
źródło
2

Dla swift4 przy użyciu redukuj:

self.scrollView.contentSize = self.scrollView.subviews.reduce(CGRect.zero, {
   return $0.union($1.frame)
}).size
mm282
źródło
Warto również sprawdzić, czy co najmniej jeden podgląd podrzędny ma początek == CGPoint.zero, czy po prostu przypisać początek konkretnej (na przykład największej) podwidoku do zera.
Paul B
Działa to tylko dla osób, które umieszczają swoje widoki za pomocą CGRect (). Jeśli używasz ograniczeń, to nie działa.
linus_hologram
1

Rozmiar zależy od zawartości załadowanej do niego i opcji przycinania. Jeśli jest to widok tekstu, zależy to również od zawijania, liczby wierszy tekstu, rozmiaru czcionki itd. I tak dalej. Prawie niemożliwe do obliczenia siebie. Dobrą wiadomością jest to, że jest obliczany po załadowaniu widoku i w pliku viewWillAppear. Wcześniej wszystko jest nieznane, a rozmiar zawartości będzie taki sam jak rozmiar ramki. Ale w metodzie viewWillAppear i po (na przykład viewDidAppear) rozmiar zawartości będzie rzeczywisty.

Tata Smerf
źródło
1

Zawijanie kodu Richy'ego Stworzyłem niestandardową klasę UIScrollView, która całkowicie automatyzuje zmianę rozmiaru treści!

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

Jak używać:
Po prostu zaimportuj plik .h do kontrolera widoku i zadeklaruj instancję SBScrollView zamiast zwykłej instancji UIScrollView.

AmitP
źródło
2
layoutSubviews wygląda na to, że jest to właściwe miejsce na taki kod związany z układem. Ale w układzie UIScrollView Subview jest wywoływana za każdym razem, gdy przesunięcie zawartości jest aktualizowane podczas przewijania. A ponieważ rozmiar treści zwykle nie zmienia się podczas przewijania, jest to raczej marnotrawstwem.
Sven
To prawda. Jednym ze sposobów uniknięcia tej nieefektywności może być sprawdzenie [self isDragging] i [self is Decelerating] i wykonanie układu tylko wtedy, gdy oba są fałszywe.
Greg Brown
0

tak naprawdę zależy od treści: content.frame.height może dać ci to, czego chcesz? Zależy, czy treść jest pojedynczą rzeczą, czy zbiorem rzeczy.

Andiih
źródło
0

Uważam również, że odpowiedź Lewiatana działa najlepiej. Jednak obliczał dziwną wysokość. Jeśli w pętli podglądów podrzędnych ustawiono wyświetlanie wskaźników przewijania, będą one znajdować się w tablicy widoków podrzędnych. W takim przypadku rozwiązaniem jest tymczasowe wyłączenie wskaźników przewijania przed zapętleniem, a następnie przywrócenie ich poprzedniego ustawienia widoczności.

-(void)adjustContentSizeToFit jest metodą publiczną w niestandardowej podklasie 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
źródło
0

Myślę, że może to być zgrabny sposób aktualizowania rozmiaru widoku zawartości UIScrollView.

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
źródło
0

dlaczego nie pojedyncza linia kodu?

_yourScrollView.contentSize = CGSizeMake(0, _lastView.frame.origin.y + _lastView.frame.size.height);
Ali
źródło
0

Ustaw dynamiczny rozmiar treści w ten sposób.

 self.scroll_view.contentSize = CGSizeMake(screen_width,CGRectGetMaxY(self.controlname.frame)+20);
Monika Patel
źródło
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
źródło