web-dev-qa-db-fra.com

UIScrollView: pagination horizontale, défilement vertical?

Comment puis-je forcer une UIScrollView dans laquelle la pagination et le défilement sont activés pour se déplacer uniquement verticalement ou horizontalement à un moment donné?

Si j'ai bien compris, la propriété directionalLockEnabled devrait permettre cela, mais un balayage en diagonale fait tout de même défiler la vue en diagonale au lieu de limiter le mouvement à un seul axe.

Éditer: pour que ce soit plus clair, j'aimerais permettre à l'utilisateur de faire défiler horizontalement OR verticalement, mais pas les deux simultanément.

80
NickD

Vous êtes dans une situation très difficile, je dois dire.

Notez que vous devez utiliser UIScrollView avec pagingEnabled=YES pour passer d'une page à l'autre, mais vous avez besoin de pagingEnabled=NO pour faire défiler verticalement.

Il y a 2 stratégies possibles. Je ne sais pas lequel fonctionnera/est le plus facile à mettre en œuvre, alors essayez les deux.

D'abord: UIScrollViews imbriqué. Franchement, je n'ai pas encore vu une personne qui a ce travail. Cependant, je n'ai pas essayé assez fort personnellement, et ma pratique montre que lorsque vous essayez assez fort, vous pouvez obliger UIScrollView à faire tout ce que vous voulez .

La stratégie consiste donc à laisser la vue de défilement externe uniquement gérer le défilement horizontal et les vues de défilement interne uniquement le défilement vertical. Pour ce faire, vous devez savoir comment UIScrollView fonctionne en interne. Il substitue la méthode hitTest et se retourne toujours lui-même, de sorte que tous les événements tactiles entrent dans UIScrollView. Ensuite, à l'intérieur de touchesBegan, touchesMoved etc., il vérifie s'il est intéressé par l'événement et le gère ou le transmet aux composants internes.

