UIScrollView horizontal paging like Mobile Safari tabs

Mobile Safari allows you to switch between pages by introducing a horizontal view of the UIScrollView swap with the bottom page control.

I am trying to reproduce this particular behavior when a horizontally scrolled UIScrollView displays some of the following presentation content.

Apple cited example: PageControl shows how to use UIScrollView for horizontal search call, but all views occupy the entire width of the screen.

How to get UIScrollView to display some content of the following type, for example, for mobile Safari?

+88
ios iphone cocoa-touch uiscrollview
Aug 03 '09 at 1:10
source share
10 answers

A UIScrollView with paging enabled will stop at the edges of its width (or height). So, the first step is to find out how wide you want your pages to be. Make the width of the UIScrollView . Then set the subtask sizes, whatever you need, and set their centers based on multiples of UIScrollView .

Then, since you want to see other pages, of course, set clipsToBounds to NO , as indicated by mhjoy. Now the trick is getting it to scroll when the user starts dragging and dropping outside the UIScrollView frame. My solution (when I needed to do this recently) was as follows:

Create a subclass of UIView (i.e. ClipView ) that will contain the UIScrollView and its subclauses. Essentially, it should have the structure of what you would assume that a UIScrollView would have normal circumstances. Place the UIScrollView in the center of the ClipView . Make sure that ClipView clipsToBounds set to YES if its width is less than its parent view. In addition, ClipView requires a link to UIScrollView .

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

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

This basically extends the touch area of ​​the UIScrollView to the frame of its parent view, exactly what you need.

Another option would be to subclass UIScrollView and override its method - (BOOL)pointInside:(CGPoint) point withEvent:(UIEvent *) event , however, to perform cropping you still need a container view, and it can be difficult to determine when to return YES based only on on a UIScrollView .

NOTE. You should also take a look at Juri Pakaste hitTest: withEvent: modify if you are having problems interacting with the subview user.

+260
Aug 03 '09 at 3:48
source share

The ClipView solution above worked for me, but I had to implement another implementation -[UIView hitTest:withEvent:] . The version of Ed Marty did not provide user interaction with vertical scrolls, which I have inside the horizontal.

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; } 
+69
03 Sep '09 at 12:25
source share

Set the scrollView frame size as the size of your pages:

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

Now you can pan to self.view , and the contents of the scroll will scroll.
Also use scrollView.clipsToBounds = NO; to prevent clipping of content.

+8
Dec 09 '14 at 13:35
source share

I ended up working with a custom UIScrollView, as it was the fastest and easiest way that seemed to me. However, I have not seen any exact code that I would share. My needs were for a UIScrollView, which had little content, and so the UIScrollView itself was small in order to achieve the impact of a search call. As the messages say, you cannot zip. But now you can.

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

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

This allows you to move from side to side (horizontal). Adjust the borders to adjust the touch / scroll area. Enjoy it!

+5
Nov 19 '13 at 3:06 on
source share

I made another implementation that can automatically return scrollview. Thus, you do not need to have an IBOutlet that limits reuse in the project.

 - (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; } 
+4
Aug 17 '12 at 10:28
source share

I have another potentially useful modification for implementing ClipView hitTest. I did not like providing a link to the UIScrollView for ClipView. My implementation below allows you to reuse the ClipView class to expand the scope of testing when testing, and you do not need to specify a link.

 - (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; } 
+3
Mar 02 2018-12-12T00:
source share

I implemented the above sentence above, but the UICollectionView that I used, read something from the frame to be off-screen. This made neighboring cells go beyond only when the user scrolled towards them, which was not ideal.

What I ended up with is emulating the scroll behavior by adding the method below to the delegate (or 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 completely eliminates the delegation of the napkin action, which is also a bonus. UIScrollViewDelegate has a similar method called scrollViewWillEndDragging:withVelocity:targetContentOffset: which can be used to create UITableViews and UIScrollViews.

+3
Oct 29 '12 at 17:01
source share

Include trigger events on scroll child views, supporting the technique of this SO issue. Uses a scroll view link (self.scrollView) for readability.

 - (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's view to capture a touch event:

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

(This is a variation on user1856273. Removed for readability and Bartserk bug fixes included. I was thinking of editing user1856273's answer, but that was too much a change to make.)

+3
Jul 25 '13 at 17:49
source share

my version Pressing a button on a 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] should be scroll

+2
Mar 15 '13 at 16:57
source share

Here is the Swift answer, based on Ed Marty's answer, but also including a modification of Yuri Pakaste that allows you to click buttons, etc. Inside the scroll.

 override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let view = super.hitTest(point, with: event) return view == self ? scrollView : view } 
0
Aug 19 '19 at 17:21
source share



All Articles