问题描述
我想要得到的是与此滚动视图相同的行为:
What I want to get is the same behaviour that this scroll view has:
我知道这是使用 HTML 而不是本机 API,但我正在尝试将其实现为 UIKit 组件.
I know that this is using HTML and not the native API, but I'm trying to implement it as a UIKit component.
现在,我正在寻找的行为:
- 请注意,这是一个分页滚动视图,但页面大小"小于视图的宽度.
- 当您从左到右滚动时,每个页面都会捕捉"到最左边的项目.
- 当您从右端向左滚动时,它会捕捉"到最右边的项目.
同一页,但现在从右到左:
The same page but now right-to-left:
我的尝试:
- 我尝试让滚动视图小于它的超级视图并覆盖 hitTest,这让我得到了从左到右的行为.
- 我已尝试实现 scrollViewWillEndDragging:withVelocity:targetContentOffset: 并设置我想要的 targetContentOffset,但由于我无法更改速度,它只会滚动太慢或太快.
- 我尝试实现 scrollViewDidEndDecelerating: 然后动画到正确的偏移量,但滚动视图首先停止然后移动,它看起来不自然.
- 我尝试实现 scrollViewDidEndDragging:willDecelerate: 然后动画到正确的偏移量,但滚动视图跳跃"并且动画不正确.
我没有想法.
谢谢!
更新:
我最终使用了 Rob Mayoff 的方法,它看起来很干净.我对其进行了更改,使其在速度为 0 时可以工作,例如当用户拖动、停止和松开手指时.
I ended up using Rob Mayoff's method, it looks clean. I changed it so it would work when the velocity is 0, for example when a user drags, stops and releases the finger.
- (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;
}
此解决方案的唯一问题是滑动滚动视图不会产生反弹"效果.感觉卡住"了.
The only problem with this solution is that swiping the scroll view doesn't produce the "bounce" effect. It feels "stuck".
推荐答案
设置scrollView.decelerationRate = UIScrollViewDecelerationRateFast
,结合实现scrollViewWillEndDragging:withVelocity:targetContentOffset:
,好像使用集合视图为我工作.
Setting scrollView.decelerationRate = UIScrollViewDecelerationRateFast
, combined with implementing scrollViewWillEndDragging:withVelocity:targetContentOffset:
, seems to work for me using a collection view.
首先,我给自己一些实例变量:
First, I give myself some instance variables:
@implementation ViewController {
NSString *cellClassName;
CGFloat baseOffset;
CGFloat offsetStep;
}
在viewDidLoad
中,我设置了视图的decelerationRate
:
In viewDidLoad
, I set the view's 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
中计算它:
I need offsetStep
to be the size of an integral number of items that fit in the view's on-screen bounds. I compute it in 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:
:
I need baseOffset
to the be the X offset of the view before scrolling starts. I initialize it in 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
.
Then I need to force the view to scroll in steps of offsetStep
. I do that in scrollViewWillEndDragging:withVelocity:targetContentOffset:
. Depending on the velocity
, I increase or decrease baseOffset
by offsetStep
. But I clamp baseOffset
to a minimum of 0 and a maximum of the 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
是什么.
Note that I don't care what targetContentOffset->x
comes in as.
这具有与最左侧可见项的左边缘对齐的效果,直到用户一直滚动到最后一项.此时它与最右侧可见项的右边缘对齐,直到用户一直滚动到左侧.这似乎与 App Store 应用的行为相符.
This has the effect of aligning to the left edge of the leftmost visible item, until the user scrolls all the way to the last item. At that point it aligns to the right edge of the rightmost visible item, until the user scroll all the way to the left. This seems to match the behavior of the App Store app.
如果这对您不起作用,您可以尝试将最后一行 (targetContentOffset->x = baseOffset
) 替换为:
If that doesn't work for you, you can try replacing the last line (targetContentOffset->x = baseOffset
) with this:
dispatch_async(dispatch_get_main_queue(), ^{
[scrollView setContentOffset:CGPointMake(baseOffset, 0) animated:YES];
});
这也适用于我.
您可以在此 git 存储库中找到我的测试应用 .
You can find my test app in this git repository.
这篇关于如何使 UIScrollView 与图标对齐(如 App Store:功能)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!