ios UIScrollView 水平分页,如 Mobile Safari 选项卡

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1220354/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 16:14:13  来源:igfitidea点击:

UIScrollView horizontal paging like Mobile Safari tabs

iosiphonecocoa-touchuiscrollview

提问by firstresponder

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

Mobile Safari 允许您通过在底部输入一种带有页面控件的 UIScrollView 水平分页视图来切换页面。

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

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

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

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

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

如何让 UIScrollView 像移动 Safari 一样显示下一个视图的一些内容?

回答by Ed Marty

A UIScrollViewwith paging enabled will stop at multiples of its frame width (or height). So the first step is to figure out how wide you want your pages to be. Make that the width of the UIScrollView. Then, set your subview's sizes however big you need them to be, and set their centers based on multiples of the UIScrollView's width.

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

Then, since you want to see the other pages, of course, set clipsToBoundsto NOas mhjoy stated. The trick part now is getting it to scroll when the user starts the drag outside the range of the UIScrollView's frame. My solution (when I had to do this very recently) was as follows:

然后,因为要查看其他页,当然,设置clipsToBoundsNO作为mhjoy说明。现在的技巧部分是让它在用户开始拖动超出UIScrollView框架范围时滚动。我的解决方案(当我最近不得不这样做时)如下:

Create a UIViewsubclass (i.e. ClipView) that will contain the UIScrollViewand it's subviews. Essentially, it should have the frame of what you would assume the UIScrollViewwould have under normal circumstances. Place the UIScrollViewin the center of the ClipView. Make sure the ClipView's clipsToBoundsis set to YESif its width is less than that of its parent view. Also, the ClipViewneeds a reference to the UIScrollView.

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

The final step is to override - (UIView *)hitTest:withEvent:inside the ClipView.

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

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

This basically expands the touch area of the UIScrollViewto the frame of its parent's view, exactly what you need.

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

Another option would be to subclass UIScrollViewand override its - (BOOL)pointInside:(CGPoint) point withEvent:(UIEvent *) eventmethod, however you will still need a container view to do the clipping, and it may be difficult to determine when to return YESbased only on the UIScrollView's frame.

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

NOTE:You should also take a look at Juri Pakaste's hitTest:withEvent: modificationif you are having issues with subview user interaction.

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

回答by Juri Pakaste

The ClipViewsolution above worked for me, but I had to do a different -[UIView hitTest:withEvent:]implementation. Ed Marty's version didn't get user interaction working with vertical scrollviews I have inside the horizontal one.

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

The following version worked for me:

以下版本对我有用:

-(UIView*)hitTest:(CGPoint)point withEvent:(UIEvent*)event
{
    UIView* child = nil;
    if ((child = [super hitTest:point withEvent:event]) == self)
        return self.scrollView;     
    return child;
}

回答by Nikolay Tabunchenko

Set frame size of scrollView as your pages size would be:

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

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

Now you can pan on self.view, and content on scrollView will be scrolled.
Also use scrollView.clipsToBounds = NO;to prevent clipping the content.

现在您可以平移self.view,并且将滚动 scrollView上的内容。
也用于scrollView.clipsToBounds = NO;防止剪切内容。

回答by djneely

I wound up going with the custom UIScrollView myself as it was the quickest and simpler method it seemed to me. However, I didn't see any exact code so figured I would share. My needs were for a UIScrollView that had small content and therefore the UIScrollView itself was small to achieve the paging affect. As the post states you can't swipe across. But now you can.

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

Create a class CustomScrollView and subclass UIScrollView. Then all you need to do is add this into the .m file:

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

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

This allows you to scroll from side to side (horizontal). Adjust the bounds accordingly to set your swipe/scrolling touch area. Enjoy!

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

回答by alvinhu

I have made another implementation which can return the scrollview automatically. So it don't need to have an IBOutlet which will limit reusage in project.

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

回答by Kyle

I implemented the upvoted suggestion above, but the UICollectionViewI was using considered anything out of the frame to be off the screen. This caused nearby cells to only render out of bounds when the user was scrolling toward them, which wasn't ideal.

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

What I ended up doing was emulating the behavior of a scrollview by adding the method below to the delegate (or UICollectionViewLayout).

我最终做的是通过将下面的方法添加到委托(或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;
}

This avoids the delegation of the the swipe action entirely, which was also a bonus. The UIScrollViewDelegatehas a similar method called scrollViewWillEndDragging:withVelocity:targetContentOffset:which could be used to page UITableViews and UIScrollViews.

这完全避免了滑动动作的委托,这也是一个好处。该UIScrollViewDelegate有一个名为类似的方法,scrollViewWillEndDragging:withVelocity:targetContentOffset:它可以用于网页UITableViews和UIScrollViews。

回答by Rod Reddekopp

I have another potentially useful modification for the ClipView hitTest implementation. I didn't like having to provide a UIScrollView reference to the ClipView. My implementation below allows you to re-use the ClipView class to expand the hit-test area of anything, and not have to supply it with a reference.

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

回答by David James

Enable firing tap events on child views of the scroll view while supporting the technique of this SO question. Uses a reference to the scroll view (self.scrollView) for readability.

在支持此 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;
}

Add this to your child view to capture the touch event:

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

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

(This is a variation on user1856273's solution. Cleaned up for readability and incorporated Bartserk's bug fix. I thought of editing user1856273's answer but it was too big a change to make.)

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

回答by kolbasek

my version Pressing the button lying on the scroll - work =)

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

- (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;
    }

but only [[self subviews] objectAtIndex: 0] must be a scroll

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

回答by Ben Packard

Here is a Swift answer based on Ed Marty's answer but also including the modification by Juri Pakaste to allow button taps etc inside the scrollview.

这是基于 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
}