UIScrollView desplazarse hacia abajo mediante programación


298

¿Cómo puedo hacer un UIScrollViewdesplazamiento hacia abajo en mi código? ¿O de una manera más genérica, a cualquier punto de una subvista?

Respuestas:


626

Puede usar la setContentOffset:animated:función UIScrollView para desplazarse a cualquier parte de la vista de contenido. Aquí hay un código que se desplazaría hacia abajo, suponiendo que su scrollView sea self.scrollView:

CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.contentInset.bottom);
[self.scrollView setContentOffset:bottomOffset animated:YES];

¡Espero que ayude!


25
probablemente desee lo siguiente para desplazarse hacia abajo, en lugar de salir de la vista de desplazamiento: CGPoint bottomOffset = CGPointMake (0, [self.scrollView contentSize] .height - self.scrollView.frame.size.height);
Grantland Chew

70
No estás teniendo en cuenta las inserciones. Creo que debería preferir este bottom Offset:CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.contentInset.bottom);
Martin

1
esto no funciona en modo horizontal! no desplazarse completamente hacia abajo
Mo Farhand

1
No funcionará correctamente si contentSizees inferior a bounds. Entonces debería ser así:scrollView.setContentOffset(CGPointMake(0, max(scrollView.contentSize.height - scrollView.bounds.size.height, 0) ), animated: true)
Bartłomiej Semańczyk

1
Esto maneja la inserción inferior y va más allá de 0:let bottomOffsetY = max(collectionView.contentSize.height - collectionView.bounds.height + collectionView.contentInset.bottom, 0)
Senseful

136

Versión rápida de la respuesta aceptada para copiar y pegar fácilmente:

let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height)
scrollView.setContentOffset(bottomOffset, animated: true)

3
bottomOffset debe dejarse y no var en su ejemplo.
Hobbes the Tige

Es cierto, lo cambió. swift2 stuff :)
Esqarrouth

99
necesita verificar si scrollView.contentSize.height - scrollView.bounds.size.height> 0 de lo contrario tendrá resultados extraños
iluvatar_GR

2
Esta solución no es perfecta cuando tienes la nueva barra de navegación.
Swift Rabbit

@ Josh, sigo esperando el día para que SO se entere de esto
Alexandre G el

53

La solución más simple:

[scrollview scrollRectToVisible:CGRectMake(scrollview.contentSize.width - 1,scrollview.contentSize.height - 1, 1, 1) animated:YES];

Gracias. Olvidé que cambié mi vista de desplazamiento a un UITableView y este código también funcionó para un UITableView, para su información.
LevinsonTechnologies

1
¿Por qué no simplemente: [scrollview scrollRectToVisible: CGRectMake (0, scrollview.contentSize.height, 1, 1) animado: SÍ];
Johannes

29

Solo una mejora a la respuesta existente.

CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.contentInset.bottom);
[self.scrollView setContentOffset:bottomOffset animated:YES];

También se encarga del recuadro inferior (en caso de que lo esté usando para ajustar su vista de desplazamiento cuando el teclado está visible)


Esta debería ser la respuesta correcta ... no las otras que se romperán si alguna vez tienen un teclado emergente.
Ben Guild

20

Una implementación rápida:

extension UIScrollView {
   func scrollToBottom(animated: Bool) {
     if self.contentSize.height < self.bounds.size.height { return }
     let bottomOffset = CGPoint(x: 0, y: self.contentSize.height - self.bounds.size.height)
     self.setContentOffset(bottomOffset, animated: animated)
  }
}

úsalo:

yourScrollview.scrollToBottom(animated: true)

19

Establecer el desplazamiento del contenido a la altura del tamaño del contenido es incorrecto: desplaza la parte inferior del contenido a la parte superior de la vista de desplazamiento y, por lo tanto, no se ve.

La solución correcta es desplazar la parte inferior del contenido hacia la parte inferior de la vista de desplazamiento, de esta manera ( sves el UIScrollView):

CGSize csz = sv.contentSize;
CGSize bsz = sv.bounds.size;
if (sv.contentOffset.y + bsz.height > csz.height) {
    [sv setContentOffset:CGPointMake(sv.contentOffset.x, 
                                     csz.height - bsz.height) 
                animated:YES];
}

