14

I'm facing a challenge of creating an introduction view, something like the "Cleanio" app (https://itunes.apple.com/fr/app/cleanio-pressing-la-demande/id885856031?mt=8).
Here is how it looks like:
enter image description here enter image description here enter image description here


So, the background and the overlay are moving independently and not in the same speed.
Does anyone have a start point how to realize that?

BJ Myers
  • 6,617
  • 6
  • 34
  • 50
sazz
  • 3,193
  • 3
  • 23
  • 33
  • Check this out, a very cool framework. This is how IFFT built their intro: https://github.com/IFTTT/JazzHands – The dude Sep 15 '14 at 13:01
  • Thanks for your comment, I checked out that sources, but it doesn't really respond to what I need. – sazz Sep 15 '14 at 13:19
  • Can u please give me a sample code for these.. i find it very diffucult to understand @SofienAzzouz – iOSDeveloper Dec 02 '14 at 12:42

2 Answers2

14

Yep.

What you need is two UIScrollViews. These should both be subviews of the main view (not contained in each other.

The bottom one has your image in it and the top one has the content.

Call them imageScrollView and contentScrollView.

Become the delegate of contentScrollView.

The contents will look something like this...

contents:    [---page 1---][---page 2---][---page 3---][---page 4---]
image:       [------------the image------------]
screen:      [---screen---]

Key is that image is smaller than all the pages and bigger than the screen.

The frames of the scrollviews are the same width as the screen. This diagram is just to show the content widths not the frame widths.

So, the screen stays where it is and the two scroll views move over it.

Now the parallax part...

- (CGFloat)maxOffsetForScrollView:(UIScrollView *)scrollView
{
    CGFloat contentWidth = scrollView.contentSize.width;
    CGFloat frameWidth = CGRectGetWidth(scrollView.frame);

    return contentWidth - frameWidth;
}

- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    // this is the delegate method for the content scroll view.
    // I'm only doing horizontal stuff here, you can do vertical too if you want

    CGFloat maximumContentOffset = [self maximumOffsetForScrollView:self.contentScrollView];
    CGFloat currentOffset = self.contentScrollView.contentOffset.x;

    CGFloat percentageOffset = currentOffset/maximumContentOffset;

    CGFloat maximumImageOffset = [self maximumContentOffsetForScrollView:self.imageScrollView];
    CGFloat actualImageOffset = maximumImageOffset * percentageOffset;

    [self.imageScrollView setContentOffset:CGPointMake(actualImageOffset, 0)];
}

This takes the percentage offset from the content view and offsets the image view by the same percentage offset.

The result is a parallax effect. You can make it faster or slower by changing the relative sizes of the image and the content. More pages (or smaller image) = slower parallax.

Fogmeister
  • 76,236
  • 42
  • 207
  • 306
  • Thanks @Fogmeister for your answer. I can't find what 'maximumOffsetForScrollView' and 'maximumContentOffsetForScrollView' refers for? – sazz Sep 15 '14 at 13:18
  • 1
    @SofienAzzouz ah, refactoring in Safari isn't very helpful :) Edited now. `maximumOffsetForScrollView:` is a function defined in my answer. It returns the maximum offset for the given scrollview. – Fogmeister Sep 15 '14 at 13:29
  • 2
    Good, but I still dont get how to manage the different view in the contentScrollView? and how to associate the background image to the imageScrollView? should I create an ImageView and put it in the imageScrollView? – sazz Sep 15 '14 at 13:45
  • How to put stuff in a scroll view. `[scrollView addSubview:someView];`. It's just a scrollView. There is nothing special about it. It looks like it has paging enabled (see InterfaceBuilder properties). Yes, use a `UIImageView` that's how you add images to views. Again, it's just a view. Just add stuff to it and mess around with it. The mechanics doesn't change how you add stuff to the screen. – Fogmeister Sep 15 '14 at 13:56
  • 2
    Got it, I had to make the two scrollView bigger to see the transitions. Thank you very much, I appreciate your help :) – – sazz Sep 15 '14 at 14:15
  • 1
    Can u please give me a sample code for these.. i find it very diffucult to understand @Fogmeister – iOSDeveloper Dec 02 '14 at 12:42
  • 1
    @iOSDeveloper here you go http://www.oliverfoggin.com/controlling-animations-with-a-uiscrollview/ There is no sample code other than the method I wrote in this answer. – Fogmeister Dec 02 '14 at 12:53
2

I had been struggling with this parallax effect for a little while. I wouldn't have done it without @Fogmeister instructions, though I still had to figure out a couple of things by myself. Anyway, here is a Swift 2.0 version, (hopefully a bit more complete):

import UIKit

class ViewController: UIViewController, UIScrollViewDelegate {


    var backgroundScrollView: UIScrollView!
    var contentScrollView: UIScrollView!
    var imageView: UIImageView!
    var contentView: UIView!
    var maxContentOffset: CGFloat!
    var maxBackgroundOffset: CGFloat!
    var cvbw: CGFloat!
    var page: Int = 1

    override func viewDidLoad() {
        super.viewDidLoad()
        self.createBackground()
        self.createContent()
    }