Pour décider si le contact doit être traité ou transmis, UIScrollView lance un chronomètre lorsque vous le touchez pour la première fois:

  • Si vous n'avez pas déplacé votre doigt de manière significative en moins de 150 ms, l'événement est transféré dans la vue interne.

  • Si vous avez beaucoup déplacé votre doigt en moins de 150 ms, le défilement commence (et ne transmet jamais l'événement à la vue interne).

    Notez que lorsque vous touchez une table (qui est une sous-classe de la vue défilement) et commencez à faire défiler immédiatement, la ligne que vous avez touchée n'est jamais mise en surbrillance.

  • Si vous avez pas déplacé votre doigt de manière significative dans un délai de 150 ms et UIScrollView a commencé à transmettre les événements à la vue interne, mais , puis vous avez déplacé le doigt suffisamment loin pour que le défilement commence, UIScrollView appelle touchesCancelled dans la vue interne et commence à défiler.

    Notez que lorsque vous touchez une table, maintenez votre doigt un peu, puis commencez à faire défiler, la ligne que vous avez touchée est mise en surbrillance en premier, puis estompée par la suite.

Ces séquences d'événements peuvent être modifiées par la configuration de UIScrollView:

  • Si delaysContentTouches vaut NO, aucune minuterie n'est utilisée - les événements sont immédiatement transmis au contrôle interne (mais sont annulés si vous déplacez le doigt suffisamment loin)
  • Si cancelsTouches vaut NO, alors le défilement ne se produira plus une fois les événements envoyés.

Notez que c’est UIScrollView qui reçoit tous les événements touchesBegin, touchesMoved, touchesEnded et touchesCanceled de CocoaTouch (car son hitTest lui dit de le faire). Il les transmet ensuite à la vue intérieure s'il le souhaite, tant qu'il le souhaite.

Maintenant que vous savez tout sur UIScrollView, vous pouvez en modifier le comportement. Je parie que vous souhaitez donner la préférence au défilement vertical, de sorte que lorsque l'utilisateur touche la vue et commence à déplacer son doigt (même légèrement), la vue commence à défiler verticalement; mais lorsque l'utilisateur déplace suffisamment son doigt dans le sens horizontal, vous souhaitez annuler le défilement vertical et lancer le défilement horizontal.

Vous souhaitez sous-classer votre UIScrollView externe (par exemple, vous appelez votre classe RemorsefulScrollView), de sorte qu'au lieu du comportement par défaut, il transmette immédiatement tous les événements à la vue interne et ne défile que lorsque le mouvement horizontal est important.

Comment faire pour que RemorsefulScrollView se comporte de cette façon?

  • Il semble que désactiver le défilement vertical et définir delaysContentTouches sur NO devrait permettre à UIScrollViews imbriqué de fonctionner. Malheureusement, ce n'est pas le cas. UIScrollView semble faire un filtrage supplémentaire pour les mouvements rapides (qui ne peuvent pas être désactivés), de sorte que même si UIScrollView ne peut être défilé que horizontalement, il dévorera toujours (et ignorera) suffisamment de mouvements verticaux rapides.

    L'effet est si grave que le défilement vertical dans une vue de défilement imbriquée est inutilisable. (Il semble que vous ayez exactement cette configuration, alors essayez-la: maintenez un doigt pendant 150 ms, puis déplacez-la verticalement - UIScrollView imbriqué fonctionne comme prévu!)

  • Cela signifie que vous ne pouvez pas utiliser le code de UIScrollView pour la gestion des événements; vous devez remplacer les quatre méthodes de traitement tactile dans RemorsefulScrollView et effectuer votre propre traitement en premier, en transmettant l'événement à super (UIScrollView) si vous avez décidé de choisir le défilement horizontal.

  • Cependant, vous devez passer touchesBegan à UIScrollView, car vous souhaitez lui rappeler une coordonnée de base pour le défilement horizontal ultérieur (si vous le décidez ultérieurement est un défilement horizontal). Vous ne pourrez pas envoyer touchesBegan à UIScrollView ultérieurement, car vous ne pouvez pas stocker l'argument touches: il contient des objets qui seront mutés avant le prochain événement touchesMoved et vous ne pouvez pas reproduire. le vieil état.

    Vous devez donc passer immédiatement touchesBegan à UIScrollView, mais vous allez masquer tout événement touchesMoved supplémentaire jusqu'à ce que vous décidiez de faire défiler horizontalement. Non touchesMoved signifie pas de défilement, donc cette initiale touchesBegan ne fera aucun mal. Mais définissez delaysContentTouches sur NO pour ne pas interférer avec des temporisateurs supplémentaires.

    (Offtopic - contrairement à vous, UIScrollView peut stocker correctement les contacts, puis reproduire et transmettre ultérieurement l'événement touchesBegan d'origine. Il présente un avantage injuste en utilisant des API non publiées. cloner les objets tactiles avant qu’ils ne soient mutés.)

  • Étant donné que vous transmettez toujours touchesBegan, vous devez également transmettre touchesCancelled et touchesEnded. Vous devez cependant transformer touchesEnded en touchesCancelled, car UIScrollView interpréterait la séquence touchesBegan, touchesEnded comme un clic-clic et l'enverrait à la vue interne. Vous transférez déjà les événements appropriés vous-même, vous ne voulez donc jamais qu'UIScrollView transmette quoi que ce soit.

En gros, voici le pseudocode pour ce que vous devez faire. Par souci de simplicité, je n'autorise jamais le défilement horizontal après qu'un événement multitouch s'est produit.

// RemorsefulScrollView.h

@interface RemorsefulScrollView : UIScrollView {
  CGPoint _originalPoint;
  BOOL _isHorizontalScroll, _isMultitouch;
  UIView *_currentChild;
}
@end

// RemorsefulScrollView.m

// the numbers from an example in Apple docs, may need to tune them
#define kThresholdX 12.0f
#define kThresholdY 4.0f

@implementation RemorsefulScrollView

- (id)initWithFrame:(CGRect)frame {
  if (self = [super initWithFrame:frame]) {
    self.delaysContentTouches = NO;
  }
  return self;
}

- (id)initWithCoder:(NSCoder *)coder {
  if (self = [super initWithCoder:coder]) {
    self.delaysContentTouches = NO;
  }
  return self;
}

- (UIView *)honestHitTest:(CGPoint)point withEvent:(UIEvent *)event {
  UIView *result = nil;
  for (UIView *child in self.subviews)
    if ([child pointInside:point withEvent:event])
      if ((result = [child hitTest:point withEvent:event]) != nil)
        break;
  return result;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event]; // always forward touchesBegan -- there's no way to forward it later
    if (_isHorizontalScroll)
      return; // UIScrollView is in charge now
    if ([touches count] == [[event touchesForView:self] count]) { // initial touch
      _originalPoint = [[touches anyObject] locationInView:self];
    _currentChild = [self honestHitTest:_originalPoint withEvent:event];
    _isMultitouch = NO;
    }
  _isMultitouch |= ([[event touchesForView:self] count] > 1);
  [_currentChild touchesBegan:touches withEvent:event];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
  if (!_isHorizontalScroll && !_isMultitouch) {
    CGPoint point = [[touches anyObject] locationInView:self];
    if (fabsf(_originalPoint.x - point.x) > kThresholdX && fabsf(_originalPoint.y - point.y) < kThresholdY) {
      _isHorizontalScroll = YES;
      [_currentChild touchesCancelled:[event touchesForView:self] withEvent:event]
    }
  }
  if (_isHorizontalScroll)
    [super touchesMoved:touches withEvent:event]; // UIScrollView only kicks in on horizontal scroll
  else
    [_currentChild touchesMoved:touches withEvent:event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
  if (_isHorizontalScroll)
    [super touchesEnded:touches withEvent:event];
    else {
    [super touchesCancelled:touches withEvent:event];
    [_currentChild touchesEnded:touches withEvent:event];
    }
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
  [super touchesCancelled:touches withEvent:event];
  if (!_isHorizontalScroll)
    [_currentChild touchesCancelled:touches withEvent:event];
}

@end

Je n'ai pas essayé d'exécuter ni même de compiler ceci (et d'avoir saisi toute la classe dans un éditeur de texte brut), mais vous pouvez commencer avec ce qui précède et, espérons-le, le faire fonctionner.

Le seul problème que je vois est que, si vous ajoutez des vues enfant non UIScrollView à RemorsefulScrollView, les événements tactiles que vous transmettez à un enfant peuvent vous être renvoyés via la chaîne de répondeur, si ce dernier ne gère pas toujours les contacts comme UIScrollView. Une implémentation RemorsefulScrollView à l'épreuve des balles protégerait contre touchesXxx rentrée.

Deuxième stratégie: Si, pour une raison quelconque, les UIScrollViews imbriqués ne fonctionnent pas ou s'avèrent trop difficiles à obtenir, vous pouvez essayer de vous entendre avec un seul UIScrollView. , en changeant sa propriété pagingEnabled à la volée à partir de votre méthode de délégué scrollViewDidScroll.

Pour éviter le défilement diagonal, commencez par essayer de vous rappeler contentOffset dans scrollViewWillBeginDragging, puis de vérifier et de réinitialiser contentOffset dans scrollViewDidScroll si vous détectez un mouvement diagonal. Une autre stratégie à essayer consiste à réinitialiser contentSize pour n’autoriser le défilement que dans un sens, une fois que vous avez choisi le sens du doigt de l’utilisateur. (UIScrollView semble plutôt indulgent à propos de la manipulation de contentSize et de contentOffset à partir de ses méthodes déléguées.)

Si cela ne fonctionne pas non plus ou donne des visuels bâclés, vous devez remplacer touchesBegan, touchesMoved etc. et ne pas transmettre les événements de mouvement diagonal à UIScrollView. (Dans ce cas, l'expérience utilisateur sera sous-optimale, car vous devrez ignorer les mouvements diagonaux au lieu de les forcer dans une seule direction. Si vous vous sentez vraiment aventureux, vous pouvez écrire votre propre sosie UITouch, quelque chose comme RevengeTouch. -C est un vieux C et rien n’est plus douteux dans le monde que C: tant que personne ne contrôle la classe réelle des objets, ce que personne ne vérifie, vous pouvez faire en sorte que toute classe ressemble à une autre classe. une possibilité de synthétiser toutes les touches que vous voulez, avec les coordonnées que vous voulez.)

Stratégie de sauvegarde: , il y a TTScrollView, une réimplémentation rationnelle de UIScrollView dans bibliothèque Three2 . Malheureusement, il semble très peu naturel et non iphonish à l'utilisateur. Mais si chaque tentative d'utilisation de UIScrollView échoue, vous pouvez revenir à une vue de défilement codée sur mesure. Je le déconseille si possible; En utilisant UIScrollView, vous obtenez l'apparence native, quelle que soit l'évolution de celle-ci dans les futures versions d'iPhone OS.

Ok, ce petit essai est un peu trop long. Je suis toujours dans les jeux UIScrollView après le travail sur ScrollingMadness il y a plusieurs jours.

P.S. Si vous rencontrez un de ces problèmes et que vous souhaitez partager, envoyez-moi un e-mail avec le code correspondant à l'adresse [email protected], je l'inclurais volontiers dans mon sac à malice ScrollingMadness .

P.P.S. Ajout de ce petit essai à ScrollingMadness README.

115
Andrey Tarantsov

Cela fonctionne pour moi à chaque fois ...

scrollView.contentSize = CGSizeMake(scrollView.frame.size.width * NumberOfPages, 1);
53
Tonetel

Pour les paresseux comme moi:

Définissez scrollview.directionalLockEnabled sur YES

- (void) scrollViewWillBeginDragging: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}

- (void) scrollViewDidScroll: (UIScrollView *) scrollView
{
    if (scrollView.contentOffset.x != self.oldContentOffset.x)
    {
        scrollView.pagingEnabled = YES;
        scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x,
                                               self.oldContentOffset.y);
    }
    else
    {
        scrollView.pagingEnabled = NO;
    }
}

- (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}
24
icompot

J'ai utilisé la méthode suivante pour résoudre ce problème, j'espère que cela vous aidera.

Commencez par définir les variables suivantes dans le fichier d’en-tête de votre contrôleur.

CGPoint startPos;
int     scrollDirection;

startPos conserve la valeur contentOffset lorsque votre délégué reçoit scrollViewWillBeginDragging message. Donc, dans cette méthode, nous faisons cela;

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    startPos = scrollView.contentOffset;
    scrollDirection=0;
}

