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

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

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.


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


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

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

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!


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.


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

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.


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


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

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


2

Dla swift4 przy użyciu redukuj:

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

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.


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.


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.


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

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

0

dlaczego nie pojedyncza linia kodu?

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

0

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

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

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

}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.