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?

nico
fuente

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!

Ben Gotow
fuente
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)
Esqarrouth
fuente
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];
Hai Hw
fuente
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)

vivek241
fuente
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)
duan
fuente
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];
}
mate
fuente
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

onmyway133
fuente
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)
Bartłomiej Semańczyk
fuente
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];
Tahir Waseem
fuente
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];
nico
fuente
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)
Kim
fuente
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

Honghao Zhang
fuente
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];
Pieter
fuente
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)
Mauro García
fuente
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];
Misha
fuente
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];
BadPirate
fuente
¡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)
Esmaeil
fuente
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)
}
Robert Koval
fuente
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];
}
jjrscott
fuente
1

Si no necesita animación, esto funciona:

[self.scrollView setContentOffset:CGPointMake(0, CGFLOAT_MAX) animated:NO];
Krys Jurgowski
fuente
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];
}
tiguero
fuente
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)
    }
Ponja
fuente
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)
}
MarionFlex
fuente
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];
valdyr
fuente
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];
8suhas
fuente
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)
mikeho
fuente
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)
        }
    }
}
jeune-loup
fuente
¿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);
Abdur
fuente
-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);
Abdur
fuente