/ Scroll

Parallax Scroll-Jacking Effects Engine for iOS

Parallax Scroll-Jacking Effects Engine for iOS

Parade

Communicating to cells inside of UICollectionViews, UITableViews, or UIScrollViews, has always been a challenge. It almost always relies on a messy process of trying to relay the scroll to progress to cells in triggering special scrolling effects. We’ve designed this framework to minimize the effort needed to animate views. With a simple blocks-based builder we’ve made it easy to define view states—from where they appear and where they will disappear to.

Features

  • Supports UICollectionView, UITableView, UIScrollview
  • Simple Blocks-Based Syntax
  • Minimal Integration Requirements
  • Supports Chaining Animatable Views
  • Adjustable Progress Ranges
  • 46 Different Parametric Curves

Communication

  • If you found a bug, or have a feature request, open an issue.
  • If you want to contribute, review the Contribution Guidelines, and submit a pull request.
  • If you contribute, please ensure that the there is 100% code coverage

Basic Use

At its core, this framework defines start and end states for your animatable views—with the scroll view interpolating between the states accordingly. The start state defines where views appear from as they scroll onto screen—while end state defines where views will disappear.

The only requirements beyond defining the states is to implement the PDAnimatableType on views to animate, the rest is seamless. Each animation consists of a start or end state which is defined by the developer—plus a snapshot state that is automatically configured the first time the view begins to scroll.

Before creating, consider the diagram below to get a sense of the coordinate space that the progress is calculated against. Progress is relative to the bounds of the scrollview itself. When scrolling horizontally—the difference in X value to the center point of the viewport is equal to the width of the scrollview itself. Just as the difference in Y value when scrolling vertically, as demonstrated below.

NOTE : There is control to adjust these ranges, and instructions can be found in the Bounding Progress Range section below.

Initialization

Initialize the Parade in application(:didFinishLaunchingWithOptions:) when the application is launched. Once initialized, the base UIScrollView will begin communicating scrolling progress to animatable views contained within.

UIScrollView.initializeParade()

Create Animatable View

The first step is to make a view animatable by implement the PDAnimatableType protocol, and the scrollview will begin to communicate progress to it's subviews accordingly.

public protocol PDAnimatableType {

    // The progress animator definition that
    // interpolates over animatable properties
    func configuredAnimator() -> PDAnimator;
}

Any of the following views, and their subviews, can implement the PDAnimatableType and be animated:

  • UICollectionViewCell
  • UITableViewCell
  • UIScrollView's subviews

But before the scrollview can communicate the progress, define an animator with the relative scroll direction that the scrollview will be tracking against. Bundled with the framework is recursive blocks based builder, that allows for simple creation of complex animations to interpolate against while scrolling.

Vertical Direction Scroll Animator

The following is an example to create a vertical animator for scrolling vertically. The closure returns an animator that can be used to create from, and to, state for any specific view within the hierarchy.

func configuredAnimator() -> PDAnimator {

   /* Create a vertical tracking animator call this class method */
   return PDAnimator.newVerticalAnimator { (animator) in

   }
}

Horizontal Direction Scroll Animator

The following is an example to create a horizontal animator for scrolling horizontally. The closure returns an animator that can be used to create from, and to, state for any specific view within the hierarchy.

func configuredAnimator() -> PDAnimator {

   /* Create a horizontal tracking animator with this class method */
   return PDAnimator.newHorizontalAnimator { (animator) in

   }
}

The closure returns an animator that can be used to create from, and to, states for a specific view accordingly.

Configuring Animation States

Configure End State

To have a view fade in relative to the scrolling progress, define a start state by calling the startState(for:) method as follows. Each time a state is added, it returns a state maker that can be used to append multiple properties to interpolate over.

func configuredAnimator() -> PDAnimator {

    let offScreenAlpha : CGFloat = 0.0

    return PDAnimator.newVerticalAnimator { (animator) in

        animator.startState(for: animatedImageView, { (s) in
          s.alpha(offScreenAlpha)
        })
     }
}

Configure End State

Building on the last example, to have a view fade out relative to the scrolling progress off the screen, define an end state by calling the endState(for:) method as follows.

func configuredAnimator() -> PDAnimator {

    let offScreenAlpha : CGFloat = 0.0

    return PDAnimator.newVerticalAnimator { (animator) in

        animator.startState(for: animatedImageView, { (s) in
          s.alpha(offScreenAlpha)
        }).endState(for: animatedImageView, { (s) in
          s.alpha(offScreenAlpha)
        })
     }
}

Configure Start & End State

Building on the prior example, it appears that the appearing and disappearing alpha is the same. In the case both start, and end state values are the same, use the startEndState(for:) method to set the value for both states simultaneously.

func configuredAnimator() -> PDAnimator {

    let offScreenAlpha : CGFloat = 0.0

    return PDAnimator.newVerticalAnimator { (animator) in

        animator.startEndState(for: animatedImageView, { (s) in
          s.alpha(offScreenAlpha)
        })
     }
}

Start & End State for Multiple Views

In the case there are multiple views that need to perform the same animation, helper methods have been defined for creating animations for an array of views too. The following is an example of how these can be used.

