iOS 根据另一个 UIScrollView 的 contentOffset 滚动一个 UIScrollView 不同步

iOS Scrolling one UIScrollView based off contentOffset of another goes out of sync

提问人:sudoExclaimationExclaimation 提问时间:11/26/2018 最后编辑:sudoExclaimationExclaimation 更新时间:11/26/2018 访问量:440

问:

编辑:我在底部附上了一个简单的演示项目dropbox链接

我有一个 UI,顶部是 UICollectionView,底部是滚动视图。我希望 collectionview 中的滚动也同步滚动 scrollview。我已经在 scrollview 中禁用了用户交互,因此只有 collectionview 才能影响其中的滚动。

出于此测试目的,每个 collectionview 项为 150px。

滚动视图中的 UIViews 大小为屏幕宽度。因此,对于 collectionview 项的每次滚动,我需要按屏幕宽度滚动 scrollview。为了实现这一点,我正在计算 collectionview 偏移量变化的距离,然后将其除以单元格宽度 (150),然后乘以 scrollview 的宽度。

我正在尝试实现以下 UI:

开始:

enter image description here

将 collectionview 滚动到单元格 1:

enter image description here

将 collectionview 滚动到单元格 2:

enter image description here

这在前几次都很好,但是当我将 collectionview 来回滚动几次到更长的距离(假设单元格 10 -> 0 -> 10 -> 0 等)时,滚动视图会因“微小”距离而不同步。为了说明这一点,请注意滚动视图右边缘的第二个 UIView 中的“黄色”颜色:

enter image description here

我也可以通过 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
iOS iPhone UI滚动视图 UIConcetionView

评论

0赞 matt 11/26/2018
那么问题解决了吗?如果是这样,请回答您自己的问题并接受您自己的答案。
0赞 sudoExclaimationExclaimation 11/26/2018
@matt它没有解决,因为我的解决方法有效,但 1.我仍然想知道实际问题是什么和 2.解决方法看起来不太好,因为您看到 scrollView 停止,然后再次滚动一点以修复自身。所以看起来有点丑。

答:

0赞 Dan Lee 11/26/2018 #1

请注意,方法帧中并没有真正设置为真实设备。viewDidLoadmyScrollView

因此,在视图的 init 中,视图的宽度和高度在代码中可能是错误的。

评论

0赞 sudoExclaimationExclaimation 11/26/2018
您对 viewDidLoad 中的宽度不正确是正确的,我修复了使用 viewDidLayoutSubviews 的问题。但是,这并不能解决偏移量不同步的实际问题,该问题仍然存在。
0赞 Dan Lee 11/26/2018
删除方法和 init &,其中为 0。系统调用似乎延迟了。scrollViewWillBeginDraggingpreviousOffset_HeaderpreviousOffset_ScrollviewvideoDidLoadscrollViewWillBeginDragging
0赞 Orgmir 11/26/2018 #2

有几件事你应该以不同的方式做,你计算偏移量的方式,而不是使用回调和。我重写了相关代码:UIScrollViewDelegatescrollViewDidEndDragging: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];
}