Iphone 如何使UIScrollView捕捉到图标(如应用商店:功能)

Iphone 如何使UIScrollView捕捉到图标(如应用商店:功能),iphone,ios,xcode,cocoa,uiscrollview,Iphone,Ios,Xcode,Cocoa,Uiscrollview,我想要得到的是与此滚动视图相同的行为: 我知道这是使用HTML而不是本机API,但我正在尝试将其作为UIKit组件实现 现在,请注意我正在寻找的行为: 请注意,它是一个分页滚动视图,但“页面大小”小于视图的宽度 当您从左向右滚动时,每页都会“捕捉”到最左边的项目 从右端向左滚动时,它会“捕捉”到最右边的项目 同一页,但现在从右向左: 我所尝试的: 我尝试过使滚动视图比它的超级视图小,并覆盖hitTest,这让我有了从左到右的行为 我已经尝试过实现ScrollViewWillendDra

我想要得到的是与此滚动视图相同的行为:

我知道这是使用HTML而不是本机API,但我正在尝试将其作为UIKit组件实现

现在,请注意我正在寻找的行为:

  • 请注意,它是一个分页滚动视图,但“页面大小”小于视图的宽度
  • 当您从左向右滚动时,每页都会“捕捉”到最左边的项目
  • 从右端向左滚动时,它会“捕捉”到最右边的项目
同一页,但现在从右向左:

我所尝试的:

  • 我尝试过使滚动视图比它的超级视图小,并覆盖hitTest,这让我有了从左到右的行为
  • 我已经尝试过实现ScrollViewWillendDraging:withVelocity:targetContentOffset:并设置我想要的targetContentOffset,但由于我无法更改速度,它只是滚动得太慢或太快
  • 我尝试过实现ScrollViewDiEndDecelling:然后将动画设置为正确的偏移量,但scroll视图先停止,然后移动,看起来不自然
  • 我尝试过实现ScrollViewDiEndDraging:WillDecreate:并将动画设置为正确的偏移量,但滚动视图“跳跃”并且动画设置不正确
我没有主意了

谢谢

更新:

最后我用了Rob 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;
}

此解决方案的唯一问题是滑动滚动视图不会产生“反弹”效果。它感觉“卡住了”

您可以启用
paginEnabled
,或者结合使用
ScrollViewDiEndDraging
ScrollViewDiEndDraging
使用
decelerationRate=UIScrollViewDecelerationRateFast
(这将最大限度地降低滚动到一个位置然后再次设置动画到另一个位置的效果)。这可能不是你想要的,但很接近。通过使用
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;
                     }];
}

设置
scrollView.decelerationRate=UIScrollViewDecelerationRateFast
,再加上实现
ScrollViewWillendDraging:withVelocity:targetContentOffset:
,似乎对我使用集合视图很有效

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

@implementation ViewController {
    NSString *cellClassName;
    CGFloat baseOffset;
    CGFloat offsetStep;
}
viewDidLoad
中,我设置了视图的
减速率

- (void)viewDidLoad {
    [super viewDidLoad];
    cellClassName = NSStringFromClass([MyCell class]);
    [self.collectionView registerNib:[UINib nibWithNibName:cellClassName bundle:nil] forCellWithReuseIdentifier:cellClassName];
    self.collectionView.decelerationRate = UIScrollViewDecelerationRateFast;
}
我需要
offsetStep
为符合视图屏幕边界的整数个项目的大小。我在
viewdilayoutsubviews
中计算它:

- (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偏移量。我在
视图中初始化它,显示:

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    baseOffset = self.collectionView.contentOffset.x;
}
然后我需要强制视图以
offsetStep
的步骤滚动。我在
ScrollViewWillendDraging:withVelocity:targetContentOffset:
中这样做。根据
速度
,我通过
offsetStep
增加或减少
baseOffset
。但我将
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;
}
这对我也适用


你可以找到我的测试应用程序。

简单的解决方案,像应用商店一样工作,有速度也有速度<代码>kCellBaseWidth-单元格的宽度

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView
                     withVelocity:(CGPoint)velocity
              targetContentOffset:(inout CGPoint *)targetContentOffset
{
    NSInteger index = lrint(targetContentOffset->x/kCellBaseWidth);
    targetContentOffset->x = index * kCellBaseWidth;
}
叫斯威夫特进来。”Ben的答案是最简单的,正如Ben所提到的,效果非常好。虽然我确实添加了一段来自的关于scrollview结尾的回答。综上所述,这一解决方案似乎非常有效

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

}
func ScrollViewWillendDraging(scrollView:UIScrollView,withVelocity:CGPoint,targetContentOffset:UnsafemeutablePointer){
if((scrollView.contentOffset.x+scrollView.frame.size.width)>=scrollView.contentSize.width){
//不需要快照…我们在滚动视图的末尾
返回
}
let index:CGFloat=CGFloat(lrintf(Float(targetContentOffset.memory.x)/kCellBaseWidth))
targetContentOffset.memory.x=索引*kCellBaseWidth
}

只需将您的
最小行间距
添加到
kCellBaseWidth
,瞧

是否有可能使用UITableView/UICollectionView实现这一点?我想要相同的滚动和抓拍效果,但使用表格/集合可以预览下一个/上一个项目,如iTunes中的项目。在一列表/集合中执行类似于4行的操作。对于所有想要修复“卡住”感觉的人:如果abs(velocity.x)大于0.8,则可以添加/减去两个偏移步骤,而不是一个。不要忘记执行此操作的非常简单的方法。(特别是自动布局。只需制作文字滚动视图,即“单位”的大小。并将其制作为页面。就这么简单。如果需要调整“可触摸区域”,请使用超级巧妙的方法。)你成功做到了吗?有没有使用故事板的例子?@Fattie你能详细说明一下吗?我想要像App Store这样的东西。但是当我模仿UI时(下一个元素在右边部分可见)并启用分页,它将分页到下一个可见区域。也就是说,如果第一个视图显示,第二个项目显示为10磅,它将分页到第二个项目的第11磅。感谢所有对此做出贡献的人。提醒一下:如果在FlowLayout中使用“minimumLineSpacing”,则必须考虑该值。将其添加到kCellBaseWidth。
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

}