nous utilisons ensuite ces valeurs pour déterminer la direction souhaitée du défilement dans scrollViewDidScroll message.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{

   if (scrollDirection==0){//we need to determine direction
       //use the difference between positions to determine the direction.
       if (abs(startPos.x-scrollView.contentOffset.x)<abs(startPos.y-scrollView.contentOffset.y)){          
          NSLog(@"Vertical Scrolling");
          scrollDirection=1;
       } else {
          NSLog(@"Horitonzal Scrolling");
          scrollDirection=2;
       }
    }
//Update scroll position of the scrollview according to detected direction.     
    if (scrollDirection==1) {
       [scrollView setContentOffset:CGPointMake(startPos.x,scrollView.contentOffset.y) animated:NO];
    } else if (scrollDirection==2){
       [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x,startPos.y) animated:NO];
    }
 }

enfin, nous devons arrêter toutes les opérations de mise à jour lorsque l'utilisateur glisse;

 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    if (decelerate) {
       scrollDirection=3;
    }
 }
16
Yildiray Meric

Je suis peut-être en train de chercher ici, mais je suis tombé sur ce message aujourd'hui alors que j'essayais de résoudre le même problème. Voici ma solution, semble fonctionner très bien.

Je souhaite afficher un écran de contenu, mais permettre à l'utilisateur de faire défiler l'écran vers l'un des 4 autres écrans, haut en bas, à gauche et à droite. J'ai un seul UIScrollView de taille 320x480 et une taille de contenu de 960x1440. Le décalage de contenu commence à 320 480. Dans scrollViewDidEndDecelerating :, Je redessine les 5 vues (les vues centrale et les 4 autour de celle-ci) et réinitialise le décalage du contenu à 320 480.