func configuredAnimator() -> PDAnimator {

    var animatedViews = [view1, view2, view3, view4]

    let offScreenAlpha : CGFloat = 0.0

    return PDAnimator.newVerticalAnimator { (animator) in

        animator.startEndState(forViews: animatedViews, { (s) in
          s.alpha(offScreenAlpha)
        })
     }
}

The following can also be used with multiple views.

func startState(forViews views:  [UIView], _ callback : ... )    -> PDAnimationMaker
func endState(forViews views:  [UIView], _ callback : ... )      -> PDAnimationMaker
func startEndState(forViews views:  [UIView], _ callback : ... ) -> PDAnimationMaker

State Properties

The framework provides quite a few helpers to define state properties for views, and/or, their backing layer with ease.

/* View Property Setters */

public func alpha(_ value : CGFloat)               -> PDAnimatablePropertyMaker
public func backgroundColor(_ value : UIColor)     -> PDAnimatablePropertyMaker
public func bounds(_ value : CGSize)               -> PDAnimatablePropertyMaker
public func center(_ value : CGPoint)              -> PDAnimatablePropertyMaker
public func size(_ value : CGSize)                 -> PDAnimatablePropertyMaker
public func transform(_ value : CGAffineTransform) -> PDAnimatablePropertyMaker

/* Layer Property Setters */

public func borderColor(_ value : UIColor)         -> PDAnimatablePropertyMaker
public func borderWidth(_ value : CGFloat)         -> PDAnimatablePropertyMaker
public func contentsRect(_ value : CGRect)         -> PDAnimatablePropertyMaker
public func cornerRadius(_ value : CGFloat)        -> PDAnimatablePropertyMaker
public func shadowColor(_ value : UIColor)         -> PDAnimatablePropertyMaker
public func shadowOffset(_ value : CGSize)         -> PDAnimatablePropertyMaker
public func shadowOpacity(_ value : CGFloat)       -> PDAnimatablePropertyMaker
public func shadowRadius(_ value : CGFloat)        -> PDAnimatablePropertyMaker
public func transform3D(_ value : CATransform3D)   -> PDAnimatablePropertyMaker
public func zPosition(_ value : CGFloat)           -> PDAnimatablePropertyMaker

In the case a setter is not defined, and there is a need to set a specific property to interpolate between, there are two defined setters that use KVC for views, and/or, their backing layer accordingly.

public func viewValue(_ value : Any?, forKey key : String)    -> PDAnimatablePropertyMaker
public func layerValue(_ value : Any?, forKey key : String)   -> PDAnimatablePropertyMaker

Parametric Easing

There are 46 different parametric curves that can be applied to the interpolation of uniquely per property. The framework comes bundled with the following supported parametric curves that can be applied to each property individually.

.inSine
.inOutSine
.outSine
.outInSine
.inQuadratic
.inOutQuadratic
.outQuadratic
.outInQuadratic
.inCubic
.inOutCubic
.outCubic
.outInCubic
.inQuartic
.inOutQuartic
.outQuartic
.outInQuartic
.inQuintic
.inOutQuintic
.outQuintic
.outInQuintic
.inAtan
.inOutAtan
.outAtan
*
.inExponential
.inOutExponential
.outExponential
.outInExponential
.inCircular
.inOutCircular
.outCircular
.outInCircular
.inBack
.inOutBack
.outBack
.outInBack
.inElastic
.inOutElastic
.outElastic
.outInElastic
.inBounce
.inOutBounce
.outBounce
.outInBounce
.linear
.smoothStep
.smootherStep
*

Just append it to the state's property definition while building the view's state. A good reference for some of the supported parametric curves can be found here

func configuredAnimator() -> PDAnimator {

    let offScreenAlpha : CGFloat = 0.0
    let offScreenTransform = CGAffineTransform.identity.scaledBy(x: 0.5, y: 0.5)

    return PDAnimator.newVerticalAnimator { (animator) in

        animator.startEndState(for: animatedImageView, { (s) in
          s.alpha(offScreenAlpha).easing.(.inSine)
          s.transform(offScreenTransform).easing(.inOutCubic)
        })
     }
}

Advanced View Animations

Chaining Animations

Animation progress does not have to apply only to the top level view that is being scrolled. If a subview implements the PDAnimatableType, and is part of the subview hierarchy, attaching it to the animator can create a chain.

The animator will then communicate the progress to the subview's animator, subviews can be attached to subviews, or even have the subviews attach to their subviews, to create chains that can traverse multiple levels in the end to make some fun effects. To attach an animatable view, call the attachAnimatableView(:) method as follows.

func configuredAnimator() -> PDAnimator {

    let offScreenAlpha : CGFloat = 0.0
    let offScreenTransform = CGAffineTransform.identity.translatedBy(x: 0.0, y: 100.0)

    return PDAnimator.newVerticalAnimator { (animator) in

        animator.startEndState(for: animatedImageView, { (s) in
          s.transform(offScreenTransform).easing(.inOutCubic)
        }).attachAnimatableView(animatedImageView)
     }
}

GitHub