提问人:sudoExclaimationExclaimation 提问时间:11/26/2018 最后编辑:sudoExclaimationExclaimation 更新时间:11/26/2018 访问量:440
iOS 根据另一个 UIScrollView 的 contentOffset 滚动一个 UIScrollView 不同步
iOS Scrolling one UIScrollView based off contentOffset of another goes out of sync
问:
编辑:我在底部附上了一个简单的演示项目dropbox链接
我有一个 UI,顶部是 UICollectionView,底部是滚动视图。我希望 collectionview 中的滚动也同步滚动 scrollview。我已经在 scrollview 中禁用了用户交互,因此只有 collectionview 才能影响其中的滚动。
出于此测试目的,每个 collectionview 项为 150px。
滚动视图中的 UIViews 大小为屏幕宽度。因此,对于 collectionview 项的每次滚动,我需要按屏幕宽度滚动 scrollview。为了实现这一点,我正在计算 collectionview 偏移量变化的距离,然后将其除以单元格宽度 (150),然后乘以 scrollview 的宽度。
我正在尝试实现以下 UI:
开始:
将 collectionview 滚动到单元格 1:
将 collectionview 滚动到单元格 2:
这在前几次都很好,但是当我将 collectionview 来回滚动几次到更长的距离(假设单元格 10 -> 0 -> 10 -> 0 等)时,滚动视图会因“微小”距离而不同步。为了说明这一点,请注意滚动视图右边缘的第二个 UIView 中的“黄色”颜色:
我也可以通过 NSLogging scrollview 的 contentOffset 来看到这个问题(注意它如何在几次后开始以 0.5 的速度不同步):
2018-11-25 19:24:28.273278-0500 ScrollViewMatchTest[19412:1203912] Finished: 0
2018-11-25 19:24:31.606521-0500 ScrollViewMatchTest[19412:1203912] Finished: 0.5
2018-11-25 19:24:55.173709-0500 ScrollViewMatchTest[19412:1203912] Finished: 1.5
2018-11-25 19:25:03.007528-0500 ScrollViewMatchTest[19412:1203912] Finished: 1.5
2018-11-25 19:25:07.841096-0500 ScrollViewMatchTest[19412:1203912] Finished: 2.5
2018-11-25 19:26:57.634429-0500
我不太确定是什么导致了这个问题,我已经尝试了很多方法来解决这个问题,但徒劳无功。我可以找出一种解决方法(重置偏移量并在滚动完成时使其恢复同步),但我想知道为什么会导致这种不同步问题。
通过将 scrollView 的 contentOffset 重置为关闭屏幕宽度的倍数来解决此问题:
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
if (scrollView==self.myCollectionView) {
NSLog(@"Finished: %g",self.myScrollView.contentOffset.x);
NSLog(@"Closest: %g",RoundTo(self.myScrollView.contentOffset.x, self.myScrollView.frame.size.width));
[self.myScrollView setContentOffset:CGPointMake(RoundTo(self.myScrollView.contentOffset.x, self.myScrollView.frame.size.width), self.myScrollView.contentOffset.y) animated:YES];
}
}
float RoundTo(float number, float to)
{
if (number >= 0) {
return to * floorf(number / to + 0.5f);
}
else {
return to * ceilf(number / to - 0.5f);
}
}
解决方法结束解决方案
我也附上了一个简单的演示项目来说明这个问题(运行应用程序并在顶部滚动视图上来回滚动几次): https://www.dropbox.com/s/e2bzgo6abq5wmgw/ScrollViewMatchTest.zip?dl=0
代码如下:
#import "ViewController.h"
#define countOfItems 50
@interface ViewController (){
CGFloat previousOffset_Header;
CGFloat previousOffset_Scrollview;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.myCollectionView registerNib:[UINib nibWithNibName:@"MyCell" bundle:nil] forCellWithReuseIdentifier:@"cell"];
[self.myCollectionView reloadData];
for (NSInteger i=0; i<countOfItems; i++) {
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(i*self.myScrollView.frame.size.width, 0, self.myScrollView.frame.size.width, self.myScrollView.frame.size.height)];
myView.backgroundColor=i%2==0?[UIColor blueColor]:[UIColor yellowColor];
[self.myScrollView addSubview:myView];
}
self.myScrollView.contentSize=CGSizeMake(countOfItems*self.myScrollView.frame.size.width, self.myScrollView.frame.size.height);
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section{
return countOfItems;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
MyCollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"cell" forIndexPath:indexPath];
cell.backgroundColor = indexPath.item%2==0?[UIColor blueColor]:[UIColor yellowColor];
cell.myLabel.text=[NSString stringWithFormat:@"%ld",indexPath.item];
return cell;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
if (scrollView==self.myCollectionView) {
previousOffset_Header = self.myCollectionView.contentOffset.x;
previousOffset_Scrollview = self.myScrollView.contentOffset.x;
}
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (scrollView==self.myCollectionView) {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self performSelector:@selector(scrollViewDidEndScrollingAnimation:) withObject:scrollView afterDelay:0.1 inModes:@[NSRunLoopCommonModes]];
CGFloat offsetToMoveBy = (self.myCollectionView.contentOffset.x-previousOffset_Header)*(self.myScrollView.frame.size.width/150);
previousOffset_Scrollview = previousOffset_Scrollview +offsetToMoveBy;
[self.myScrollView setContentOffset:CGPointMake(previousOffset_Scrollview, self.myScrollView.contentOffset.y) animated:NO];
previousOffset_Header = self.myCollectionView.contentOffset.x;
}
}
-(void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView
{
[NSObject cancelPreviousPerformRequestsWithTarget:self];
NSLog(@"Finished: %g",self.myScrollView.contentOffset.x);
}
@end
答:
请注意,方法帧中并没有真正设置为真实设备。viewDidLoad
myScrollView
因此,在视图的 init 中,视图的宽度和高度在代码中可能是错误的。
评论
scrollViewWillBeginDragging
previousOffset_Header
previousOffset_Scrollview
videoDidLoad
scrollViewWillBeginDragging
有几件事你应该以不同的方式做,你计算偏移量的方式,而不是使用回调和。我重写了相关代码:UIScrollViewDelegate
scrollViewDidEndDragging:willDecelerate:
scrollViewDidEndDecelerating:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
if (scrollView==self.myCollectionView) {
[self calculateScrollviewOffset:self.myCollectionView];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (scrollView == self.myCollectionView && !decelerate) {
[self calculateEndPosition: self.myCollectionView];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
if (scrollView == self.myCollectionView) {
[self calculateEndPosition: self.myCollectionView];
}
}
- (void) calculateScrollviewOffset:(UICollectionView *) collectionView {
CGFloat cellWidth = 150.0;
CGFloat percentageMoved = collectionView.contentOffset.x / cellWidth;
[self setScrollViewOffset:percentageMoved animated:false];
}
- (void) calculateEndPosition:(UICollectionView*) collectionView {
CGFloat cellWidth = 150.0;
// NOTE: Add 0.5 to play around with the end animation positioning
CGFloat percentageMoved = floor(collectionView.contentOffset.x / cellWidth);
CGFloat collectionViewFixedOffset = (percentageMoved * cellWidth);
[collectionView setContentOffset:CGPointMake(collectionViewFixedOffset, collectionView.contentOffset.y) animated:true];
[self setScrollViewOffset:percentageMoved animated:true];
}
- (void) setScrollViewOffset:(CGFloat) percentageMoved animated:(BOOL) animated {
CGFloat newOffsetX = percentageMoved * self.myScrollView.frame.size.width;
[self.myScrollView setContentOffset:CGPointMake(newOffsetX, self.myScrollView.contentOffset.y) animated: animated];
}
评论