Voici le fond de ma solution, la méthode scrollViewDidScroll:.

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{   
    if (!scrollingVertically && ! scrollingHorizontally)
    {
        int xDiff = abs(scrollView.contentOffset.x - 320);
        int yDiff = abs(scrollView.contentOffset.y - 480);

        if (xDiff > yDiff)
        {
            scrollingHorizontally = YES;
        }
        else if (xDiff < yDiff)
        {
            scrollingVertically = YES;
        }
    }

    if (scrollingHorizontally)
    {
        scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x, 480);
    }
    else if (scrollingVertically)
    {
        scrollView.contentOffset = CGPointMake(320, scrollView.contentOffset.y);
    }
}
13
James J

Les gars, c'est aussi simple que de donner la même hauteur à la sous-vue qu'à la hauteur de la vue de défilement et à la hauteur de la taille du contenu. Ensuite, le défilement vertical est désactivé.

4
julianlubenov

Voici mon implémentation d'une UIScrollView paginée qui n'autorise que les scrolls orthogonaux. Assurez-vous de définir pagingEnabled sur YES.

PagedOrthoScrollView.h

@interface PagedOrthoScrollView : UIScrollView
@end

PagedOrthoScrollView.m

#import "PagedOrthoScrollView.h"

typedef enum {
  PagedOrthoScrollViewLockDirectionNone,
  PagedOrthoScrollViewLockDirectionVertical,
  PagedOrthoScrollViewLockDirectionHorizontal
} PagedOrthoScrollViewLockDirection; 

