UIScrollView faire défiler vers le bas par programme

298

Comment faire UIScrollViewdéfiler vers le bas dans mon code? Ou d'une manière plus générique, à n'importe quel point d'une sous-vue?

Nico
la source

Réponses:

626

Vous pouvez utiliser la setContentOffset:animated:fonction UIScrollView pour faire défiler jusqu'à n'importe quelle partie de la vue de contenu. Voici un code qui défilerait vers le bas, en supposant que votre scrollView est 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];

J'espère que cela pourra aider!

Ben Gotow
la source
25
vous souhaiterez probablement que les éléments suivants défilent vers le bas, au lieu de sortir de la vue de défilement: CGPoint bottomOffset = CGPointMake (0, [self.scrollView contentSize] .height - self.scrollView.frame.size.height);
Grantland Chew
70
Vous ne tenez pas compte des encarts. Je pense que vous devriez préférer ce bas Offset:CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.contentInset.bottom);
Martin
1
cela ne fonctionne pas en mode paysage! pas complètement défiler vers le bas
Mo Farhand
1
Il ne fonctionnera pas correctement s'il contentSizeest inférieur à bounds. Il devrait donc en être ainsi:scrollView.setContentOffset(CGPointMake(0, max(scrollView.contentSize.height - scrollView.bounds.size.height, 0) ), animated: true)
Bartłomiej Semańczyk
1
Cela gère l'encart inférieur et va au-delà de 0:let bottomOffsetY = max(collectionView.contentSize.height - collectionView.bounds.height + collectionView.contentInset.bottom, 0)
Sensible
136

Version rapide de la réponse acceptée pour un copier-coller facile:

let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.size.height)
scrollView.setContentOffset(bottomOffset, animated: true)
Esqarrouth
la source
3
bottomOffset doit être laissé et non var dans votre exemple.
Hobbes le Tige
vrai, l'a changé. swift2 stuff :)
Esqarrouth
9
vous devez vérifier si scrollView.contentSize.height - scrollView.bounds.size.height> 0 sinon vous obtiendrez des résultats
bizarres
2
Cette solution n'est pas parfaite lorsque vous avez la nouvelle barre de navigation.
Swift Rabbit
@Josh, toujours dans l'attente du jour où SO l'apprend
Alexandre G
53

Solution la plus simple:

[scrollview scrollRectToVisible:CGRectMake(scrollview.contentSize.width - 1,scrollview.contentSize.height - 1, 1, 1) animated:YES];
Hai Hw
la source
Merci. J'ai oublié que j'ai changé mon scrollview en UITableView et ce code a également fonctionné pour UITableView, FYI.
LevinsonTechnologies
1
Pourquoi pas simplement: [scrollview scrollRectToVisible: CGRectMake (0, scrollview.contentSize.height, 1, 1) animé: YES];
Johannes
29

Juste une amélioration de la réponse existante.

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

Il prend également en charge l'encart inférieur (au cas où vous l'utiliseriez pour ajuster votre vue de défilement lorsque le clavier est visible)

vivek241
la source
Cela devrait être la bonne réponse ... pas les autres qui se briseront si jamais un clavier apparaît.
Ben Guild
20

Une implémentation rapide:

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

utilise le:

yourScrollview.scrollToBottom(animated: true)
duan
la source
19

La définition du décalage du contenu à la hauteur de la taille du contenu est incorrecte: il fait défiler le bas du contenu vers le haut de la vue de défilement, et donc hors de vue.

La bonne solution consiste à faire défiler le bas du contenu vers le bas de la vue de défilement, comme ceci ( svest le 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];
}
mat
la source
1
N'est-ce pas if (sv.contentOffset.y + csz.height > bsz.height) {?
i_am_jorf
@jeffamaphone - Merci d'avoir demandé. En fait, à ce stade, je ne sais même pas à quoi servait la condition! C'est la setContentOffset:commande qui est importante.
mat
Je suppose que c'est pour éviter l'appel setContentOffset s'il ne veut rien faire?
i_am_jorf
@jeffamaphone - Auparavant, après la rotation de l'appareil, une vue de défilement pouvait se terminer avec son contenu inférieur plus haut que le bas du cadre (car si nous faisons pivoter une vue de défilement, son décalage de contenu reste constant, mais la hauteur de la vue de défilement peut avoir augmenté en raison à redimensionnement automatique). La condition dit: Si cela se produit, remettez le contenu en bas en bas du cadre. Mais c'était stupide de ma part d'inclure la condition lorsque j'ai collé dans le code. La condition est de tester correctement pour ce qu'il a été testé, cependant.
mat
15

Une solution Swift 2.2, prenant contentInseten compte

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

Cela devrait être dans une extension

