如何使一个UIScrollView捕捉到的图标(如应用程序商店:功能)(How to make a

2019-08-22 03:08发布

我想要得到的是相同的行为,这卷轴观点有:

我知道,这是使用HTML,而不是原生API,但我想实现它作为一个UIKit的组成部分。

现在,我在寻找的行为:

  • 请注意,这是一个分页滚动视图,但“页面大小”小于视图的宽度。
  • 当你把它的滚动由左到右各页“嵌入”到最左边的项目。
  • 当您从右端向左它“捕捉”到最右边的项滚动它。

在同一个页面,但现在从右到左:

我已经试过:

  • 我试图使滚动视图小于它的超视图和压倒一切的则hitTest,这让我留下对正确的行为。
  • 我已经试过落实scrollViewWillEndDragging:withVelocity:targetContentOffset:和设置targetContentOffset我想,但因为我不能改变它只是滚动太慢或太快的速度。
  • 我已经试过落实scrollViewDidEndDecelerating:然后动画到正确的偏移,但滚动视图首先停止然后移动时,它看起来不自然。
  • 我已经试过落实scrollViewDidEndDragging:willDecelerate:然后动画到正确的偏移,但滚动视图“跳”,不正确动画。

我的想法。

谢谢!

更新:

最后我用罗布Mayoff的方法,它看起来干净。 我改变了它,所以当速度为0,例如,当用户拖拽,停止,松开手指会工作。

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView 
                     withVelocity:(CGPoint)velocity 
              targetContentOffset:(CGPoint *)targetContentOffset {
    CGFloat maxOffset = scrollView.contentSize.width - scrollView.bounds.size.width;
    CGFloat minOffset = 0;

    if (velocity.x == 0) {
        CGFloat targetX = MAX(minOffset,MIN(maxOffset, targetContentOffset->x));

        CGFloat diff = targetX - baseOffset;

        if (ABS(diff) > offsetStep/2) {
            if (diff > 0) {
                //going left
                baseOffset = MIN(maxOffset, baseOffset + offsetStep);
            } else {
                //going right
                baseOffset = MAX(minOffset, baseOffset - offsetStep);
            }
        }
    } else {
        if (velocity.x > 0) {
            baseOffset = MIN(maxOffset, baseOffset + offsetStep);
        } else {
            baseOffset = MAX(minOffset, baseOffset - offsetStep);
        }
    }

    targetContentOffset->x = baseOffset;
}

这种解决方案的唯一问题是,刷卡滚动视图不产生“反弹”的效果。 感觉“卡住”。

Answer 1:

设置scrollView.decelerationRate = UIScrollViewDecelerationRateFast ,与实施相结合scrollViewWillEndDragging:withVelocity:targetContentOffset:在我看来,使用集合视图的工作。

首先,我给自己一些实例变量:

@implementation ViewController {
    NSString *cellClassName;
    CGFloat baseOffset;
    CGFloat offsetStep;
}

viewDidLoad ,我设置视图的decelerationRate

- (void)viewDidLoad {
    [super viewDidLoad];
    cellClassName = NSStringFromClass([MyCell class]);
    [self.collectionView registerNib:[UINib nibWithNibName:cellClassName bundle:nil] forCellWithReuseIdentifier:cellClassName];
    self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast;
}

我需要offsetStep是适合在视图的屏幕上的界限项目的整数倍的大小。 我计算它在viewDidLayoutSubviews

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];
    UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout;
    CGFloat stepUnit = layout.itemSize.width + layout.minimumLineSpacing;
    offsetStep = stepUnit * floorf(self.collectionView.bounds.size.width / stepUnit);
}

我需要baseOffset到可以在X视图的滚动开始之前所抵消。 我初始化它在viewDidAppear:

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    baseOffset = self.collectionView.contentOffset.x;
}

然后,我需要强制视图中的步骤进行滚动offsetStep 。 我这样做,在scrollViewWillEndDragging:withVelocity:targetContentOffset: 根据不同的velocity ,我增加或减少baseOffset通过offsetStep 。 但我钳baseOffset的最小值为0,最大的的contentSize.width - bounds.size.width

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
    if (velocity.x < 0) {
        baseOffset = MAX(0, baseOffset - offsetStep);
    } else if (velocity.x > 0) {
        baseOffset = MIN(scrollView.contentSize.width - scrollView.bounds.size.width, baseOffset + offsetStep);
    }
    targetContentOffset->x = baseOffset;
}

请注意,我不在乎什么targetContentOffset->x进来的。

这具有使位于最左侧可视项的左边缘,直到用户滚动一路到最后一个项目的效果。 在这一点上它对齐到最右边的可见项目的右边缘,直到用户滚动一路到左侧。 这似乎符合App Store应用的行为。

如果不适合你,你可以尝试将最后一行( targetContentOffset->x = baseOffset本):

    dispatch_async(dispatch_get_main_queue(), ^{
        [scrollView setContentOffset:CGPointMake(baseOffset, 0) animated:YES];
    });

这也为我工作。

你可以找到我的测试应用程序在此git仓库 。



Answer 2:

你可以打开pagingEnabled ,或使用decelerationRate = UIScrollViewDecelerationRateFast联合scrollViewDidEndDraggingscrollViewDidEndDecelerating (这将最小化减慢滚动到一个位置,然后再次动画到另一个位置的效果)。 这可能不正是你想要的,但它是相当接近。 通过使用animateWithDuration它避免了最终捕捉的瞬间跳跃。

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    if (!decelerate)
        [self snapScrollView:scrollView];
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    [self snapScrollView:scrollView];
}

然后,你可以写一个snapScrollView ,如:

- (void)snapScrollView:(UIScrollView *)scrollView
{
    CGPoint offset = scrollView.contentOffset;

    if ((offset.x + scrollView.frame.size.width) >= scrollView.contentSize.width)
    {
        // no snap needed ... we're at the end of the scrollview
        return;
    }

    // calculate where you want it to snap to

    offset.x = floorf(offset.x / kIconOffset + 0.5) * kIconOffset;

    // now snap it to there

    [UIView animateWithDuration:0.1
                     animations:^{
                         scrollView.contentOffset = offset;
                     }];
}


Answer 3:

简单的解决方案,原理类似于App Store的,具有速度或没有它。 kCellBaseWidth -细胞的宽度。

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
                     withVelocity:(CGPoint)velocity
              targetContentOffset:(inout CGPoint *)targetContentOffset
{
    NSInteger index = lrint(targetContentOffset->x/kCellBaseWidth);
    targetContentOffset->x = index * kCellBaseWidth;
}


Answer 4:

插嘴的斯威夫特。 @avdyushin的答案是目前为止最简单,正如本所提到的,效果非常好。 虽然我从没有添加一块@Rob的回答关于滚动视图的结尾。 总之,这个解决方案似乎很好地工作。

func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {

    if ((scrollView.contentOffset.x + scrollView.frame.size.width) >= scrollView.contentSize.width) {
        // no snap needed ... we're at the end of the scrollview
        return
    }

    let index: CGFloat = CGFloat(lrintf(Float(targetContentOffset.memory.x) / kCellBaseWidth))
    targetContentOffset.memory.x = index * kCellBaseWidth

}

只要你加入minimumLineSpacingkCellBaseWidth ,瞧。



文章来源: How to make a UIScrollView snap to icons (like App Store: Feature)