@interface PagedOrthoScrollView ()
@property(nonatomic, assign) PagedOrthoScrollViewLockDirection dirLock;
@property(nonatomic, assign) CGFloat valueLock;
@end

@implementation PagedOrthoScrollView
@synthesize dirLock;
@synthesize valueLock;

- (id)initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  if (self == nil) {
    return self;
  }

  self.dirLock = PagedOrthoScrollViewLockDirectionNone;
  self.valueLock = 0;

  return self;
}

- (void)setBounds:(CGRect)bounds {
  int mx, my;

  if (self.dirLock == PagedOrthoScrollViewLockDirectionNone) {
    // is on even page coordinates, set dir lock and lock value to closest page 
    mx = abs((int)CGRectGetMinX(bounds) % (int)CGRectGetWidth(self.bounds));
    if (mx != 0) {
      self.dirLock = PagedOrthoScrollViewLockDirectionHorizontal;
      self.valueLock = (round(CGRectGetMinY(bounds) / CGRectGetHeight(self.bounds)) *
                        CGRectGetHeight(self.bounds));
    } else {
      self.dirLock = PagedOrthoScrollViewLockDirectionVertical;
      self.valueLock = (round(CGRectGetMinX(bounds) / CGRectGetWidth(self.bounds)) *
                        CGRectGetWidth(self.bounds));
    }

    // show only possible scroll indicator
    self.showsVerticalScrollIndicator = dirLock == PagedOrthoScrollViewLockDirectionVertical;
    self.showsHorizontalScrollIndicator = dirLock == PagedOrthoScrollViewLockDirectionHorizontal;
  }

  if (self.dirLock == PagedOrthoScrollViewLockDirectionHorizontal) {
    bounds.Origin.y = self.valueLock;
  } else {
    bounds.Origin.x = self.valueLock;
  }

  mx = abs((int)CGRectGetMinX(bounds) % (int)CGRectGetWidth(self.bounds));
  my = abs((int)CGRectGetMinY(bounds) % (int)CGRectGetHeight(self.bounds));

  if (mx == 0 && my == 0) {
    // is on even page coordinates, reset lock
    self.dirLock = PagedOrthoScrollViewLockDirectionNone;
  }

  [super setBounds:bounds];
}

@end
3
Mattias Wadman

J'ai également eu à résoudre ce problème, et bien que la réponse d'Andrey Tarantsov contienne certainement de nombreuses informations utiles pour comprendre le fonctionnement de UIScrollViews, je pense que la solution est un peu trop compliquée. Ma solution, qui n’implique aucun sous-classement ni aucun renvoi automatique, est la suivante:

  1. Créez deux vues de défilement imbriquées, une pour chaque direction.
  2. Créez deux variables UIPanGestureRecognizers, à nouveau une pour chaque direction.
  3. Créez des dépendances entre les propriétés UIScrollViews 'panGestureRecognizer et la valeur factice UIPanGestureRecognizer correspondant à la direction perpendiculaire à la direction de défilement souhaitée par UIScrollView à l'aide du sélecteur UIGestureRecognizer's-requireGestureRecognizerToFail:.
  4. Dans les rappels -gestureRecognizerShouldBegin: de votre indicateur UIPanGestureRecognizers factice, calculez la direction initiale du panoramique à l'aide du sélecteur -translationInView: du geste (votre UIPanGestureRecognizers n'appellera pas ce rappel du délégué jusqu'à ce que votre contact ait suffisamment traduit pour être enregistré). Autorisez ou interdisez le début de votre reconnaissance des gestes en fonction de la direction calculée, ce qui devrait ensuite permettre de contrôler si la reconnaissance des gestes de panoramique UIScrollView perpendiculaire est autorisée ou non.
  5. Dans -gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:, n'autorisez pas votre variable factice UIPanGestureRecognizers à fonctionner parallèlement au défilement (à moins que vous souhaitiez ajouter un comportement supplémentaire).
3
Kurt Horimoto

