Smooth user paging for UIScrollView

I have two (maybe more) views in a UIScrollView and you want to use paging with it. The problem occurs when I try to use the default paging option for UIScrollView, since the views have different widths, they cannot be displayed correctly.

So, I implemented custom paging code that works. However, when the scrolls are slow, it does not work as expected. (It returns to its original position without animation.)

This is how I am currently performing user paging through UIScrollViewDelegate

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) { if direction == 1{ targetContentOffset.pointee.x = 0 }else{ targetContentOffset.pointee.x = 100 } } func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { if scrollView.panGestureRecognizer.translation(in: scrollView.superview).x > 0 { direction = 1 } else { direction = 0 } } 

What I want: enter image description here What I have: enter image description here

+7
ios swift
source share
4 answers

try below example for custom class UIScrollView

 import UIKit public class BaseScrollViewController: UIViewController, UIScrollViewDelegate { public var leftVc: UIViewController! public var middleVc: UIViewController! public var rightVc: UIViewController! public var initialContentOffset = CGPoint() // scrollView initial offset public var maximumWidthFirstView : CGFloat = 0 public var scrollView: UIScrollView! public class func containerViewWith(_ leftVC: UIViewController, middleVC: UIViewController, rightVC: UIViewController) -> BaseScrollViewViewController { let container = BaseScrollViewViewController() container.leftVc = leftVC container.middleVc = middleVC container.rightVc = rightVC return container } override public func viewDidLoad() { super.viewDidLoad() setupHorizontalScrollView() } func setupHorizontalScrollView() { scrollView = UIScrollView() scrollView.isPagingEnabled = true scrollView.showsHorizontalScrollIndicator = false scrollView.bounces = false let view = ( x: self.view.bounds.origin.x, y: self.view.bounds.origin.y, width: self.view.bounds.width, height: self.view.bounds.height ) scrollView.frame = CGRect(x: view.x, y: view.y, width: view.width, height: view.height ) self.view.addSubview(scrollView) let scrollWidth = 3 * view.width let scrollHeight = view.height scrollView.contentSize = CGSize(width: scrollWidth, height: scrollHeight) leftVc.view.frame = CGRect(x: 0, y: 0, width: view.width, height: view.height ) middleVc.view.frame = CGRect(x: view.width, y: 0, width: view.width, height: view.height ) rightVc.view.frame = CGRect(x: 2 * view.width, y: 0, width: view.width, height: view.height ) addChildViewController(leftVc) addChildViewController(middleVc) addChildViewController(rightVc) scrollView.addSubview(leftVc.view) scrollView.addSubview(middleVc.view) scrollView.addSubview(rightVc.view) leftVc.didMove(toParentViewController: self) middleVc.didMove(toParentViewController: self) rightVc.didMove(toParentViewController: self) scrollView.contentOffset.x = middleVc.view.frame.origin.x scrollView.delegate = self } public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { self.initialContentOffset = scrollView.contentOffset } public func scrollViewDidScroll(_ scrollView: UIScrollView) { if maximumWidthFirstView != 0 { if scrollView.contentOffset.x < maximumWidthFirstView { scrollView.isScrollEnabled = false let newOffset = CGPoint(x: maximumWidthFirstView, y: self.initialContentOffset.y) self.scrollView!.setContentOffset(newOffset, animated: false) scrollView.isScrollEnabled = true } } } } 

Using BaseScrollViewController

 let left = FirstController.init() let middle = MiddleController() let right = RightController.init() let container = BaseScrollViewController.containerViewWith(left,middleVC: middle,rightVC: right) container.maximumWidthFirstView = 150 

Output:

simulator screen shot 14-sep-2017 12 57 09 pmsimulator screen shot 14-sep-2017 12 57 05 pmsimulator screen shot 14-sep-2017 12 59 38 pm

GitHub gist Code Example: https://gist.github.com/mspvirajpatel/58dac2fae0d3b4077a0cb6122def6570

+2
source share

Earlier, I wrote a short note about this problem, and I will copy / paste it, since it is no longer available anywhere. This may not be a specific answer, and the codes are pretty old, but I hope this helps you to some extent.


If you used the paging function included in UIScrollView , you might also be tempted to adjust the width of each page instead of the standard, boring, swap frame widths. It would be great if you could stop scrolling at shorter or longer intervals than just multiple frame widths. Surprisingly, there is no built-in way to adjust page width even in our latest iOS7 SDK. There are several ways to achieve user paging, but none of them, I would say, was complete. At the moment, you need to choose one of the following solutions.

1. Change the frame size of your UIScrollView

Alexander Repti presented a good and easy solution to this problem, and also added an example code through his blog: http://blog.proculo.de/archives/180-Paging-enabled-UIScrollView-With-Previews.html p>

In principle, an instruction can be irrigated to the following steps:

  • Subclass UIView and override hitTest: withEvent:
 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { if ([self pointInside:point withEvent:event]) { if ([self.subviews count] == 0) return nil; else return [self.subviews lastObject]; } return nil; } 
  1. Include UIScrollView as a subquery of the above subclass of UIView .

  2. Adjust the frame size of your UIScrollView .

  3. Set the clipsToBound property of your scroll view to NO .

  4. Set the pagingEnabled property of your scroll view to YES .

As you can see, I just assumed that there is only one view (scrollView!) For your subclass of UIView . Since you pass all the touch events that occurred in the UIView subclass to your UIScrollView , you can scroll the content by panning in the UIView subclass, but the swap width will be determined by the width of the UIScrollView .

The best part of this approach is that you get the true feeling and responsiveness, as it is somewhat difficult to emulate swap using the UIScrollView delegate methods.

The only problem I found with this solution is that the width of all pages should be the same. You cannot set different widths on different pages. If you try to resize the scrollView frame dynamically, you will find that there are a number of new problems that arise. Before attempting to fix these crashes, you can check out the other two solutions using the UIScrollView delegates.

2. scrollViewWillEndDragging: withVelocity: targetContentOffset

scrollViewWillEndDragging: withVelocity: targetContentOffset is one of the latest UIScrollView delegation methods (iOS 5.0 or higher) that gives you more information than other old ones.

Since you get the scrollView speed right after lifting your finger up from the screen, we can determine the direction of the scrolled content. The final argument, targetContentOffset, not only gives you the expected offset when the scrolling stops eventually, you can also assign a CGPoint value so that the scroll scroll scrolls to the desired point.

 targetContentOffset = CGPointMake(500, 0); 

or

 targetContentOffset->x = 500; 

However, this will not work the way you think, because you cannot set the scroll speed of the animation. This is more like scrolling a View that stops at the right point, rather than in place. I should also warn you that manually scrolling content using setContentOffset: animated: or just using UIView animation inside the method will not work properly.

If the speed is 0, you can (and you need) to use manual scrolling to snap it to the nearest paging point.

This may be the easiest and cleanest approach among all, but the main drawback is that it does not provide the same experience that you always had with a real paging function. To be more honest, it doesn't even look like what we call swap. For a better result, we need to combine more delegate methods.

3. Use multiple UIScrollView delegate methods

From my small experience, trying to manually scroll your scrollView inside any delegate methods of the UIScrollView will only work when your scrollView starts to slow down or doesn't scroll at all. Therefore, the best place I've found to do manual scrolling is scrollViewWillBeginDecelerating:

Before viewing the sample code, remember that the scrollViewEndDragging: withVelocity: targetContentOffset: method will always be called before scrollViewWillBeginDecelerating:

 - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { _scrollVelocity = velocity.x; if (_scrollVelocity == 0) { // Find the nearest paging point and scroll. } } - (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView { if (_scrollVelocity < 0) { [UIView animateWithDuration:0.3 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{ scrollView.contentOffset = // Previous page offset } completion:^(BOOL finished){}]; } else if (_scrollVelocity > 0) { // Animate to the next page offset } } 

_scrollVelocity means a global variable or property, and I suggested that you have your own ways of deciding the page offsets for each page. Note that you will have to handle the case of zero speed inside the top method, because the last method will not be called.

UIView animation with a duration of 0.3 and an EaseOut curve option gave me the best result, but of course you should try other combinations to find the best one for you.

+2
source share

enter image description here

This is not the exact solution you can look for.

1) Check the scrollView offset when it reaches 0, you can show the VIEW that you have above, you can animate by checking the scroll movement so that it looks good. But not completely

2) Now VIEW is partially above your camera (you can reduce its alpha so that scrolling is visible).

3) the user can click on the view, and you can show it in full.

+1
source share

You may want to calculate the most visible cell in your collection view after dragging the ends, and then programmatically scroll to - and in the center - that cell.

So something like:

First we implement the scrollViewDidEndDragging(_:willDecelerate:) your collection view delegate. Then, in this body of the method, determine which cell in collectionView.visibleCells most visible, comparing each of its centers with your collection view center. Once you find that your collection is viewing the most visible cell, select it by calling scrollToItem(at:at:animated:) .

+1
source share

All Articles