1
¿No debería ser if (sv.contentOffset.y + csz.height > bsz.height) {?
i_am_jorf

@jeffamaphone - Gracias por preguntar. En realidad, en este punto, ¡ni siquiera estoy seguro de qué propósito se suponía que debía cumplir la condición! Es el setContentOffset:comando lo que importa.
mate

Supongo que es para evitar la llamada setContentOffset si no va a hacer nada?
i_am_jorf

@jeffamaphone: solía ser que después de la rotación del dispositivo, una vista de desplazamiento podría terminar con su parte inferior de contenido más alta que la parte inferior del marco (porque si giramos una vista de desplazamiento, su desplazamiento de contenido permanece constante, pero la altura de la vista de desplazamiento puede haber aumentado debido a autoresizing). La condición dice: Si eso sucede, vuelva a colocar el contenido en la parte inferior del marco. Pero fue una tontería por mi parte incluir la condición cuando pegué el código. Sin embargo, la condición está probando correctamente lo que estaba probando.
mate

15

Una solución Swift 2.2, teniendo contentInseten cuenta

let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.bottom)
scrollView.setContentOffset(bottomOffset, animated: true)

Esto debería estar en una extensión

extension UIScrollView {

  func scrollToBottom() {
    let bottomOffset = CGPoint(x: 0, y: contentSize.height - bounds.size.height + contentInset.bottom)
    setContentOffset(bottomOffset, animated: true)
  }
}

Tenga en cuenta que es posible que desee comprobar si bottomOffset.y > 0antes de desplazarse


14

¿Qué pasa si contentSizees menor que bounds?

Para Swift es:

scrollView.setContentOffset(CGPointMake(0, max(scrollView.contentSize.height - scrollView.bounds.size.height, 0) ), animated: true)

13

Vuelve al comienzo

- CGPoint topOffset = CGPointMake(0, 0);
- [scrollView setContentOffset:topOffset animated:YES];

Desplazarse hacia abajo

- CGPoint bottomOffset = CGPointMake(0, scrollView.contentSize.height - self.scrollView.bounds.size.height);
 - [scrollView setContentOffset:bottomOffset animated:YES];

7

También encontré otra forma útil de hacerlo en el caso de que esté utilizando una UITableview (que es una subclase de UIScrollView):

[(UITableView *)self.view scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

FYI, esa es solo una capacidad
UITableView

7

Usando la setContentOffset:animated:función UIScrollView para desplazarse hacia abajo en Swift.

let bottomOffset : CGPoint = CGPointMake(0, scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.bottom)
scrollView.setContentOffset(bottomOffset, animated: true)

5

Parece que todas las respuestas aquí no tomaron en cuenta el área segura. Desde iOS 11, iPhone X introdujo un área segura. Esto puede afectar los scrollView's contentInset.

Para iOS 11 y superior, para desplazarse correctamente hasta la parte inferior con el contenido incluido. Deberías usar en adjustedContentInsetlugar de contentInset. Comprueba este código:

  • Rápido:
let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.height + scrollView.adjustedContentInset.bottom)
scrollView.setContentOffset(bottomOffset, animated: true)
  • C objetivo
CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.adjustedContentInset.bottom);
[self.scrollView setContentOffset:bottomOffset animated:YES];
  • Extensión rápida (esto mantiene el original contentOffset.x):
extension UIScrollView {
    func scrollsToBottom(animated: Bool) {
        let bottomOffset = CGPoint(x: contentOffset.x,
                                   y: contentSize.height - bounds.height + adjustedContentInset.bottom)
        setContentOffset(bottomOffset, animated: animated)
    }
}

Referencias


5

Con un (opcional) footerViewy contentInset, la solución es:

CGPoint bottomOffset = CGPointMake(0, _tableView.contentSize.height - tableView.frame.size.height + _tableView.contentInset.bottom);
if (bottomOffset.y > 0) [_tableView setContentOffset: bottomOffset animated: YES];

4

Rápido:

Podrías usar una extensión como esta:

