Come dimensionare automaticamente un UIScrollView per adattarlo al suo contenuto


130

C'è un modo per UIScrollViewregolare automaticamente l'altezza (o la larghezza) del contenuto che sta scorrendo?

Qualcosa di simile a:

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

Risposte:


306

Il metodo migliore che abbia mai incontrato per aggiornare la dimensione del contenuto di una in UIScrollViewbase alle sue visualizzazioni secondarie contenute:

Objective-C

CGRect contentRect = CGRectZero;

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

veloce

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

9
Lo stavo eseguendo in viewDidLayoutSubviewsmodo che l'autolayout finisse, in iOS7 ha funzionato bene, ma test su iOS6 l'autolayout per qualche motivo non ha finito il lavoro, quindi avevo alcuni valori di altezza sbagliati, quindi sono passato viewDidAppearora funziona bene .. solo per sottolineare che forse qualcuno avrebbe bisogno di questo. grazie
Hatem Alimam

1
Con iOS6 iPad c'era un misterioso UIImageView (7x7) nell'angolo in basso a destra. L'ho filtrato accettando solo i componenti dell'interfaccia utente (visibili && alpha), quindi ha funzionato. Mi chiedevo se quell'immagine fosse correlata a scrollBars, sicuramente non al mio codice.
JOM

5
Si prega di fare attenzione quando gli indicatori di scorrimento sono abilitati! un UIImageView verrà creato automaticamente per ognuno dall'SDK. devi renderlo conto altrimenti la tua dimensione del contenuto sarà errata. (Vedi stackoverflow.com/questions/5388703/... )
AmitP

Posso confermare che questa soluzione funziona perfettamente per me in Swift 4
Ethan Humphries,

In realtà, non possiamo iniziare l'unione da CGRect.zero, funziona solo se ti trovi in ​​alcune sottoview in coordinate negative. È necessario avviare i frame della sottoview unione dal primo subview, non da zero. Ad esempio, hai solo una sottoview che è una UIView con frame (50, 50, 100, 100). Le dimensioni del contenuto saranno (0, 0, 150, 150), non (50, 50, 100, 100).
Evgeny,

74

UIScrollView non conosce automaticamente l'altezza del suo contenuto. Devi calcolare l'altezza e la larghezza per te stesso

Fallo con qualcosa del genere

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

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

Ma questo funziona solo se le viste sono una sotto l'altra. Se hai una vista l'una accanto all'altra devi solo aggiungere l'altezza di una se non vuoi impostare il contenuto dello scroller più grande di quello che è realmente.


Pensi che funzionerebbe anche qualcosa del genere? CGFloat scrollViewHeight = scrollView.contentSize.height; [scrollView setContentSize: (CGSizeMake (320, scrollViewHeight))]; A proposito, non dimenticare di chiedere la dimensione quindi l'altezza. scrollViewHeight + = view.frame.size.height
jwerre

L'inizializzazione di scrollViewHeight all'altezza rende lo scroller più grande del suo contenuto. In questo caso non aggiungere l'altezza delle viste visibili sull'altezza iniziale del contenuto dello scroller. Anche se forse hai bisogno di un gap iniziale o qualcos'altro.
emenegro

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

@saintjab, grazie, l'ho appena aggiunto come risposta formale :)
Gal

41

Soluzione se si utilizza il layout automatico:

  • Situato translatesAutoresizingMaskIntoConstraintsa NOsu tutte le viste coinvolte.

  • Posiziona e ridimensiona la vista di scorrimento con vincoli esterni alla vista di scorrimento.

  • Utilizzare i vincoli per disporre le viste secondarie all'interno della vista di scorrimento, accertandosi che i vincoli si leghino a tutti e quattro i bordi della vista di scorrimento e non si basino sulla vista di scorrimento per ottenere le loro dimensioni.

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


10
Imho questa è la vera soluzione al mondo in cui tutto accade con il layout automatico. Per quanto riguarda le altre soluzioni: cosa ... sei serio nel calcolare l'altezza di ogni vista e talvolta la larghezza?
Fawkes,

