88

Mobile Safari allows you to switch pages by entering a sort of UIScrollView horizontal paging view with a page control at the bottom.

I am trying to replicate this particular behavior where a horizontally scrollable UIScrollView shows some of the next view's content.

The Apple provided example: PageControl shows how to use a UIScrollView for horizontal paging, but all views take up the whole screen width.

How do I get a UIScrollView to show some content of the next view like mobile Safari does?

4

11 回答 11

265

启用分页的 AUIScrollView将在其帧宽度(或高度)的倍数处停止。所以第一步是弄清楚你希望你的页面有多宽。使其宽度为UIScrollView. 然后,将您的子视图的大小设置为您需要的大小,并根据UIScrollView宽度的倍数设置它们的中心。

然后,既然要查看其他页面,当然要设置clipsToBoundsNOmhjoy所说的那样。UIScrollView现在的技巧部分是当用户在' 的框架范围之外开始拖动时让它滚动。我的解决方案(当我最近不得不这样做时)如下:

创建一个包含它的子视图的子UIView类(即)。本质上,它应该具有您在正常情况下所假设的框架。将放在 的中心。如果's的宽度小于其父视图的宽度,请确保将其设置为。此外,需要引用.ClipViewUIScrollViewUIScrollViewUIScrollViewClipViewClipViewclipsToBoundsYESClipViewUIScrollView

最后一步是- (UIView *)hitTest:withEvent:ClipView.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
  return [self pointInside:point withEvent:event] ? scrollView : nil;
}

这基本上将触摸区域扩展UIScrollView到其父视图的框架,正是您所需要的。

另一种选择是子类化UIScrollView并覆盖其- (BOOL)pointInside:(CGPoint) point withEvent:(UIEvent *) event方法,但是您仍然需要一个容器视图来进行剪辑,并且可能很难YES仅根据UIScrollView' 的框架确定何时返回。

注意: 如果您在子视图用户交互方面遇到问题,您还应该查看 Juri Pakaste 的hitTest:withEvent: 修改。

于 2009-08-03T03:48:49.503 回答
70

上面的ClipView解决方案对我有用,但我必须做一个不同的-[UIView hitTest:withEvent:]实现。Ed Marty 的版本没有让用户交互与我在水平滚动视图中的垂直滚动视图一起工作。

以下版本对我有用:

-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
    UIView* child = nil;
    if ((child = [super hitTest:point withEvent:event]) == self)
        return self.scrollView;     
    return child;
}
于 2009-09-03T12:25:46.520 回答
8

将 scrollView 的框架大小设置为您的页面大小:

[self.view addSubview:scrollView];
[self.view addGestureRecognizer:mainScrollView.panGestureRecognizer];

现在可以平移了self.view,scrollView 上的内容会滚动。
也用于scrollView.clipsToBounds = NO;防止剪辑内容。

于 2014-12-09T13:35:48.443 回答
5

我最终自己使用了自定义 UIScrollView,因为它是我认为最快和最简单的方法。但是,我没有看到任何确切的代码,所以我想我会分享。我的需求是内容较小的 UIScrollView,因此 UIScrollView 本身很小,无法实现分页效果。正如帖子所述,您无法滑动。但现在你可以了。

创建一个类 CustomScrollView 和子类 UIScrollView。然后您需要做的就是将其添加到 .m 文件中:

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
  return (point.y >= 0 && point.y <= self.frame.size.height);
}

这允许您从一侧滚动到另一侧(水平)。相应地调整边界以设置您的滑动/滚动触摸区域。享受!

于 2013-11-19T03:06:47.727 回答
4

我做了另一个可以自动返回滚动视图的实现。所以它不需要有一个 IBOutlet 来限制项目中的重用。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if ([self pointInside:point withEvent:event]) {
        for (id view in [self subviews]) {
            if ([view isKindOfClass:[UIScrollView class]]) {
                return view;
            }
        }
    }
    return nil;
}
于 2012-08-17T10:28:59.283 回答
3

对于 ClipView hitTest 实现,我还有另一个可能有用的修改。我不喜欢提供对 ClipView 的 UIScrollView 引用。我在下面的实现允许您重新使用 ClipView 类来扩展任何东西的命中测试区域,而不必为其提供参考。

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    if (([self pointInside:point withEvent:event]) && (self.subviews.count >= 1))
    {
        // An extended-hit view should only have one sub-view, or make sure the
        // first subview is the one you want to expand the hit test for.
        return [self.subviews objectAtIndex:0];
    }

    return nil;
}
于 2012-03-02T21:27:10.057 回答
3

我实施了上面赞成的建议,但UICollectionView我使用的任何东西都被认为是超出屏幕的。这导致附近的单元格仅在用户向它们滚动时才会渲染出边界,这并不理想。

我最终做的是通过将下面的方法添加到委托(或UICollectionViewLayout)来模拟滚动视图的行为。

- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset withScrollingVelocity:(CGPoint)velocity
{    
  if (velocity.x > 0) {
    proposedContentOffset.x = ceilf(self.collectionView.contentOffset.x / pageSize) * pageSize;
  }
  else {
    proposedContentOffset.x = floorf(self.collectionView.contentOffset.x / pageSize) * pageSize;
  }

  return proposedContentOffset;
}

这完全避免了滑动操作的授权,这也是一个奖励。UIScrollViewDelegate有一个类似的方法被称为可scrollViewWillEndDragging:withVelocity:targetContentOffset:用于分页 UITableViews 和 UIScrollViews。

于 2012-10-29T17:01:34.650 回答
3

在支持这个 SO question 的技术的同时,在滚动视图的子视图上启用触发点击事件。使用对滚动视图 (self.scrollView) 的引用以提高可读性。

- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
    UIView *hitView = nil;
    NSArray *childViews = [self.scrollView subviews];
    for (NSUInteger i = 0; i < childViews.count; i++) {
        CGRect childFrame = [[childViews objectAtIndex:i] frame];
        CGRect scrollFrame = self.scrollView.frame;
        CGPoint contentOffset = self.scrollView.contentOffset;
        if (childFrame.origin.x + scrollFrame.origin.x < point.x + contentOffset.x &&
            point.x + contentOffset.x < childFrame.origin.x + scrollFrame.origin.x + childFrame.size.width &&
            childFrame.origin.y + scrollFrame.origin.y < point.y + contentOffset.y &&
            point.y + contentOffset.y < childFrame.origin.y + scrollFrame.origin.y + childFrame.size.height
        ){
            hitView = [childViews objectAtIndex:i];
            return hitView;
        }
    }
    hitView = [super hitTest:point withEvent:event];
    if (hitView == self)
        return self.scrollView;
    return hitView;
}

将此添加到您的子视图以捕获触摸事件:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

(这是 user1856273 解决方案的一个变体。为提高可读性而进行了清理,并结合了 Bartserk 的错误修复。我想编辑 user1856273 的答案,但改变太大了。)

于 2013-07-25T17:49:49.633 回答
3

这是一个基于 Ed Marty 的答案的 Swift 答案,但也包括 Juri Pakaste 的修改,以允许在滚动视图中点击按钮等。

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    let view = super.hitTest(point, with: event)
    return view == self ? scrollView : view
}
于 2019-08-19T17:21:34.237 回答
2

我的版本 按下卷轴上的按钮 - 工作 =)

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView* child = nil;
    for (int i=0; i<[[[[self subviews] objectAtIndex:0] subviews] count];i++) {

        CGRect myframe =[[[[[self subviews] objectAtIndex:0] subviews]objectAtIndex:i] frame];
        CGRect myframeS =[[[self subviews] objectAtIndex:0] frame];
        CGPoint myscroll =[[[self subviews] objectAtIndex:0] contentOffset];
        if (myframe.origin.x < point.x && point.x < myframe.origin.x+myframe.size.width &&
            myframe.origin.y+myframeS.origin.y < point.y+myscroll.y && point.y+myscroll.y < myframe.origin.y+myframeS.origin.y +myframe.size.height){
            child = [[[[self subviews] objectAtIndex:0] subviews]objectAtIndex:i];
            return child;
        }


    }

    child = [super hitTest:point withEvent:event];
    if (child == self)
        return [[self subviews] objectAtIndex:0];
    return child;
    }

但只有 [[self subviews] objectAtIndex: 0] 必须是滚动

于 2013-03-15T16:57:07.853 回答
0

斯威夫特 5 / iOS 15

TLDR - 做公认的答案,但使用我的hitTest(...)以下版本

我希望用户能够向左或向右点击页面并将其滚动到中心,但是现有的解决方案不起作用(为此我在每个页面上添加了手势识别器,并且从未触发页面的手势scrollView框外)。

接受的答案对我来说几乎很有效,但hitTest覆盖并不好 - 每个页面上的元素都没有响应触摸(我的 UISwitches 不会对点击做出反应)。允许在每个页面上进行交互的建议修改hitTest(...)也不完整,因为在scrollView边界外的任何页面上的点击都不会发送到页面(建议的答案只是将触摸传递给scrollView,而不会将触摸传递到其内容位于其框架之外)。

我改用以下代码,它运行良好。我不喜欢这样访问scrollView's 的内容视图,但代码不会崩溃并安全地回退到原始建议代码。如果有人知道将触摸传递给scrollView's 内容视图的更好方法,我将不胜感激。

public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    guard let scrollViewContentView = scrollView.subviews.first else {
        // fallback behavior - user can scroll carousel if touch starts outside the scrollView frame, but taps on elements outside the scrollView frame will be ignored
        let view = super.hitTest(point, with: event)
        return view == self ? scrollView : view
    }
    
    let convertedPoint = scrollViewContentView.convert(point, from: self)
    return self.point(inside: point, with: event) ? scrollViewContentView.hitTest(convertedPoint, with: event) : nil
}
于 2021-09-29T00:37:53.963 回答