web-dev-qa-db-fra.com

Change the speed of setContentOffset: animé:?

Existe-t-il un moyen de changer la vitesse de l'animation lors du défilement d'une UITableView à l'aide de setContentOffset: animated :? Je veux faire défiler vers le haut, mais lentement. Lorsque j'essaie ce qui suit, les quelques cellules du bas disparaissent avant le début de l'animation (en particulier celles qui ne seront pas visibles lorsque le défilement est terminé):

[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:3.0];
[self.tableView setContentOffset:CGPointMake(0, 0)];
[UIView commitAnimations];

Tout autre moyen de contourner ce problème? Il existe une méthode privée _setContentOffsetAnimationDuration qui fonctionne, mais je ne veux pas être rejeté de l'App Store.

36
Jordan Kay

Il n'y a pas de moyen direct de faire cela, ni de faire ce que vous avez écrit. La seule façon pour moi d’y parvenir est de créer le mouvement/l’animation moi-même.

Par exemple, déplacez 1 pixel toutes les dix secondes pour simuler une animation de défilement très lente. (Puisque c'est une animation linéaire, les maths sont très faciles!)

Si vous voulez avoir plus de réalisme ou de fantaisie et simuler un effet easy-in easy-off, vous avez besoin de quelques calculs pour calculer le chemin de Bézier afin de connaître la position exacte toutes les 1/10 de seconde, par exemple

Au moins, la première approche ne devrait pas être si difficile. Il suffit d'utiliser ou -performSelector:withObject:afterDelay or NSTimerswith 

-[UIScrollView setContentOffset:(CGPoint*)];`

J'espère que ça aide

11
nacho4d
[UIView animateWithDuration:2.0 animations:^{
    scrollView.contentOffset = CGPointMake(x, y);
}];

Ça marche.

90
TK189

J'ai pris en compte la réponse de nacho4d et implémenté le code. J'ai donc pensé qu'il serait utile que les autres utilisateurs de cette question voient le code qui fonctionne:

J'ai ajouté des variables de membre à ma classe:

CGPoint startOffset;
CGPoint destinationOffset;
NSDate *startTime;

NSTimer *timer;

et propriétés:

@property (nonatomic, retain) NSDate *startTime;
@property (nonatomic, retain) NSTimer *timer;

et un rappel de la minuterie:

- (void) animateScroll:(NSTimer *)timerParam
{
    const NSTimeInterval duration = 0.2;

    NSTimeInterval timeRunning = -[startTime timeIntervalSinceNow];

    if (timeRunning >= duration)
    {
        [self setContentOffset:destinationOffset animated:NO];
        [timer invalidate];
        timer = nil;
        return;
    }
    CGPoint offset = [self contentOffset];

    offset.x = startOffset.x +
        (destinationOffset.x - startOffset.x) * timeRunning / duration;

    [self setContentOffset:offset animated:NO];
}

puis:

- (void) doAnimatedScrollTo:(CGPoint)offset
{
    self.startTime = [NSDate date];
    startOffset = self.contentOffset;
    destinationOffset = offset;

    if (!timer)
    {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:0.01
                                                      target:self
                                                    selector:@selector(animateScroll:)
                                                    userInfo:nil
                                                     repeats:YES];
    }
}

vous auriez également besoin d'un nettoyage de la minuterie dans la méthode dealloc. Etant donné que le minuteur conservera une référence à la cible (self) et que self aura une référence au minuteur, un code de nettoyage pour annuler/détruire le minuteur dans viewWillDisappear sera probablement une bonne idée également.

Tous les commentaires sur ce qui précède ou des suggestions d'amélioration seraient les bienvenus, mais cela fonctionne très bien avec moi et résout d'autres problèmes que j'avais avec setContentOffset: animated :.

14
JosephH

La définition du décalage de contenu directement ne fonctionnait pas pour moi. Cependant, encapsuler setContentOffset(offset, animated: false) dans un bloc d’animation a fait l'affaire.

UIView.animate(withDuration: 0.5, animations: {
                self.tableView.setContentOffset(
               CGPoint(x: 0, y: yOffset), animated: false)
            })
7
astro4

Je suis curieux de savoir si vous avez trouvé une solution à votre problème. Ma première idée était d'utiliser un appel animateWithDuration:animations: avec un bloc définissant le contentOffset:

[UIView animateWithDuration:2.0 animations:^{
    scrollView.contentOffset = CGPointMake(x, y);
}];

Effets secondaires

Bien que cela fonctionne pour des exemples simples, il a également des effets secondaires très indésirables. Contrairement à setContentOffset:animated:, tout ce que vous faites dans les méthodes de délégation est également animé, comme la méthode de délégation scrollViewDidScroll:.

Je fais défiler une vue de défilement en mosaïque avec des mosaïques réutilisables. Ceci est vérifié dans le scrollViewDidScroll:. Lorsqu'ils sont réutilisés, ils obtiennent une nouvelle position dans la vue de défilement, mais ils s'animent, de sorte que des tuiles sont animées tout au long de la vue. Ça a l'air cool, mais totalement inutile. Un autre effet secondaire indésirable est que le test de frappe des tuiles et des limites de ma vue de défilement est instantanément rendu inutile, car contentOffset est déjà à une nouvelle position dès que le bloc d’animation s’exécute. Cela fait apparaître et disparaître des éléments tant qu'ils sont encore visibles, par rapport à l'endroit où ils étaient basculés juste en dehors des limites de la vue de défilement.

Avec setContentOffset:animated:, ce n'est pas le cas. On dirait que UIScrollView n'utilise pas la même technique en interne.

Quelqu'un a-t-il une autre suggestion à faire pour modifier la vitesse/durée de l'exécution de UIScrollView setContentOffset:animated:?

5
epologee

https://github.com/dominikhofmann/PRTween

sous-classe UITableview 

#import "PRTween.h"


@interface JPTableView : UITableView{
  PRTweenOperation *activeTweenOperation;
}



- (void) doAnimatedScrollTo:(CGPoint)destinationOffset
{
    CGPoint offset = [self contentOffset];

    activeTweenOperation = [PRTweenCGPointLerp lerp:self property:@"contentOffset" from:offset to:destinationOffset duration:1.5];


}
2
johndpope

Si tout ce que vous essayez de faire est de faire défiler votre scrollview, je pense que vous devriez utiliser scroll recto vers visible. Je viens d'essayer ce code

 [UIView animateWithDuration:.7
                      delay:0
                    options:UIViewAnimationOptionCurveEaseOut
                 animations:^{  
                     CGRect scrollToFrame = CGRectMake(0, slide.frame.Origin.y, slide.frame.size.width, slide.frame.size.height + kPaddingFromTop*2);
                     CGRect visibleFrame = CGRectMake(0, scrollView.contentOffset.y,
                                                      scrollView.frame.size.width, scrollView.frame.size.height);
                     if(!CGRectContainsRect(visibleFrame, slide.frame))
                         [self.scrollView scrollRectToVisible:scrollToFrame animated:FALSE];}];

et il fait défiler la vue de défilement vers l'emplacement dont j'ai besoin, quelle que soit la durée pour laquelle je le configure. La clé est de définir animate sur false. Lorsqu'elle était définie sur true, la vitesse d'animation était la valeur par défaut définie par la méthode.

1
Esko918

Pour les personnes qui ont également des problèmes avec la disparition d’éléments lors du défilement d’un UITableView ou d’un UICollectionView, vous pouvez développer l’affichage lui-même afin que nous puissions contenir davantage d’éléments visibles. Cette solution n'est pas recommandée pour les situations dans lesquelles vous devez faire défiler une grande distance ou dans lesquelles l'utilisateur peut annuler l'animation. Dans l'application sur laquelle je travaille actuellement, je n'avais besoin que de laisser la vue défiler à une résolution de 100 pixels.

NSInteger scrollTo = 100;

CGRect frame = self.collectionView.frame;
frame.size.height += scrollTo;
[self.collectionView setFrame:frame];

[UIView animateWithDuration:0.8 delay:0.0 options:(UIViewAnimationOptionCurveEaseIn) animations:^{
    [self.collectionView setContentOffset:CGPointMake(0, scrollTo)];
} completion:^(BOOL finished) {
    [UIView animateWithDuration:0.8 delay:0.0 options:(UIViewAnimationOptionCurveEaseIn) animations:^{
        [self.collectionView setContentOffset:CGPointMake(0, 0)];

    } completion:^(BOOL finished) {
        CGRect frame = self.collectionView.frame;
        frame.size.height -= scrollTo;
        [self.collectionView setFrame:frame];
    }];
}];
0
Ben Groot

J'utilise transitionWithView:duration:options:animations:completion:

        [UIView transitionWithView:scrollView duration:3 options:(UIViewAnimationOptionCurveLinear) animations:^{
        transitionWithView:scrollView.contentOffset = CGPointMake(contentOffsetWidth, 0);
    } completion:nil];

UIViewAnimationOptionCurveLinear est une option permettant d’animer uniformément l’animation.

Bien que j'ai trouvé cela dans une durée d'animation, la méthode déléguée scrollViewDidScroll n'a pas appelé jusqu'à la fin de l'animation.

0
FFur