extension UIScrollView {
    func scrollsToBottom(animated: Bool) {
        let bottomOffset = CGPoint(x: 0, y: contentSize.height - bounds.size.height)
        setContentOffset(bottomOffset, animated: animated)
    }
}

Utilizar:

scrollView.scrollsToBottom(animated: true)

3

valdyr, espero que esto te ayude:

CGPoint bottomOffset = CGPointMake(0, [textView contentSize].height - textView.frame.size.height);

if (bottomOffset.y > 0)
 [textView setContentOffset: bottomOffset animated: YES];

2

Categoría al rescate!

Agregue esto a un encabezado de utilidad compartida en alguna parte:

@interface UIScrollView (ScrollToBottom)
- (void)scrollToBottomAnimated:(BOOL)animated;
@end

Y luego a esa implementación de utilidad:

@implementation UIScrollView(ScrollToBottom)
- (void)scrollToBottomAnimated:(BOOL)animated
{
     CGPoint bottomOffset = CGPointMake(0, self.contentSize.height - self.bounds.size.height);
     [self setContentOffset:bottomOffset animated:animated];
}
@end

Luego impleméntelo donde quiera, por ejemplo:

[[myWebView scrollView] scrollToBottomAnimated:YES];

¡Siempre, siempre, siempre, siempre use categorías! Perfecto :)
Fattie

2

Para vista de desplazamiento horizontal

Si me gusta tiene una vista de desplazamiento horizontal y desea desplazarse hasta el final (en mi caso, a la derecha), debe cambiar algunas partes de la respuesta aceptada:

C objetivo

CGPoint rightOffset = CGPointMake(self.scrollView.contentSize.width - self.scrollView.bounds.size.width + self.scrollView.contentInset.right, 0 );
[self.scrollView setContentOffset:rightOffset animated:YES];

Rápido

let rightOffset: CGPoint = CGPoint(x: self.scrollView.contentSize.width - self.scrollView.bounds.size.width + self.scrollView.contentInset.right, y: 0)
self.scrollView.setContentOffset(rightOffset, animated: true)

2

Si de alguna forma cambiar ScrollView contentSize (ej. Añadir algo a stackView que está dentro ScrollView) debe llamar scrollView.layoutIfNeeded()antes de desplazarse, de lo contrario, no hace nada.

Ejemplo:

scrollView.layoutIfNeeded()
let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height + scrollView.contentInset.bottom)
if(bottomOffset.y > 0) {
    scrollView.setContentOffset(bottomOffset, animated: true)
}

1

Una buena manera de asegurarse de que la parte inferior de su contenido sea visible es usar la fórmula:

contentOffsetY = MIN(0, contentHeight - boundsHeight)

Esto garantiza que el borde inferior de su contenido esté siempre en o por encima del borde inferior de la vista. Se MIN(0, ...)requiere porque UITableView(y probablemente UIScrollView) asegura contentOffsetY >= 0cuando el usuario intenta desplazarse haciendo un snap visible contentOffsetY = 0. Esto parece bastante extraño para el usuario.

El código para implementar esto es:

UIScrollView scrollView = ...;
CGSize contentSize = scrollView.contentSize;
CGSize boundsSize = scrollView.bounds.size;
if (contentSize.height > boundsSize.height)
{
    CGPoint contentOffset = scrollView.contentOffset;
    contentOffset.y = contentSize.height - boundsSize.height;
    [scrollView setContentOffset:contentOffset animated:YES];
}

1

Si no necesita animación, esto funciona:

[self.scrollView setContentOffset:CGPointMake(0, CGFLOAT_MAX) animated:NO];

1

Si bien la Mattsolución me parece correcta, debe tener en cuenta también la inserción de la vista de recopilación si hay una que se ha configurado.

El código adaptado será:

CGSize csz = sv.contentSize;
CGSize bsz = sv.bounds.size;
NSInteger bottomInset = sv.contentInset.bottom;
if (sv.contentOffset.y + bsz.height + bottomInset > csz.height) {
    [sv setContentOffset:CGPointMake(sv.contentOffset.x, 
                                     csz.height - bsz.height + bottomInset) 
                animated:YES];
}

