web-dev-qa-db-fra.com

Taille de pagination personnalisée UIScrollView

la pagination dans UIScrollView est une fonctionnalité très utile, ce dont j'ai besoin ici est de régler la pagination sur une distance inférieure, par exemple, je veux que mon UIScrollView ait une taille de page inférieure à celle du cadre UIScrollView ..

39
user784625

Vous pouvez utiliser une méthode de délégué UIScrollView. Définissez votre classe en tant que délégué de la vue de défilement, puis implémentez les éléments suivants:

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset
{
    CGFloat kMaxIndex = 23;
    CGFloat targetX = scrollView.contentOffset.x + velocity.x * 60.0;
    CGFloat targetIndex = round(targetX / (kCellWidth + kCellSpacing));
    if (targetIndex < 0)
        targetIndex = 0;
    if (targetIndex > kMaxIndex)
        targetIndex = kMaxIndex;
    targetContentOffset->x = targetIndex * (kCellWidth + kCellSpacing);
}

Le paramètre vélocité est nécessaire pour que le défilement soit naturel et ne se termine pas brusquement lorsqu'un contact se termine alors que votre doigt est toujours en mouvement. La largeur et l'espacement des cellules sont la largeur et l'espacement entre les pages de votre vue. Dans ce cas, j'utilise une UICollectionView.

63
lucius
  1. Changez votre taille de scrollView à la taille de page que vous voulez
  2. Définissez votre scroll.clipsToBounds = NO 
  3. Créez une sous-classe UIView (par exemple HackClipView) et substituez la méthode hitTest: withEvent: 

    -(UIView *) hitTest:(CGPoint) point withEvent:(UIEvent *)event
    {     
        UIView* child = [super hitTest:point withEvent:event]; 
        if (child == self && self.subviews.count > 0)  
        {
            return self.subviews[0];
        }
        return child;
    }
    
  4. Définissez le HackClipView.clipsToBounds = YES

  5. Placez votre scrollView dans ce HackClipView (avec la taille totale de défilement souhaitée)

Voir cette réponse pour plus de détails

Update: Comme indiqué dans lucius answer, vous pouvez maintenant implémenter le protocole UIScollViewDelegate et utiliser la méthode - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset. Comme la targetContentOffset est un pointeur. L'utilisation de cette méthode ne vous garantit pas le même résultat avec les pages à vue de défilement, car l'utilisateur peut faire défiler plusieurs pages à la fois. Mais régler descelerationRate sur fast donnera presque le même résultat

21
Francescu

Vous devez désactiver la pagination et ajouter un UIPanGestureRecognizer à votre vue de défilement et gérer vous-même la pagination.

- (void)viewDidLoad {

    [super viewDidLoad];
    CGRect viewRect = self.view.bounds; // View controller's view bounds
    theScrollView = [[UIScrollView alloc] initWithFrame:viewRect]; 

    theScrollView.scrollsToTop      = NO;
    theScrollView.pagingEnabled         = NO;
    theScrollView.delaysContentTouches  = NO;
    theScrollView.delegate = self;

    [self.view addSubview:theScrollView];

    UIPanGestureRecognizer * peter = [[[UIPanGestureRecognizer alloc] initWithTarget:self  
                                                                              action:@selector(handlePan:)]
                                       autorelease]; 
    [theScrollView addGestureRecognizer:peter]; 

}

-(void)handlePan:(UIPanGestureRecognizer*)recognizer{

switch (recognizer.state) {
    case UIGestureRecognizerStateBegan:{
        // panStart and startPoint are instance vars for the viewContainer 
        panStart = theScrollView.contentOffset;
        startPoint = [recognizer locationInView:theScrollView]; 


        break;
    }
    case UIGestureRecognizerStateChanged:{

        CGPoint newPoint = [recognizer locationInView:theScrollView];
        CGFloat delta = startPoint.x - newPoint.x;
        if ( abs(delta) > 2)
            theScrollView.contentOffset = CGPointMake( theScrollView.contentOffset.x + delta, 0); 

        CGFloat moveDelta = panStart.x - theScrollView.contentOffset.x;                               


        // current witdh should hold the currently displayed page/view in theScrollView
        if ( abs(moveDelta) > (currentWidth * 0.40)){
            panStart = theScrollView.contentOffset;
            startPoint = newPoint;

            //NSLog(@"delta is bigger"); 
            if ( moveDelta < 0 )
                [self incrementPageNumber]; // you should implement this method and present the next view
            else 
                [self decrementPageNumber]; // you should implement this method and present the previous view

            recognizer.enabled = NO; // disable further event until view change finish

        }

        break; 
    }

    case UIGestureRecognizerStateEnded:
    case UIGestureRecognizerStateCancelled:

        recognizer.enabled = YES; 
        [self showDocumentPage:currentPage]; 

        break;


    default:
        break;
}

}

11
aimless

La solution Swift 4.1 qui simplifie la réutilisation:

/// Protocol that simplifies custom page size configuration for UIScrollView.
/// Sadly, can not be done better due to protocol extensions limitations - https://stackoverflow.com/questions/39487168/non-objc-method-does-not-satisfy-optional-requirement-of-objc-protocol
/// - note: Set `.decelerationRate` to `UIScrollViewDecelerationRateFast` for a fancy scrolling animation.
protocol ScrollViewCustomHorizontalPageSize: UIScrollViewDelegate {
    /// Custom page size
    var pageSize: CGFloat { get }

