UIScrollView role para baixo programaticamente

298

Como posso fazer uma UIScrollViewrolagem para a parte inferior do meu código? Ou de uma maneira mais genérica, para qualquer ponto de uma subvisão?

nico
fonte

Respostas:

626

Você pode usar a setContentOffset:animated:função do UIScrollView para rolar para qualquer parte da exibição do conteúdo. Aqui está um código que rolaria para a parte inferior, supondo que seu scrollView seja 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 ajude!

Ben Gotow
fonte
25
você provavelmente deseja o seguinte para rolar para a parte inferior, em vez de sair da visualização de rolagem: CGPoint bottomOffset = CGPointMake (0, [self.scrollView contentSize] .height - self.scrollView.frame.size.height);
Grantland Chew
70
Você não está levando em consideração as inserções. Eu acho que você deve preferir este fundoOffset:CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.contentInset.bottom);
Martin
1
isso não funciona no modo paisagem! não rolar completamente para baixo
Mo Farhand
1
Não funcionará corretamente se contentSizefor menor que bounds. Por isso deve ser assim:scrollView.setContentOffset(CGPointMake(0, max(scrollView.contentSize.height - scrollView.bounds.size.height, 0) ), animated: true)
Bartłomiej Semańczyk
1
Este lida com baixo-relevo de fundo, e indo além 0:let bottomOffsetY = max(collectionView.contentSize.height - collectionView.bounds.height + collectionView.contentInset.bottom, 0)
Senseful
136

Versão rápida da resposta aceita para facilitar a colagem de cópias:

let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height)
scrollView.setContentOffset(bottomOffset, animated: true)
Esqarrouth
fonte
3
bottomOffset deve ser permitido e não var no seu exemplo.
Hobbes the Tige
verdade, mudou. coisas swift2 :)
Esqarrouth
9
você precisa verificar se scrollView.contentSize.height - scrollView.bounds.size.height> 0 caso contrário você terá resultados estranhas
iluvatar_GR
2
Esta solução não é perfeita quando você possui a nova barra de navegação.
Swift Coelho
@ Josh, Ainda esperando o dia em que descobre sobre isso #
Alexandre G
53

Solução mais simples:

[scrollview scrollRectToVisible:CGRectMake(scrollview.contentSize.width - 1,scrollview.contentSize.height - 1, 1, 1) animated:YES];
Hai Hw
fonte
Obrigado. Esqueci que alterei meu scrollview para um UITableView e esse código também funcionou para um UITableView, FYI.
LevinsonTechnologies
1
Por que não apenas: [scrollview scrollRectToVisible: CGRectMake (0, scrollview.contentSize.height, 1, 1) animado: SIM];
Johannes
29

Apenas um aprimoramento para a resposta existente.

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

Ele também cuida da parte inferior inferior (caso você esteja usando isso para ajustar a visualização de rolagem quando o teclado estiver visível)

vivek241
fonte
Essa deve ser a resposta correta ... não as outras que quebram se um teclado aparecer.
Ben Aliança
20

Uma implementação 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)
  }
}

use-o:

yourScrollview.scrollToBottom(animated: true)
duan
fonte
19

Definir o deslocamento do conteúdo para a altura do tamanho do conteúdo está errado: ele rola a parte inferior do conteúdo para o topo da visualização de rolagem e, portanto, fora de vista.

A solução correta é rolar a parte inferior do conteúdo até a parte inferior da visualização de rolagem, desta forma ( své o 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
fonte
1
Não deveria ser if (sv.contentOffset.y + csz.height > bsz.height) {?
I_am_jorf
@jeffamaphone - Obrigado por perguntar. Na verdade, neste momento, eu nem tenho certeza de qual propósito a condição deveria servir! É o setContentOffset:comando que é importante.
Matt
Presumo que seja para evitar a chamada setContentOffset se não for fazer nada?
I_am_jorf
@jeffamaphone - Antes da rotação do dispositivo, uma exibição de rolagem podia terminar com o conteúdo inferior à parte inferior do quadro (porque, se girarmos uma exibição de rolagem, seu deslocamento de conteúdo permanece constante, mas a altura da exibição de rolagem pode ter aumentado devido para autoresizing). A condição diz: Se isso acontecer, coloque o conteúdo novamente na parte inferior do quadro. Mas foi tolice da minha parte incluir a condição quando colei no código. A condição está testando corretamente para o que estava testando.
Matt
15

Uma solução Swift 2.2, levando contentInsetem consideração

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

Isso deve estar em uma extensão

extension UIScrollView {

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

Observe que você pode verificar se bottomOffset.y > 0antes de rolar

onmyway133
fonte
14

E se contentSizefor menor que bounds?

Para Swift é:

scrollView.setContentOffset(CGPointMake(0, max(scrollView.contentSize.height - scrollView.bounds.size.height, 0) ), animated: true)
Bartłomiej Semańczyk
fonte
13

Role para cima

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

Role para baixo

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

Eu também encontrei outra maneira útil de fazer isso no caso de você estar usando uma UITableview (que é uma subclasse de UIScrollView):

[(UITableView *)self.view scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
nico
fonte
FYI, essa é uma capacidade UITableView única
OhadM
7

Usando a setContentOffset:animated:função do UIScrollView para rolar para baixo no Swift.

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

Parece que todas as respostas aqui não levaram em consideração a área segura. Desde o iOS 11, o iPhone X apresentou uma área segura. Isso pode afetar o scrollView contentInset.

Para iOS 11 e superior, role a página corretamente para baixo com o conteúdo incluído. Você deve usar em adjustedContentInsetvez de contentInset. Verifique 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)
  • Objetivo-C
CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.adjustedContentInset.bottom);
[self.scrollView setContentOffset:bottomOffset animated:YES];
  • Extensão Swift (mantém o 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)
    }
}