extension UIScrollView {

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

Notez que vous voudrez peut-être vérifier si bottomOffset.y > 0avant de faire défiler

onmyway133
la source
14

Et si contentSizeest inférieur àbounds ?

Pour Swift, c'est:

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

Faire défiler vers le haut

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

Faire défiler vers le bas

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

J'ai également trouvé un autre moyen utile de le faire dans le cas où vous utilisez un UITableview (qui est une sous-classe de UIScrollView):

[(UITableView *)self.view scrollToRowAtIndexPath:scrollIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
Nico
la source
Pour info, c'est une capacité UITableView uniquement
OhadM
7

Utilisation de la setContentOffset:animated:fonction UIScrollView pour faire défiler vers le bas dans Swift.

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

Il semble que toutes les réponses ici n'aient pas pris en compte la zone de sécurité. Depuis iOS 11, l'iPhone X a introduit une zone de sécurité. Cela peut affecter les scrollView contentInset.

Pour iOS 11 et supérieur, pour faire défiler correctement vers le bas avec l'encart de contenu inclus. Vous devez utiliser à la adjustedContentInsetplace de contentInset. Vérifiez ce code:

  • Rapide:
let bottomOffset = CGPoint(x: 0, y: scrollView.contentSize.height - scrollView.bounds.height + scrollView.adjustedContentInset.bottom)
scrollView.setContentOffset(bottomOffset, animated: true)
  • Objectif c
CGPoint bottomOffset = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height + self.scrollView.adjustedContentInset.bottom);
[self.scrollView setContentOffset:bottomOffset animated:YES];
  • Extension Swift (cela conserve l'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)
    }
}

Références:

Honghao Zhang
la source
5

Avec un (facultatif) footerViewet contentInset, la solution est:

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

Rapide:

Vous pouvez utiliser une extension comme celle-ci:

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

Utilisation:

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

valdyr, j'espère que cela vous aidera:

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

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

Catégorie à la rescousse!

Ajoutez ceci à un en-tête d'utilitaire partagé quelque part:

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

Et puis à cette implémentation de l'utilitaire:

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

Ensuite, implémentez-le où vous voulez, par exemple:

[[myWebView scrollView] scrollToBottomAnimated:YES];
BadPirate
la source
Toujours, toujours, toujours, utilisez toujours des catégories! Parfait :)
Fattie
2

Pour ScrollView horizontal

Si vous m'aimez a un ScrollView horizontal et que vous souhaitez faire défiler jusqu'à la fin (dans mon cas à l'extrême droite), vous devez modifier certaines parties de la réponse acceptée:

Objectif c

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

Rapide

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
la source
2

Si vous changez en quelque sorte scrollView contentSize (par exemple, ajoutez quelque chose à stackView qui se trouve à l'intérieur de scrollView), vous devez appelerscrollView.layoutIfNeeded() avant de faire défiler, sinon cela ne fait rien.

Exemple:

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
la source
1

Un bon moyen de s'assurer que le bas de votre contenu est visible est d'utiliser la formule:

contentOffsetY = MIN(0, contentHeight - boundsHeight)

Cela garantit que le bord inférieur de votre contenu est toujours au niveau ou au-dessus du bord inférieur de la vue. Le MIN(0, ...)est requis car UITableView(et probablement UIScrollView) garantit contentOffsetY >= 0que l'utilisateur essaie de faire défiler en claquant visiblementcontentOffsetY = 0 . Cela semble assez étrange pour l'utilisateur.

Le code pour implémenter ceci est:

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
la source
1

Si vous n'avez pas besoin d'animation, cela fonctionne:

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

Bien que la Mattsolution me semble correcte, vous devez également prendre en compte l'encart de la vue de collection s'il y en a un qui a été configuré.

Le code adapté sera:

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
la source
1

En bref:

   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
la source
1

Solution pour faire défiler jusqu'au dernier élément d'une table Voir:

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
la source
0

N'a pas travaillé pour moi, quand j'ai essayé de l' utiliser dans UITableViewControllerle self.tableView (iOS 4.1), après avoir ajoutéfooterView . Il défile hors des frontières, montrant un écran noir.

Solution alternative:

 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
la source
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
la source
0

J'ai trouvé que contentSizecela ne reflète pas vraiment la taille réelle du texte, donc lorsque vous essayez de faire défiler vers le bas, il sera un peu éteint. La meilleure façon de déterminer la taille du contenu réel est en fait d'utiliser la NSLayoutManagerde » usedRectForTextContainer:méthode:

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

Pour déterminer la quantité de texte réellement affichée dans le UITextView, vous pouvez le calculer en soustrayant les encarts du conteneur de texte de la hauteur du cadre.

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

Ensuite, il devient facile de faire défiler:

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

Quelques chiffres pour référence sur ce que les différentes tailles représentent pour un vide 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
la source
0

Étendez UIScrollView pour ajouter une méthode 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
la source
Qu'est-ce que cela ajoute aux réponses existantes?
greybeard
-1

Version Xamarin.iOS de la réponse acceptée

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

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

Version Xamarin.iOS pour UICollectionViewla réponse acceptée pour faciliter le copier-coller

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