Grazie per aver condiviso questo! Questa dovrebbe essere la risposta accettata.
SePröbläm,

2
Questo non funziona quando si desidera che l'altezza della vista di scorrimento sia basata sulla sua altezza del contenuto. (ad esempio un popup centrato nella schermata in cui non si desidera scorrere fino a una determinata quantità di contenuto).

Questo non risponde alla domanda
Igor Vasilev,

Ottima soluzione Tuttavia, aggiungerei un altro proiettile ... "Non limitare l'altezza di una vista pila figlio se dovesse crescere verticalmente. Basta bloccarla ai bordi della vista scorrimento."
Andrew Kirna,

35

Ho aggiunto questo alla risposta di Espuz e JCC. Utilizza la posizione y delle viste secondarie e non include le barre di scorrimento. Modifica Utilizza la parte inferiore della vista secondaria inferiore visibile.

+ (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
Meraviglioso! Proprio quello di cui avevo bisogno.
Richard,

2
Sono sorpreso che un metodo come questo non faccia parte della classe.
João Portela,

Perché l'hai fatto self.scrollView.showsHorizontalScrollIndicator = NO; self.scrollView.showsVerticalScrollIndicator = NO;all'inizio e self.scrollView.showsHorizontalScrollIndicator = YES; self.scrollView.showsVerticalScrollIndicator = YES;alla fine del metodo?
João Portela,

Grazie Richy :) +1 per la risposta.
Shraddha,

1
@richy hai ragione gli indicatori di scorrimento sono viste secondarie se visibili, ma mostrare / nascondere la logica è sbagliato. È necessario aggiungere due locali BOOL: restoreHorizontal = NO, restoreVertical = NO. Se mostra Orizzontale, nascondilo, imposta restoreHorizontal su SÌ. Lo stesso per verticale. Successivamente, dopo for / in loop inserisci l'istruzione if: se restoreHorizontal, imposta per mostrarlo, lo stesso per verticale. Il tuo codice forzerà solo a mostrare entrambi gli indicatori
clandestino-immigrato il

13

Ecco la risposta accettata in fretta per chiunque sia troppo pigro per convertirlo :)

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

e aggiornato per Swift3: var contentRect = CGRect.zero per la visualizzazione in self.scrollView.subviews {contentRect = contentRect.union (view.frame)} self.scrollView.contentSize = contentRect.size
drew ..

Questo deve essere chiamato sul thread principale? e in didLayoutSubViews?
Maksim Kniazev,

8

Ecco un adattamento di Swift 3 della risposta di @ leviatan:

ESTENSIONE

import UIKit


extension UIScrollView {

    func resizeScrollViewContentSize() {

        var contentRect = CGRect.zero

        for view in self.subviews {

            contentRect = contentRect.union(view.frame)

        }

        self.contentSize = contentRect.size

    }

}

USO

scrollView.resizeScrollViewContentSize()

Molto facile da usare!


Molto utile. Dopo così tante iterazioni ho trovato questa soluzione ed è perfetta.
Hardik Shah,

Bello! L'ho appena implementato.
Arvidurs,

7

La seguente estensione sarebbe utile in 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
    }
}

La logica è la stessa con le risposte fornite. Tuttavia, omette le viste nascoste all'interno UIScrollViewe il calcolo viene eseguito dopo che gli indicatori di scorrimento sono stati nascosti.

Inoltre, è disponibile un parametro di funzione opzionale e puoi aggiungere un valore di offset passando il parametro alla funzione.


Questa soluzione contiene troppi presupposti, perché stai mostrando gli indicatori di scorrimento in un metodo che imposta la dimensione del contenuto?
Kappe,

5

Ottima e migliore soluzione di @leviathan. Sto solo traducendo in rapido usando l'approccio FP (programmazione funzionale).

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

Se si desidera ignorare le visualizzazioni nascoste, è possibile filtrare le visualizzazioni secondarie prima di passare per ridurle.
Andy,

Aggiornato per Swift 3:self.contentSize = self.subviews.reduce(CGRect(), { $0.union($1.frame) }).size
John Rogers,

4