Referências:

Honghao Zhang
fonte
5

Com um (opcional) footerViewe contentInset, a solução é:

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

Rápido:

Você pode usar uma extensão como esta:

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

Usar:

scrollView.scrollsToBottom(animated: true)
Mauro García
fonte
3

valdyr, espero que isso ajude você:

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

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

Categoria para o resgate!

Adicione isso a um cabeçalho de utilitário compartilhado em algum lugar:

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

E então para a implementação desse utilitário:

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

Em seguida, implemente-o onde quiser, por exemplo:

[[myWebView scrollView] scrollToBottomAnimated:YES];
BadPirate
fonte
Sempre, sempre, sempre, sempre use categorias! Perfeito :)
Fattie
2

Para ScrollView horizontal

Se você gosta de mim tem um ScrollView horizontal e deseja rolar até o final (no meu caso, à direita), é necessário alterar algumas partes da resposta aceita:

Objetivo-C

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
fonte
2

Se você alterar de alguma forma o scrollView contentSize (por exemplo, adicionar algo ao stackView que está dentro do scrollView), você deve chamarscrollView.layoutIfNeeded() antes de rolar, caso contrário, não fará nada.

Exemplo:

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
fonte
1

Uma boa maneira de garantir que a parte inferior do seu conteúdo fique visível é usar a fórmula:

contentOffsetY = MIN(0, contentHeight - boundsHeight)

Isso garante que a borda inferior do seu conteúdo esteja sempre igual ou acima da borda inferior da visualização. Isso MIN(0, ...)é necessário porque UITableView(e provavelmente UIScrollView) garante contentOffsetY >= 0quando o usuário tenta rolar visivelmente contentOffsetY = 0. Isso parece muito estranho para o usuário.

O código para implementar isso é:

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
fonte
1

Se você não precisa de animação, isso funciona:

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

Embora a Mattsolução me pareça correta, é necessário levar em conta também a exibição da coleção, se houver uma que foi configurada.

O 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
fonte
1

Em rápida:

   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
fonte
1

Solução para rolar para o último item de uma tabela

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
fonte
0

Não funcionou para mim, quando eu tentei usá-lo em UITableViewControlleron self.tableView (iOS 4.1), após a adição footerView. Rola para fora das bordas, mostrando a tela preta.

Solução 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
fonte
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
fonte
0

Descobri que contentSizeisso realmente não reflete o tamanho real do texto; portanto, ao tentar rolar para o final, ficará um pouco fora. A melhor maneira de determinar o tamanho real conteúdo é realmente usar o NSLayoutManager's usedRectForTextContainer:método:

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

Para determinar quanto texto realmente é mostrado no UITextView, você pode calculá-lo subtraindo as inserções do contêiner de texto da altura do quadro.

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

Então fica fácil rolar:

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

Alguns números para referência sobre o que os diferentes tamanhos representam para um vazio 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
fonte
0

Estenda o UIScrollView para adicionar um 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
fonte
O que isso acrescenta às respostas existentes?
greybeard 26/04
-1

Versão Xamarin.iOS da resposta aceita

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

scrollView.SetContentOffset (bottomOffset, false);
Abdur
fonte
-1

Versão Xamarin.iOSUICollectionView da resposta aceita para facilitar a cópia e a colagem

var bottomOffset = new CGPoint (0, CollectionView.ContentSize.Height - CollectionView.Frame.Size.Height + CollectionView.ContentInset.Bottom);          
CollectionView.SetContentOffset (bottomOffset, false);
Abdur
fonte