制作两个UIScrollViews按照彼此滚动

时间:2011-10-27 15:38:27

标签: iphone objective-c ios uiscrollview

如何让两个滚动视图跟随彼此滚动?

例如,我在屏幕左侧有一个滚动视图(A),其内容可以向上和向下滚动,但不能左右滚动。滚动视图B匹配A的上下,但也可以左右滚动。滚动视图A始终显示在屏幕上。

-----------------------------------------------------------
|             |                                           |
|             |                                           |
|             |                                           |
|     A       |                    B                      |
|             |                                           |
|    scrolls  |                                           |
|   up & down |              scrolls all directions       |
|             |                                           |
-----------------------------------------------------------

我如何制作它以使上下滚动(任一视图)也使另一个视图在相同的上下方向滚动?或者还有另一种方法可以做到这一点吗?

5 个答案:

答案 0 :(得分:40)

将滚动视图A的代理设置为视图控制器...然后...

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  CGPoint offset = scrollViewB.contentOffset;
  offset.y = scrollViewA.contentOffset.y;
  [scrollViewB setContentOffset:offset];
}

如果你想让两者互相跟随,那么为它们设置委托并使用......

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  if([scrollView isEqual:scrollViewA]) {
    CGPoint offset = scrollViewB.contentOffset;
    offset.y = scrollViewA.contentOffset.y;
    [scrollViewB setContentOffset:offset];
  } else {
    CGPoint offset = scrollViewA.contentOffset;
    offset.y = scrollViewB.contentOffset.y;
    [scrollViewA setContentOffset:offset];
  }
}

上面的内容可以重构为一个方法,它接收两个滚动视图并相互匹配。

- (void)matchScrollView:(UIScrollView *)first toScrollView:(UIScrollView *)second {
  CGPoint offset = first.contentOffset;
  offset.y = second.contentOffset.y;
  [first setContentOffset:offset];
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  if([scrollView isEqual:scrollViewA]) {
    [self matchScrollView:scrollViewB toScrollView:scrollViewA];  
  } else {
    [self matchScrollView:scrollViewA toScrollView:scrollViewB];  
  }
}

Swift 3版本:

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView == scrollViewA {
            self.synchronizeScrollView(scrollViewB, toScrollView: scrollViewA)
        }
        else if scrollView == scrollViewB {
            self.synchronizeScrollView(scrollViewA, toScrollView: scrollViewB)
        }
    }

    func synchronizeScrollView(_ scrollViewToScroll: UIScrollView, toScrollView scrolledView: UIScrollView) {
        var offset = scrollViewToScroll.contentOffset
        offset.y = scrolledView.contentOffset.y

        scrollViewToScroll.setContentOffset(offset, animated: false)
    }

答案 1 :(得分:7)

我在iOS 11上试过了Simon Lee的答案。但是效果不是很好。两个滚动视图已同步,但使用他的方法,滚动视图将失去惯性效果(当您释放手指后继续滚动)和弹跳效果。我认为这是因为设置contentOffsetsetContentOffset(offset, animated: false)方法会导致scrollViewDidScroll(_ scrollView: UIScrollView)委托方法的循环调用(请参阅this question

以下是适用于iOS 11的解决方案:

 // implement UIScrollViewDelegate method
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView == self.scrollViewA {
            self.syncScrollView(self.scrollViewB, toScrollView: self.scrollViewA)
        }
        else if scrollView == self.scrollViewB {
            self.syncScrollView(self.scrollViewA, toScrollView: scrollViewB)
        }
    }

    func syncScrollView(_ scrollViewToScroll: UIScrollView, toScrollView scrolledView: UIScrollView) {
        var scrollBounds = scrollViewToScroll.bounds
        scrollBounds.origin.y = scrolledView.contentOffset.y
        scrollViewToScroll.bounds = scrollBounds
    }

因此,我们不是设置contentOffset,而是使用bounds属性将其他scrollView与用户滚动的滚动视图同步。这样,委托方法scrollViewDidScroll(_ scrollView: UIScrollView)不会被循环调用,并且滚动发生得非常平滑,并且具有惯性和弹跳效果,就像单个滚动视图一样。

答案 2 :(得分:2)

伙计们,我知道问题已经解决,但决定在这里与您分享我解决我遇到的类似问题的方法,因为我认为这是一个非常干净的解决方案。基本上,我必须使三个集合视图一起滚动,然后我做的是custom UICollectionView subclass called SharedOffsetCollectionView,那么当您在情节提要中将此类设置为集合视图或直接从代码实例化它时,就可以实例滚动相同。

因此,在SharedOffsetCollectionView中,应用程序中此类的所有集合视图实例将始终滚动。我认为这是一个干净的解决方案,因为它需要在视图控制器中添加零逻辑,所有这些都包含在此外部类中,您只需要将集合视图的类设置为SharedOffsetCollectionView就可以了

可以轻松地将相同的方法转移到UITableViewUIScrollView s

希望对您有所帮助。 :)

我的解决方案写在:

Swift 5.2,XCode 11.4.1

答案 3 :(得分:1)

对我来说完美无缺的是:

  1. 创建UIScrollView的自定义子类
  2. 符合UIGestureRecognizer代表
  3. 覆盖gestureRecognizer(_:shouldRecognizeSimultaneouslyWith:) GestureRecognizerDelegate方法
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
  1. 两个集合视图都必须具有相同的超级视图。将两个手势识别器添加到您的超级视图
yourSuperview.addGestureRecognizer(scrollView1.panGestureRecognizer)
yourSuperview.addGestureRecognizer(scrollView1)

希望这会有所帮助!

答案 4 :(得分:0)

最重要的答案对我来说不太适用,因为我遇到了 scrollViewDidScroll 的循环调用。之所以发生这种情况,是因为设置滚动视图的内容偏移量也会调用 scrollViewDidScoll。我通过在它之间放置一个锁来解决它,这是根据用户是否拖动滚动视图来设置的,因此通过设置内容偏移量不会发生同步:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    self.synchronizeScrollView(offSet: scrollView.contentOffset.y,
                               scrollViewAIsScrolling: scrollView == scrollViewA,
                               isScroller: scrollView.isDragging)
}

private enum Scroller {
    case page, time
}

private var scroller: Scroller? = nil

func synchronizeScrollView(offSet: CGFloat, scrollViewAIsScrolling: Bool,
                           isScroller: scrollView.isDragging) {
    
    let scrollViewToScroll = scrollViewAIsScrolling ? scrollViewB : scrollViewA
    var offset = scrollViewToScroll.contentOffset
    offset.y = offSet
    scrollViewToScroll.setContentOffset(offset, animated: false)
}

可以根据使用的滚动视图数量和拥有它们的人来重构此代码。我不建议让一个控制器作为许多滚动视图的代表。我宁愿用委托来解决。