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 ..
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
.
scroll.clipsToBounds = NO
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;
}
Définissez le HackClipView.clipsToBounds = YES
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
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;
}
}
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
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.
Définissez la méthode contentOffset
in -(void)scrollViewDidScroll:(UIScrollView *)scrollView
.
Voir également UIScrollViewDelegate refernces
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
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];
}
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.
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
)
}