Ios UIScrollView类似于移动Safari选项卡的水平分页

Ios UIScrollView类似于移动Safari选项卡的水平分页,ios,iphone,cocoa-touch,uiscrollview,Ios,Iphone,Cocoa Touch,Uiscrollview,MobileSafari允许您通过输入一种UIScrollView水平分页视图(底部有一个页面控件)来切换页面 我试图复制这种特殊的行为,即水平滚动的UIScrollView显示下一个视图的一些内容 苹果提供的示例:PageControl显示了如何使用UIScrollView进行水平分页,但所有视图都占据整个屏幕宽度 如何获得UIScrollView以像mobile Safari那样显示下一个视图的某些内容?启用分页的UIScrollView将在其帧宽度(或高度)的倍数处停止。因此,第一步是确定

MobileSafari允许您通过输入一种UIScrollView水平分页视图(底部有一个页面控件)来切换页面

我试图复制这种特殊的行为,即水平滚动的UIScrollView显示下一个视图的一些内容

苹果提供的示例:PageControl显示了如何使用UIScrollView进行水平分页,但所有视图都占据整个屏幕宽度


如何获得UIScrollView以像mobile Safari那样显示下一个视图的某些内容?

启用分页的
UIScrollView将在其帧宽度(或高度)的倍数处停止。因此,第一步是确定页面的宽度。使其成为
UIScrollView
的宽度。然后,根据需要设置子视图的大小,并根据
UIScrollView
宽度的倍数设置其中心

然后,由于您想查看其他页面,当然,请将
clipstobunds
设置为
NO
,如前所述。现在的诀窍是当用户在
UIScrollView
的框架范围外开始拖动时,让它滚动。我的解决方案(当我最近不得不这样做时)如下:

创建一个
UIView
子类(即
ClipView
),该子类将包含
UIScrollView
及其子视图。本质上,它应该具有您认为在正常情况下
UIScrollView
应该具有的框架。将
UIScrollView
放在
ClipView
的中心。如果
ClipView
的宽度小于其父视图的宽度,请确保将其
clipToBounds
设置为
YES
。另外,
ClipView
需要对
UIScrollView
的引用

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
  return [self pointInside:point withEvent:event] ? scrollView : nil;
}
最后一步是在
ClipView
中覆盖
-(UIView*)hitTest:withEvent:

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
  return [self pointInside:point withEvent:event] ? scrollView : nil;
}
这基本上将
UIScrollView
的触摸区域扩展到其父视图的框架,这正是您所需要的

另一个选项是子类化
UIScrollView
并覆盖其
-(BOOL)pointInside:(CGPoint)pointwithevent:(UIEvent*)event
方法,但是仍然需要容器视图来执行剪裁,仅根据
UIScrollView
的框架,可能很难确定何时返回
YES


注意:如果您在子视图用户交互方面遇到问题,您也应该看看Juri Pakaste的解决方案。

上面的
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;
}

我对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;
}

我已经做了另一个可以自动返回scrollview的实现。所以它不需要一个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;
}

我实现了上面的向上投票的建议,但是我使用的
UICollectionView
认为帧外的任何内容都不在屏幕上。这导致当用户向附近的单元格滚动时,附近的单元格仅在边界外渲染,这是不理想的

我最后做的是通过将下面的方法添加到委托(或
UICollectionViewLayout
)来模拟scrollview的行为

这完全避免了刷卡操作的委托,这也是一个额外的好处。
UIScrollViewDelegate
有一个类似的方法,名为
ScrollViewWillendDraging:withVelocity:targetContentOffset:
,可用于分页UITableView和UIScrollView。

我的版本 按下滚动条上的按钮-工作=)

-(UIView*)hitTest:(CGPoint)point with event:(UIEvent*)event{
UIView*child=nil;

对于(int i=0;i启用在滚动视图的子视图上触发点击事件,同时支持此SO问题的技术。使用对滚动视图(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;
}

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

我自己最终选择了自定义UIScrollView,因为它是我认为最快捷、最简单的方法。但是,我没有看到任何确切的代码,所以我想我会与大家分享。我需要的是一个内容小的UIScrollView,因此UIScrollView本身很小,无法实现分页效果。正如帖子所说的,你不能swipe穿过。但现在你可以了

创建CustomScrollView类和UIScrollView子类。然后,您只需将其添加到.m文件中:

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

return(point.y>=0&&point.y将scrollView的框架大小设置为页面大小:

[self.view addSubview:scrollView];
[self.view addGestureRecognizer:mainScrollView.panGestureRecognizer];
现在您可以在
self.view
上平移,滚动视图上的内容将被滚动。

还可以使用
scrollView.clipsToBounds=NO;
来防止剪切内容。

以下是基于Ed Marty的答案的快速回答,但也包括Juri Pakaste的修改,允许在scrollView内点击按钮等

override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    let view = super.hitTest(point, with: event)
    return view == self ? scrollView : view
}

只是一个想法…试着让滚动视图的边界比屏幕小,然后摆弄着让视图正确显示。(并将滚动视图的clipsToBounds设置为NO)我想让页面比uiscrollview的宽度(水平滚动)大。Mjoy的想法是真的
[self.view addSubview:scrollView];
[self.view addGestureRecognizer:mainScrollView.panGestureRecognizer];
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
    let view = super.hitTest(point, with: event)
    return view == self ? scrollView : view
}