1

En rápido:

   if self.mainScroll.contentSize.height > self.mainScroll.bounds.size.height {
        let bottomOffset = CGPointMake(0, self.mainScroll.contentSize.height - self.mainScroll.bounds.size.height);
        self.mainScroll.setContentOffset(bottomOffset, animated: true)
    }

1

Solución para desplazarse al último elemento de una vista de tabla:

Swift 3:

if self.items.count > 0 {
        self.tableView.scrollToRow(at:  IndexPath.init(row: self.items.count - 1, section: 0), at: UITableViewScrollPosition.bottom, animated: true)
}

0

No funcionó para mí, cuando traté de usarlo en UITableViewControllerel self.tableView (iOS 4.1), después de añadir footerView. Se desplaza fuera de los bordes, mostrando la pantalla en negro.

Solución alternativa:

 CGFloat height = self.tableView.contentSize.height; 

 [self.tableView setTableFooterView: myFooterView];
 [self.tableView reloadData];

 CGFloat delta = self.tableView.contentSize.height - height;
 CGPoint offset = [self.tableView contentOffset];
 offset.y += delta;

 [self.tableView setContentOffset: offset animated: YES];

0
CGFloat yOffset = scrollView.contentOffset.y;

CGFloat height = scrollView.frame.size.height;

CGFloat contentHeight = scrollView.contentSize.height;

CGFloat distance = (contentHeight  - height) - yOffset;

if(distance < 0)
{
    return ;
}

CGPoint offset = scrollView.contentOffset;

offset.y += distance;

[scrollView setContentOffset:offset animated:YES];

0

Descubrí que en contentSizerealidad no refleja el tamaño real del texto, por lo que cuando intente desplazarse hacia la parte inferior, estará un poco apagado. La mejor manera de determinar el tamaño real del contenido es usar el método de NSLayoutManagers usedRectForTextContainer::

UITextView *textView;
CGSize textSize = [textView.layoutManager usedRectForTextContainer:textView.textContainer].size;

Para determinar cuánto texto se muestra realmente en el UITextView, puede calcularlo restando las inserciones del contenedor de texto de la altura del marco.

UITextView *textView;
UIEdgeInsets textInsets = textView.textContainerInset;
CGFloat textViewHeight = textView.frame.size.height - textInsets.top - textInsets.bottom;

Entonces se vuelve fácil desplazarse:

// if you want scroll animation, use contentOffset
UITextView *textView;
textView.contentOffset = CGPointMake(textView.contentOffset.x, textSize - textViewHeight);

// if you don't want scroll animation
CGRect scrollBounds = textView.bounds;
scrollBounds.origin = CGPointMake(textView.contentOffset.x, textSize - textViewHeight);
textView.bounds = scrollBounds;

Algunos números de referencia sobre lo que representan los diferentes tamaños para un vacío UITextView.

textView.frame.size = (width=246, height=50)
textSize = (width=10, height=16.701999999999998)
textView.contentSize = (width=246, height=33)
textView.textContainerInset = (top=8, left=0, bottom=8, right=0)

0

Extienda UIScrollView para agregar un método scrollToBottom:

extension UIScrollView {
    func scrollToBottom(animated:Bool) {
        let offset = self.contentSize.height - self.visibleSize.height
        if offset > self.contentOffset.y {
            self.setContentOffset(CGPoint(x: 0, y: offset), animated: animated)
        }
    }
}

¿Qué agrega esto a las respuestas existentes?
barba gris

-1

Xamarin.iOS versión de respuesta aceptada

var bottomOffset = new CGPoint (0,
     scrollView.ContentSize.Height - scrollView.Frame.Size.Height
     + scrollView.ContentInset.Bottom);

scrollView.SetContentOffset (bottomOffset, false);

-1

Versión Xamarin.iOSUICollectionView de la respuesta aceptada para facilitar la copia y el pegado

var bottomOffset = new CGPoint (0, CollectionView.ContentSize.Height - CollectionView.Frame.Size.Height + CollectionView.ContentInset.Bottom);          
CollectionView.SetContentOffset (bottomOffset, false);
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.