C'est une solution améliorée de icompot, qui était assez instable pour moi. Celui-ci fonctionne bien et il est facile à mettre en œuvre:

- (void) scrollViewWillBeginDragging: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}

- (void) scrollViewDidScroll: (UIScrollView *) scrollView
{    

    float XOffset = fabsf(self.oldContentOffset.x - scrollView.contentOffset.x );
    float YOffset = fabsf(self.oldContentOffset.y - scrollView.contentOffset.y );

        if (scrollView.contentOffset.x != self.oldContentOffset.x && (XOffset >= YOffset) )
        {

            scrollView.pagingEnabled = YES;
            scrollView.contentOffset = CGPointMake(scrollView.contentOffset.x,
                                                   self.oldContentOffset.y);

        }
        else
        {
            scrollView.pagingEnabled = NO;
               scrollView.contentOffset = CGPointMake( self.oldContentOffset.x,
                                                   scrollView.contentOffset.y);
        }

}


- (void) scrollViewDidEndDecelerating: (UIScrollView *) scrollView
{
    self.oldContentOffset = scrollView.contentOffset;
}
2
Maciek z Wrocławia

Merci Tonetel. J'ai légèrement modifié votre approche, mais c'était exactement ce dont j'avais besoin pour empêcher le défilement horizontal.

self.scrollView.contentSize = CGSizeMake(self.scrollView.contentSize.width, 1);
2
Jeremy

Ce que j'ai fait est de créer une UIScrollView de pagination avec un cadre de la taille de l'écran de visualisation et de définir la taille du contenu sur la largeur requise pour tout mon contenu et une hauteur de 1 (merci Tonetel). Ensuite, j'ai créé des pages de menu UIScrollView avec des cadres définis pour chaque page, la taille de l'écran de visualisation et la taille du contenu de chacune d'elles en fonction de la largeur de l'écran et de la hauteur nécessaire pour chacune d'elles. Ensuite, j'ai simplement ajouté chacune des pages de menu à la vue de pagination. Assurez-vous que la pagination est activée dans la vue de défilement de la pagination, mais désactivée dans les vues du menu (doit être désactivée par défaut) et vous devriez être prêt à partir. 

La vue de défilement de la pagination fait maintenant défiler horizontalement, mais pas verticalement à cause de la hauteur du contenu. Chaque page défile verticalement mais pas horizontalement à cause de ses contraintes de taille de contenu.

Voici le code si cette explication laisse à désirer:

UIScrollView *newPagingScrollView =  [[UIScrollView alloc] initWithFrame:self.view.bounds]; 
[newPagingScrollView setPagingEnabled:YES];
[newPagingScrollView setShowsVerticalScrollIndicator:NO];
[newPagingScrollView setShowsHorizontalScrollIndicator:NO];
[newPagingScrollView setDelegate:self];
[newPagingScrollView setContentSize:CGSizeMake(self.view.bounds.size.width * NumberOfDetailPages, 1)];
[self.view addSubview:newPagingScrollView];

float pageX = 0;

for (int i = 0; i < NumberOfDetailPages; i++)
{               
    CGRect pageFrame = (CGRect) 
    {
        .Origin = CGPointMake(pageX, pagingScrollView.bounds.Origin.y), 
        .size = pagingScrollView.bounds.size
    };
    UIScrollView *newPage = [self createNewPageFromIndex:i ToPageFrame:pageFrame]; // newPage.contentSize custom set in here        
    [pagingScrollView addSubview:newPage];

    pageX += pageFrame.size.width;  
}           
2
Jon Conner

De la docs:

"La valeur par défaut est NO, ce qui signifie que le défilement est autorisé dans les directions horizontale et verticale. Si la valeur est OUI et que l'utilisateur commence à faire glisser dans une direction générale (horizontalement ou verticalement), la vue de défilement désactive le défilement dans l'autre direction. "

Je pense que la partie importante est "si ... l'utilisateur commence en glissant dans une direction générale" . Donc, s'ils commencent en glissant en diagonale, cela ne démarre pas . Non que ces documents soient toujours fiable en matière d’interprétation - mais cela semble correspondre à ce que vous voyez.

Cela semble réellement raisonnable. Je dois demander - pourquoi voulez-vous limiter à tout moment à l'horizontale ou à la verticale? UIScrollView n'est peut-être pas l'outil pour vous?

1
philsquared

