Last night I was working on a UITableViewController of a native iOS project. It was all standard stuff, so I feel like adding some eye candy to the view.

Many apps now add a Parallax Effect to their table view header, making the UI more lively and vivid. There are libs out there that do this for you, but then where is the fun in that.

So I went to search for a tutorial, there are some good ones, but I end up using this one from Matthew Cheok. Below is the final product of his tutorial.

alt text

The good things about Matthew’s tutorial is that he uses story board, has video instructions, and utilizes many things like struct and UIBezierPath.

When I applied Matthew’s technique into my project, I had a problem. I want apply this parallax effect to all my table views that has a header background image. As a good software developer, we should always keep our code DRY. So I did it with a some very simple setup.

Create ParallaxTableView class

First we will create a new UITableView subclass containing all the code needed to construct and update a parallax header:

import UIKit

class ParallaxTableView: UITableView {

    // these are public so we can change them in controller when needed
    var kTableHeaderHeight: CGFloat = 300.0
    var kTableHeaderCutAway: CGFloat = 80.0
    var kOverlapRatio: CGFloat = 3

    private var headerView: UIView?
    private var headerMaskLayer: CAShapeLayer?

    func constructParallaxHeader() {
        if self.tableHeaderView !== nil {
            // get the original table header
            headerView = self.tableHeaderView
            // remove the header from table view
            self.tableHeaderView = nil

            // add the header back to table view as a subview
            self.addSubview(headerView!)
            let effectiveHeight = kTableHeaderHeight - kTableHeaderCutAway / kOverlapRatio
            self.contentInset = UIEdgeInsets(top: effectiveHeight, left: 0, bottom: 0, right: 0)
            self.contentOffset = CGPoint(x: 0, y: -effectiveHeight)

            // construct cut away
            headerMaskLayer = CAShapeLayer()
            headerMaskLayer!.fillColor = UIColor.black.cgColor
            headerView!.layer.mask = headerMaskLayer
            // call the update to calculate header size and cut away
            updateHeaderView()
        }
    }

    func updateHeaderView() {
        let effectiveHeight = kTableHeaderHeight - kTableHeaderCutAway / kOverlapRatio
        var headerRect = CGRect(x: 0, y: -effectiveHeight, width: self.bounds.width, height: kTableHeaderHeight)
        if self.contentOffset.y < -effectiveHeight {
            headerRect.origin.y = self.contentOffset.y
            headerRect.size.height = -self.contentOffset.y + kTableHeaderCutAway / kOverlapRatio
        }

        let path = UIBezierPath()
        path.move(to: CGPoint(x: 0, y: 0))
        path.addLine(to: CGPoint(x: headerRect.width, y: 0))
        path.addLine(to: CGPoint(x: headerRect.width, y: headerRect.height))
        path.addLine(to: CGPoint(x: 0, y: headerRect.height - kTableHeaderCutAway))
        headerMaskLayer?.path = path.cgPath

        headerView?.frame = headerRect
    }

}

Most of the code is identical to Matthew’s tutorial, which you should definitely take a look if you have any trouble understanding what each line does.

Change the class of UITableView in storyboard

For any UITableView that you want to give it a parallax header, change its class to the ParallaxTableView class you just created.

Connect a IBOutlet from ParallaxTableView to its controller

We need an IBOutlet to the ParallaxTableView so we can call the construct and update functions when the table view is loaded. We do this by Ctrl-Drag, and I will name it parallaxTableView.

Call construct and update function in UITableViewController

Finally, we need to call the ParallaxTableView‘s function in the controller.

class ItemDetailTableViewController: UITableViewController {
    @IBOutlet var parallaxTableView: ParallaxTableView!

    ...

    override func viewDidLoad() {
        super.viewDidLoad()

        ...

        // call the construct function in viewDidLoad()
        parallaxTableView.constructParallaxHeader()

        ...
    }

    ...

    // MARK: - Scroll view delegate

    override func scrollViewDidScroll(_ scrollView: UIScrollView) {
        // call the update function in scrollViewDidScroll()
        parallaxTableView.updateHeaderView()
    }

    ...
}

Done

If you build and run your project now. Your UITableView should have a parallax header. If you want to add this to any other table view in your project, just change the class, add the outlet, and call 2 functions in its controller!

As you can see I have also made the configuration variables in ParallaxTableView to be public, so if you want a shorter parallax header, you simply do:

    parallaxtableView.kTableHeaderHeight = 100.0
    parallaxtableView.constructParallaxHeader()

Then your header will be 100 pixels tall.