    /// Helper method to get current page fraction
    func getCurrentPage(scrollView: UIScrollView) -> CGFloat

    /// Helper method to get targetContentOffset. Usage:
    ///
    ///     func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    ///         targetContentOffset.pointee.x = getTargetContentOffset(scrollView: scrollView, velocity: velocity)
    ///     }
    func getTargetContentOffset(scrollView: UIScrollView, velocity: CGPoint) -> CGFloat

    /// Must be implemented. See `getTargetContentOffset` for more info.
    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>)
}

extension ScrollViewCustomHorizontalPageSize {
    func getCurrentPage(scrollView: UIScrollView) -> CGFloat {
        return (scrollView.contentOffset.x + scrollView.contentInset.left) / pageSize
    }

    func getTargetContentOffset(scrollView: UIScrollView, velocity: CGPoint) -> CGFloat {
        let targetX: CGFloat = scrollView.contentOffset.x + velocity.x * 60.0

        var targetIndex = (targetX + scrollView.contentInset.left) / pageSize
        let maxOffsetX = scrollView.contentSize.width - scrollView.bounds.width + scrollView.contentInset.right
        let maxIndex = (maxOffsetX + scrollView.contentInset.left) / pageSize
        if velocity.x > 0 {
            targetIndex = ceil(targetIndex)
        } else if velocity.x < 0 {
            targetIndex = floor(targetIndex)
        } else {
            let (maxFloorIndex, lastInterval) = modf(maxIndex)
            if targetIndex > maxFloorIndex {
                if targetIndex >= lastInterval / 2 + maxFloorIndex {
                    targetIndex = maxIndex
                } else {
                    targetIndex = maxFloorIndex
                }
            } else {
                targetIndex = round(targetIndex)
            }
        }

        if targetIndex < 0 {
            targetIndex = 0
        }

        var offsetX = targetIndex * pageSize - scrollView.contentInset.left
        offsetX = min(offsetX, maxOffsetX)

        return offsetX
    }
}

Il suffit de se conformer au protocole ScrollViewCustomPageSize dans votre délégué UIScrollView/UITableView/UICollectionView et vous avez terminé, par exemple:

extension MyCollectionViewController: ScrollViewCustomPageSize {
    var pageSize: CGFloat {
        return 200
    }

    func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
        targetContentOffset.pointee.x = getTargetContentOffset(scrollView: scrollView, velocity: velocity)
    }
}

Pour un défilement sophistiqué, je recommande également de définir collectionView.decelerationRate = UIScrollViewDecelerationRateFast

2
Anton Plebanovich

J'ai eu le même problème, j'ai donc créé un fichier UIScrollView ..__ personnalisé. Il est disponible sur Github maintenant, car lors de ma recherche, je n'ai trouvé aucune solution de ce type. Enjoy! https://github.com/MartinMetselaar/MMCPSScrollView

MMCPSScrollView* scrollView = [[MMCPSScrollView alloc] initWithFrame:self.view.bounds];
[scrollView setType:MMCPSScrollVertical];
[scrollView setPageHeight:250];
[scrollView setPageSize:2];
[self.view addSubview:scrollView];

Si vous avez d'autres questions sur ce composant, il suffit de demander.

1
Martin Metselaar

Définissez la méthode contentOffset in -(void)scrollViewDidScroll:(UIScrollView *)scrollView

Voir également UIScrollViewDelegate refernces

1
Hisenberg

J'ai eu le même problème il y a peu de temps. Mon approche consistait à ajouter un second UIScrollView à scrollview. Vous pouvez donc passer à la page. Sur cette page, il semble que si la page est plus grande que l'écran. J'espère que cela fonctionne aussi dans votre situation. ;-)

Sandro Meier

0
Sandro Meier

Ajouter des reconnaisseurs de geste ou d'autres vues secondaires, etc., est ridicule. Il suffit de définir le délégué de la vue de défilement sur l'un des éléments suivants: 

// This is for a vertical scrolling scroll view. 
// Let's say you want it to snap to every 160 pixels :    

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{     
     int y = scrollView.contentOffset.y;
     int yOff = y % 160;
     if(yOff < 80)
          y -= yOff;
     else
          y += 160 - yOff;

     [scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, y) animated:YES];
}

// This is for a horizontal scrolling scroll view.
// Let's say you want the same, to snap to every 160 pixels :

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
     int x = scrollView.contentOffset.x;
     int xOff = x % 160;
     if(xOff < 80)
          x -= xOff;
     else
          x += 160 - xOff;

     [scrollView setContentOffset:CGPointMake(x, scrollView.contentOffset.y) animated:YES];
}
0
Danut Pralea

Cela semblait fonctionner beaucoup mieux pour moi:

Pagination personnalisée UIScrollView

Ici, ils ajoutent le scrollview (en gardant sa douceur de pagination) en tant que sous-vue à un ExtendedTouchView ou une sous-classe de UIVIew et écrasant la méthode de test de frappe

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
if ([self pointInside:point withEvent:event]) {
    if ([[self subviews] count] > 0) {
        //force return of first child, if exists
        return [[self subviews] objectAtIndex:0];
    } else {
        return self;
    }
}
return nil;
}

Cela a fait exactement ce que je voulais avec un code minimal et des maux de tête.

0
brad.roush

Swift 4.1, iOS11 +:

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    targetContentOffset.pointee = CGPoint(
        x: round(targetContentOffset.pointee.x / pageWidth) * pageWidth,
        y: targetContentOffset.pointee.y
    )
}
0
Deniss Fedotovs