5

This is not strictly a programming question but more "how to accomplish this" question.

I am curious (and working on an app that will probably require this) how is left-right parallax scrolling implemented. To know exactly what I mean check the Yahoo Weather app (it's free - no worries there).

Are they using just one view controller or a separate controller for each view that is shown there?
What is the easiest way to implement this? I have found this topic here which kinda explains it a bit but when are they getting information from their servers? Is it when the views are changed or at the startup of the app?

Any information how to implement such scrolling would be much appreciated.

Community
  • 1
  • 1
Majster
  • 3,611
  • 5
  • 38
  • 60

3 Answers3

5

It's really simple actually:

  • Subclass UIScrollView
  • Add a UIView *parallaxView; as @property
  • Add a CGFloat parallaxFactor as @property
  • Override layoutSubviews
  • Call super, and then use self.scrollOffset*parallaxFactor to position the parallaxView

That's it!

I've made a multifunctional UIScrollView subclass myself that's really simple to use, perfect for this case! Get it on GitHub: https://github.com/LeonardPauli/LPParallaxScrollView

Good Luck!

Leonard Pauli
  • 2,662
  • 1
  • 23
  • 23
  • Yes, well this handles the visible part indeed. But how about functionality? For example if user wants to update weather he pulls the view down and it refreshes with the latest weather info. I doubt that they use `if`s to determine which view is presented. Any ideas? – Majster Jul 31 '13 at 20:29
  • I open-sourced something I made a while ago! https://github.com/LeonardPauli/MYParallaxScrollView Just use parallaxView.currentPageIndex :) But even better, use a different vertical (parallax for the simple block) scrollview for each page, with the reload there. (Don't forget to make the gesture recognizers block each other right) :D – Leonard Pauli Jul 31 '13 at 20:59
  • I tried to make it tick today but you forgot to include `UIView+myExt.h`. Could you post that as well? – Majster Aug 01 '13 at 08:28
  • Oh, sorry!! Will add it immediately! – Leonard Pauli Aug 01 '13 at 10:31
  • Almost there :D now `UIImage+Crop.h` is missing. – Majster Aug 01 '13 at 12:09
  • Now I really hope I haven't forgot something else.. https://github.com/LeonardPauli/MYParallaxScrollView – Leonard Pauli Aug 01 '13 at 14:00
  • 1
    Looks like a great library. Would love to have it available on cocoapods ;) – LordParsley May 30 '14 at 12:56
0

I implemented a small prototype based on a standard UIPageViewController subclass, and autolayout constraints for the parallax effect. It's quite fully documented, if you want to have a look : TestParallax

In a nutshell, the heart of the parallax is done in the scrollViewDidScroll delegate method:

extension PageViewController: UIScrollViewDelegate {

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let screenWidth = scrollView.bounds.width
        /*
         In a UIPageViewController, the initial contentOffset.x is not 0, but equal to the screen width
         (so that the offset goes between (1 * screenWidth) and 0 when going to the previous view controller, 
         and from (1 * screenWidth) to (2 * screenWidth) when going to the next view controller).
         Also, it's reset to the screenWidth when the scroll to a previous or next view controller is complete.
         Therefore, we calculate a new 'horizontalOffset' starting at 0, and going:
         - negative from 0 to (-screenWidth/2) when scrolling to the next view controller,
         - and from 0 to (screenWidth/2) when scrolling to the previous view controller.
         */
        let horizontalOffset = (scrollView.contentOffset.x - screenWidth)/2

        // Special case: initial situation, or when the horizontalOffset is reset to 0 by the UIPageViewController.
        guard horizontalOffset != 0 else {
            previousPageController?.offsetBackgroundImage(by: screenWidth/2)
            currentPageController?.offsetBackgroundImage(by: 0)
            nextPageController?.offsetBackgroundImage(by: -screenWidth/2)
            return
        }

        // The background image of the current page controller should always be offset by the horizontalOffset (which may be positive or negative)
        guard let currentPageController = currentPageController else { return }
        currentPageController.offsetBackgroundImage(by: horizontalOffset)

        if horizontalOffset > 0 { // swiping left, to the next page controller

            // The background image of the next page controller starts with an initial offset of (-screenWidth/2), then we apply the (positive) horizontalOffset
            if let nextPageController = nextPageController {
                let nextOffset = -screenWidth/2 + horizontalOffset
                nextPageController.offsetBackgroundImage(by: nextOffset)
            }
        } else { // swiping right, to the previous page controller

            // The background image of the previous page controller starts with an initial offset of (+screenWidth/2), then we apply the (negative) horizontalOffset
            if let previousPageController = previousPageController {
                let previousOffset = screenWidth/2 + horizontalOffset
                previousPageController.offsetBackgroundImage(by: previousOffset)
            }
        }
    }
}
Frederic Adda
  • 5,905
  • 4
  • 56
  • 71
0

You are not supposed to change the delegate of the page view controller's scroll view. It can break its normal behaviour.

Instead, you can:

  1. Add a pan gesture to the page view controller's view:

    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(panRecognized(gesture:)))
    view.addGestureRecognizer(panGesture)
    panGesture.delegate = self
    
  2. Add the new function in order to know how the view is being scrolled.

    @objc func panRecognized(gesture: UIPanGestureRecognizer) {
        // Do whatever you need with the gesture.translation(in: view)
    }
    
  3. Declare your ViewController as UIGestureRecognizerDelegate.

  4. Implement this function:

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
    
Tulleb
  • 8,919
  • 8
  • 27
  • 55