Pour référence future, je me suis inspiré de la réponse d’Andrey Tanasov pour proposer la solution suivante qui fonctionne bien.

Commencez par définir une variable pour stocker le contentOffset et définissez-la sur 0,0 coordonnées 

var positionScroll = CGPointMake(0, 0)

Maintenant, implémentez ce qui suit dans le délégué scrollView:

override func scrollViewWillBeginDragging(scrollView: UIScrollView) {
    // Note that we are getting a reference to self.scrollView and not the scrollView referenced passed by the method
    // For some reason, not doing this fails to get the logic to work
    self.positionScroll = (self.scrollView?.contentOffset)!
}

override func scrollViewDidScroll(scrollView: UIScrollView) {

    // Check that contentOffset is not nil
    // We do this because the scrollViewDidScroll method is called on viewDidLoad
    if self.scrollView?.contentOffset != nil {

        // The user wants to scroll on the X axis
        if self.scrollView?.contentOffset.x > self.positionScroll.x || self.scrollView?.contentOffset.x < self.positionScroll.x {

            // Reset the Y position of the scrollView to what it was BEFORE scrolling started
            self.scrollView?.contentOffset = CGPointMake((self.scrollView?.contentOffset.x)!, self.positionScroll.y);
            self.scrollView?.pagingEnabled = true
        }

        // The user wants to scroll on the Y axis
        else {
            // Reset the X position of the scrollView to what it was BEFORE scrolling started
            self.scrollView?.contentOffset = CGPointMake(self.positionScroll.x, (self.scrollView?.contentOffset.y)!);
            self.collectionView?.pagingEnabled = false
        }

    }

}

J'espère que cela t'aides.

1
Benjamin

Nécessité de remettre _isHorizontalScroll à NO dans touchesEnded et touchesCancelled

1
Praveen Matanam

Ma vue est constituée de 10 vues horizontales, et certaines de ces vues sont composées de plusieurs vues verticales. Vous obtiendrez ainsi le résultat suivant:


1.0   2.0   3.1    4.1
      2.1   3.1
      2.2
      2.3

Avec la pagination activée sur les axes horizontal et vertical

La vue possède également les attributs suivants:

[self setDelaysContentTouches:NO];
[self setMultipleTouchEnabled:NO];
[self setDirectionalLockEnabled:YES];

Maintenant, pour empêcher le défilement diagonal, procédez comme suit:

-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
    int pageWidth = 768;
    int pageHeight = 1024;
    int subPage =round(self.contentOffset.y / pageHeight);
    if ((int)self.contentOffset.x % pageWidth != 0 && (int)self.contentOffset.y % pageHeight != 0) {
        [self setContentOffset:CGPointMake(self.contentOffset.x, subPage * pageHeight];
    } 
}

Fonctionne comme un charme pour moi!

1
user422756

Si vous rencontrez un grand défilement ... et que vous souhaitez limiter le défilement diagonal http://chandanshetty01.blogspot.in/2012/07/restricting-diagonal-scrolling-in.html

1
Chandan Shetty SP

Pour ceux qui cherchent dans Swift @AndreyTarantsov deuxième solution, son code est le suivant:

    var positionScroll:CGFloat = 0

    func scrollViewWillBeginDragging(scrollView: UIScrollView) {
        positionScroll = self.theScroll.contentOffset.x
    }

    func scrollViewDidScroll(scrollView: UIScrollView) {
        if self.theScroll.contentOffset.x > self.positionScroll || self.theScroll.contentOffset.x < self.positionScroll{
             self.theScroll.pagingEnabled = true
        }else{
            self.theScroll.pagingEnabled = false
        }
    }

ce que nous faisons ici est de garder la position actuelle juste avant que scrollview ne soit déplacé, puis de vérifier si x a augmenté ou diminué par rapport à la position actuelle de x. Si oui, définissez pagingEnabled sur true, si non (y a augmenté/diminué), puis sur pagingEnabled sur false

J'espère que cela sera utile aux nouveaux venus!

0
jonprasetyo

Si vous n'avez pas essayé de le faire dans la version bêta 3.0, je vous recommande de l'essayer. J'utilise actuellement une série de vues de table dans une vue de défilement, et cela fonctionne comme prévu. (Et bien, le défilement le fait quand même. Vous avez trouvé cette question lorsque vous cherchez une solution à un problème totalement différent ...)

0
Tim Keating