Puoi ottenere l'altezza del contenuto all'interno di UIScrollView calcolando quale bambino "raggiunge più lontano". Per calcolare questo devi considerare l'origine Y (inizio) e l'altezza dell'oggetto.

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

In questo modo gli oggetti (sottoview) non devono essere impilati direttamente uno sotto l'altro.


Puoi anche usare questa fodera all'interno del loop: maxHeight = MAX (maxHeight, child.frame.origin.y + child.frame.size.height) ;)
Thomas CG de Vilhena

3

Ho trovato un'altra soluzione basata sulla soluzione di @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)];

Fondamentalmente, scopriamo quale elemento è più in basso nella vista e aggiunge un'imbottitura di 10px al fondo


3

Poiché uno scrollView può avere altri scrollView o diversi alberi subView in profondità, è preferibile eseguire in modo ricorsivo approfondito. inserisci qui la descrizione dell'immagine

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

uso

scrollView.recalculateVerticalContentSize_synchronous()

2

O semplicemente:

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

(Questa soluzione è stata aggiunta da me come commento in questa pagina. Dopo aver ottenuto 19 voti positivi per questo commento, ho deciso di aggiungere questa soluzione come risposta formale a beneficio della comunità!)


2

Per swift4 usando ridurre:

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

Vale anche la pena verificare se almeno una sottoview ha origine == CGPoint.zero o semplicemente assegna l'origine di una subview particolare (più grande, ad esempio) a zero.
Paul B,

Questo funziona solo per le persone che posizionano le loro opinioni con CGRect (). Se si utilizzano vincoli, questo non funziona.
linus_hologram,

1

La dimensione dipende dal contenuto caricato al suo interno e dalle opzioni di ritaglio. Se si tratta di una visualizzazione di testo, dipende anche dall'involucro, dal numero di righe di testo, dalla dimensione del carattere e così via. Quasi impossibile per te calcolare te stesso. La buona notizia è che viene calcolata dopo il caricamento della vista e in viewWillAppear. Prima di ciò, è tutto sconosciuto e la dimensione del contenuto sarà uguale alla dimensione del frame. Tuttavia, nel metodo viewWillAppear e dopo (come viewDidAppear) le dimensioni del contenuto saranno effettive.


1

Avvolgendo il codice di Richy ho creato una classe UIScrollView personalizzata che automatizza completamente il ridimensionamento del contenuto!

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

Come usare:
È sufficiente importare il file .h sul controller della vista e dichiarare un'istanza SBScrollView invece di quella normale UIScrollView.


2
layoutSubviews sembra che sia il posto giusto per tale codice relativo al layout. Ma in un layout UIScrollView Subview viene chiamato ogni volta che l'offset del contenuto viene aggiornato durante lo scorrimento. E poiché le dimensioni del contenuto di solito non cambiano durante lo scorrimento, questo è piuttosto dispendioso.
Sven,

È vero. Un modo per evitare questa inefficienza potrebbe essere quello di controllare [self isDragging] e [self isDecelerating] ed eseguire il layout solo se entrambi sono falsi.
Greg Brown,

1

Imposta la dimensione del contenuto dinamico in questo modo.

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

0

dipende davvero dal contenuto: content.frame.height potrebbe darti quello che vuoi? Dipende se il contenuto è una cosa sola o una raccolta di cose.


0

Ho anche trovato la risposta del leviatano per funzionare al meglio. Tuttavia, stava calcolando una strana altezza. Durante il ciclo tra le visualizzazioni secondarie, se la visualizzazione a scorrimento è impostata per mostrare gli indicatori di scorrimento, questi saranno nella serie di visualizzazioni secondarie. In questo caso, la soluzione è disabilitare temporaneamente gli indicatori di scorrimento prima di eseguire il looping, quindi ristabilire le impostazioni di visibilità precedenti.

-(void)adjustContentSizeToFit è un metodo pubblico su una sottoclasse personalizzata di 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

Penso che questo possa essere un modo semplice per aggiornare le dimensioni della vista del contenuto di 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

perché non una singola riga di codice ??

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

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.