    func createBackground() {
        self.imageView = UIImageView(image: UIImage(named: "ParalaxMaterial.jpg"))  //ParalaxMaterial.jpg is of size: 2500 x 2668px
        self.imageView.frame = CGRectMake(0, 0, ((self.view.bounds.height/2668) * 2500), self.view.bounds.height)
        self.backgroundScrollView = UIScrollView(frame: view.bounds)
        self.backgroundScrollView.backgroundColor = UIColor.redColor()
        self.backgroundScrollView.contentSize = imageView.bounds.size
        self.backgroundScrollView.addSubview(self.imageView)
        self.view.addSubview(self.backgroundScrollView)
        self.maxBackgroundOffset = self.maxOffsetForScrollView(self.backgroundScrollView)
    }

    func createContent() {
        self.contentView = UIView(frame: CGRectMake(0, 0, (self.view.bounds.width * 3), self.view.bounds.height))
        self.contentScrollView = UIScrollView(frame: view.bounds)
        self.contentScrollView.backgroundColor = UIColor.clearColor()
        self.contentScrollView.contentSize = self.contentView.bounds.size
        self.contentScrollView.delegate = self
        let firstButton = UIButton()
        firstButton.frame = CGRectMake(((self.contentView.bounds.width / 6) - 150), 300, 300, 100)
        firstButton.setTitle("START", forState: UIControlState.Normal)
        firstButton.titleLabel?.font = UIFont(name: "Arial", size: 18)
        firstButton.addTarget(self, action: "firstAction:", forControlEvents: UIControlEvents.TouchUpInside)
        self.contentView.addSubview(firstButton)
        let firstLabel = UILabel()
        firstLabel.frame = CGRectMake(((self.contentView.bounds.width / 6) - 100), 0, 200, 200)
        firstLabel.text = "#BrusselsLockDown"
        firstLabel.textAlignment = NSTextAlignment.Center
        self.contentView.addSubview(firstLabel)
        let secondLabel = UILabel()
        secondLabel.frame = CGRectMake(((self.contentView.bounds.width / 2) - 100), 0, 200, 200)
        secondLabel.text = "#LolCats"
        secondLabel.textAlignment = NSTextAlignment.Center
        self.contentView.addSubview(secondLabel)
        let thirdLabel = UILabel()
        thirdLabel.frame = CGRectMake((((self.contentView.bounds.width / 6) * 5) - 100), 0, 200, 200)
        thirdLabel.text = "#Final"
        thirdLabel.textAlignment = NSTextAlignment.Center
        self.contentView.addSubview(thirdLabel)
        self.contentScrollView.addSubview(self.contentView)
        self.view.addSubview(self.contentScrollView)
        self.maxContentOffset = self.maxOffsetForScrollView(self.contentScrollView)
        self.cvbw = self.contentView.bounds.width
    }

    func scrollViewWillBeginDecelerating(scrollView: UIScrollView) {
        if self.page == 1 {
            if scrollView.contentOffset.x > 0 {
                scrollView.setContentOffset(CGPointMake((self.cvbw / 3), 0), animated: true)
            }
        } else if self.page == 2 {
            if scrollView.contentOffset.x < (self.cvbw * 4/12) {
                scrollView.setContentOffset(CGPointMake(0, 0), animated: true)
            } else if scrollView.contentOffset.x > (self.cvbw * 4/12) {
                scrollView.setContentOffset(CGPointMake(((self.cvbw / 3) * 2), 0), animated: true)
            }
        } else if self.page == 3 {
            if scrollView.contentOffset.x < (self.cvbw * 8/12) {
                scrollView.setContentOffset(CGPointMake((self.cvbw / 3), 0), animated: true)
            }
        } else {
            print("self.page messed up")
        }
    }


    func scrollViewDidScroll(scrollView: UIScrollView) {
        if scrollView == self.contentScrollView {
            let percentageOffset: CGFloat = self.contentScrollView.contentOffset.x / self.maxContentOffset
            let currentBackgroundOffsetPoint: CGPoint = CGPointMake(((self.maxBackgroundOffset * percentageOffset) + 50), 0)                self.backgroundScrollView.setContentOffset(currentBackgroundOffsetPoint, animated: false)
        }

        if self.contentScrollView.contentOffset.x == 0 {
            print("page 1")
            self.page = 1
        } else if self.contentScrollView.contentOffset.x == 320 {
            print("page 2")
            self.page = 2
        } else if self.contentScrollView.contentOffset.x == 640 {
            print("page 3")
            self.page = 3
        }
    }


    func maxOffsetForScrollView(scrollView: UIScrollView) -> CGFloat {
        let contentWidth: CGFloat = scrollView.contentSize.width - 100
        let frameWidth: CGFloat = CGRectGetWidth(scrollView.frame)
        return contentWidth - frameWidth
    }


    func firstAction (sender: UIButton) {
        print("firstAction")
        self.contentScrollView.setContentOffset(CGPointMake((self.cvbw / 3), 0), animated: true)
    }
}
Quentin Malgaud
  • 405
  • 6
  • 21