How to add image with parallax effect above UITableView header and keep header sticky?
Asked Answered
H

3

15

Here is an image that explains everything I want to do:

enter image description here

My question is, how would I make my view structure. The header of the table view should be fixed at top of the table. But what about the top most image that is above the table view header. Will I have to add the table view inside the UIScrollView ?

Parallax effect can be done by CATransform3D, but how would I achieve what I want, that is my question. There are lots of demos but I want to make it done custom.

Hypsometry answered 17/6, 2015 at 7:29 Comment(6)
So what did you finally used?Affect
So Finally u achieved the above effect . If yes can u share me sample codeGilmore
@AmanGupta007 - have you got any solution of above demo?Coccus
@Affect - have you got any solution of above demo?Coccus
I think I ended up modifing APParallaxHeader project. But it was some time ago, don't really rememberAffect
Maybe it is a little bit late but I have posted an answerBlinnie
A
7

You can add image view to the view like -

let imageView = UIImageView()
let lblName = UILabel()

imageView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 300)
imageView.image = UIImage.init(named: "poster")
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
view.addSubview(imageView)

lblName.frame = CGRect(x: 20, y: 100, width: 200, height: 22)
lblName.text = "Steve Jobs"
lblName.textColor = UIColor.white
lblName.font = UIFont.systemFont(ofSize: 26)
lblName.clipsToBounds = true
imageView.addSubview(lblName)

After that in tableview delegate method you can add scrollviewDidScroll method like -

let y = 300 - (scrollView.contentOffset.y + 300)
let height = min(max(y, 60), 400)
imageView.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: height)
lblName.frame = CGRect(x: 20, y: height - 30, width: 200, height: 22)

I hope this will be helpful. Please correct me if I am wrong.

enter image description here

Aftermost answered 14/5, 2018 at 9:59 Comment(8)
How does this change with the iPhone X format?! With this example there is image clipping at the top of the screen with the iPhone X...Affective
@FamicTech Can you explain it little better? Because for me it is showing same in iPhone X as well.Aftermost
Do you have a sample app you can make available for download. On the iPhone X with the camera being in the way of the full screen , usually the image get clipped on the left side (clock/time) and right side (battery indicator, wifi and cellular indicators)Affective
No this is the demo for parallax effect. You can check this functionality using the code which is in answer. Please let me know if you have any query. And also it is working in iPhone X same as looks in gif or other iPhones.Aftermost
how can we achieve it if navigation bar is present ?Nearly
@jayantrawat I think you have to use custom navigation bar.Aftermost
how can i scroll from parallax view as u r adding subview to view?Auxesis
I think you code is missing the adjustment to tableView's contentInset in scrollViewDidScroll. Otherwise it will be covered by the header? tableView.contentInset = UIEdgeInsets(top: height, left: 0, bottom: 0, right: 0)Roguish
P
3

Swift 5

as of my requirement i have used https://github.com/maxep/MXParallaxHeader

i explained you the things step by step

you need to install the above mentioned third party library by using this pod command

1.)

pod "MXParallaxHeader"

open up commannd manager(Terminal) go to your target folder and run this command:

2.)

pod install

you need a parallax effect of your image view and stick the header on the top you need to create the custom .xib file to user as a parallax header.

3.) 

Add new file choose a (User Interface) View as a new template and name the 
file. eg.. ParallaxView and tap on the create.

you have created UIView now you need to add the Cocoa Touch Class file for your custom view.

4.) 

Add new file choose a (Cocoa Touch Class) View as a new template and name the file. eg.. ParallaxView and tap on the Next.

now you have pair of class file with its custom UIView eg.(ParallaxView.xib & ParallaxView.swift)

and according to my project requirement i need to add a pagemenu in the bottom of the parallaxheader so i use a another third party library named CAPSPageMenu

5.)

just visit this https://github.com/PageMenu/PageMenu/blob/master/Classes/CAPSPageMenu.swift and download the CAPSPageMenu.swift file and drag from your downloads and drop to your project destination folder.

now we are ready to go for the code part.

Goto your ViewController file and import the frame work

6.)

import MXParallaxHeader

delegate methods

 7.)

 class MyParralax: UIViewController, MXScrollViewDelegate, CAPSPageMenuDelegate 
 {// Parant Controller Code }

Define the class (MyParralax.swift) variables for the controllers (for page menu )and (MXParallaxHeader)like this

var scrollView      : MXScrollView!
let Parallax        = Bundle.main.loadNibNamed("ParallaxView", owner: nil, options: nil)?.first as? ParallaxView
let controller1     : VC1 = VC1.instantiateFromStoryboard()
let controller2     : VC2 = VC2.instantiateFromStoryboard()
var controllerArray : [UIViewController] = []
var pageMenu        : CAPSPageMenu?

you have to create two view controller file as a child view controller of the pagemenu and storyboard too.these both controller.swift (VC1 & VC2) will look like this.

import UIKit

class VC1: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
       // child conroller 
    }

    class func instantiateFromStoryboard() -> VC1
    {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        return storyboard.instantiateViewController(withIdentifier: "VC1") as! VC1
    }
}

put this three functions in your parent controller(MyParralax.swift)

func setParallaxMenu(){
        self.scrollView = MXScrollView()
        self.scrollView.backgroundColor  = UIColor.green
        self.scrollView.delegate = self
        self.scrollView.parallaxHeader.view = Parallax // You can set the parallax header view from a nib.
        self.scrollView.parallaxHeader.height = 446.0 // desired hieght or hight of the xib file
        self.scrollView.parallaxHeader.mode = MXParallaxHeaderMode.fill
        self.scrollView.parallaxHeader.minimumHeight = UIApplication.shared.statusBarFrame.size.height + (self.navigationController?.navigationBar.frame.height)!
        let newFrame = CGRect(x: 0,y: UIApplication.shared.statusBarFrame.size.height + (self.navigationController?.navigationBar.frame.height)!, width: self.view.frame.size.width, height: self.view.frame.size.height - (UIApplication.shared.statusBarFrame.size.height + (self.navigationController?.navigationBar.frame.height)!)) // scrollview's frame calculation
        scrollView.frame = newFrame
        scrollView.contentSize = newFrame.size
        self.scrollView.delegate = self
        view.addSubview(scrollView)
        self.pagemenuSetup()
    }


func pagemenuSetup()
    {
        controllerArray.removeAll()
        controllerArray.append(controller1)
        controllerArray.append(controller2)

        controller1.title = "ORANGE"
        controller2.title = "YELLOW"


        // Customize menu (Optional)
        let parameters: [CAPSPageMenuOption] = [
            .menuItemSeparatorWidth(4.3),
            .scrollMenuBackgroundColor(UIColor(red: 25.0/255.0, green: 26.0/255.0, blue: 36.0/255.0, alpha: 1.0)),
            .viewBackgroundColor(UIColor.clear),
            .selectionIndicatorColor(UIColor.white),
            .bottomMenuHairlineColor(UIColor.clear),
            .unselectedMenuItemLabelColor(UIColor(red: 255.0/255.0, green: 255.0/255.0, blue: 255.0/255.0, alpha: 0.5)),
            .menuItemFont(UIFont(name: "Helvetica", size: 16.0)!),
            .enableHorizontalBounce(false),
            .menuHeight(52.0),
            .menuMargin(0.0),
            .menuItemWidth(self.view.bounds.width/2),
            .selectionIndicatorHeight(15.0),
            .menuItemSeparatorPercentageHeight(0.1),
            .iconIndicator(true),
            .iconIndicatorView(self.getIndicatorView())
        ]
        // Initialize scroll menu
        var frame = view.frame
        scrollView.frame = frame
        scrollView.contentSize = frame.size
        let Height = self.view.frame.size.height - (UIApplication.shared.statusBarFrame.size.height + (self.navigationController?.navigationBar.frame.height)!)
        frame.size.height = Height
        self.pageMenu = CAPSPageMenu(viewControllers: controllerArray, frame: frame, pageMenuOptions: parameters)
        pageMenu!.delegate = self
        self.scrollView.addSubview(pageMenu!.view)
        view.addSubview(scrollView)
    }


private func getIndicatorView()->UIView
    {
        let imgView = UIImageView(frame: CGRect(x: 0, y: 0, width: self.view.bounds.width/2, height: 15.0))
        imgView.image = UIImage(named: "Indicator")
        imgView.contentMode = .scaleAspectFit
        return imgView
    }

check this output.

enter image description here

Purtenance answered 2/5, 2019 at 12:15 Comment(3)
how to deal when we have tableview inside each tab.In my case tableview and Scrollview scrolls are conflictingLandahl
@Landahl thing is you can disable the scroll of UITableviewPurtenance
Unfortunately the author of MXParallaxHeader has stopped maintenance. There is an alternative swift version for this library called HPParallaxHeader: github.com/ngochiencse/HPParallaxHeaderUralite
B
2

I have wondering how to achieve a parallax sticky header and I found this post that does the work.

The post is in Swift 2 but I have recode it for swift 4.2

CustomHeaderView

import UIKit

class CustomHeaderView: UIView {

    //MARK:- Variables
    //MARK: Constants


    //MARK: Variables
    var imageView:UIImageView!
    var colorView:UIView!
    var bgColor = UIColor(red: 235/255, green: 96/255, blue: 91/255, alpha: 1)
    var titleLabel = UILabel()
    var articleIcon:UIImageView!



    //MARK:- Constructor
    init(frame:CGRect, title: String) {

        self.titleLabel.text = title.uppercased()
        super.init(frame: frame)

        setUpView()

    }

    required init?(coder aDecoder: NSCoder) {

        fatalError("init(coder:) has not been implemented")

    }



    //MARK:- Private methods
    private func setUpView() {
        backgroundColor = UIColor.white

        imageView = UIImageView()
        imageView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(imageView)

        colorView = UIView()
        colorView.translatesAutoresizingMaskIntoConstraints = false
        addSubview(colorView)

        let constraints:[NSLayoutConstraint] = [
            imageView.topAnchor.constraint(equalTo: self.topAnchor),
            imageView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
            imageView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
            imageView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
            colorView.topAnchor.constraint(equalTo: self.topAnchor),
            colorView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
            colorView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
            colorView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
        ]
        NSLayoutConstraint.activate(constraints)


        imageView.image = UIImage(named: "bg-header")
        imageView.contentMode = .scaleAspectFill

        colorView.backgroundColor = bgColor
        colorView.alpha = 0.6

        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        self.addSubview(titleLabel)
        let titlesConstraints:[NSLayoutConstraint] = [
            titleLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            titleLabel.topAnchor.constraint(equalTo: self.topAnchor, constant: 28),
            ]
        NSLayoutConstraint.activate(titlesConstraints)

        titleLabel.font = UIFont.systemFont(ofSize: 15)
        titleLabel.textAlignment = .center

        articleIcon = UIImageView()
        articleIcon.translatesAutoresizingMaskIntoConstraints = false
        addSubview(articleIcon)
        let imageConstraints:[NSLayoutConstraint] = [
            articleIcon.centerXAnchor.constraint(equalTo: self.centerXAnchor),
            articleIcon.centerYAnchor.constraint(equalTo: self.centerYAnchor, constant: 6),
            articleIcon.widthAnchor.constraint(equalToConstant: 40),
            articleIcon.heightAnchor.constraint(equalToConstant: 40)
        ]

        NSLayoutConstraint.activate(imageConstraints)
        articleIcon.image = UIImage(named: "article")
    }


    //MARK:- Public methods
    func decrementColorAlpha(offset: CGFloat) {

        if self.colorView.alpha <= 1 {

            let alphaOffset = (offset/500)/85
            self.colorView.alpha += alphaOffset

        }
    }

    func decrementArticleAlpha(offset: CGFloat) {

        if self.articleIcon.alpha >= 0 {

            let alphaOffset = max((offset - 65)/85.0, 0)
            self.articleIcon.alpha = alphaOffset

        }

    }

    func incrementColorAlpha(offset: CGFloat) {

        if self.colorView.alpha >= 0.6 {

            let alphaOffset = (offset/200)/85
            self.colorView.alpha -= alphaOffset

        }

    }

    func incrementArticleAlpha(offset: CGFloat) {

        if self.articleIcon.alpha <= 1 {

            let alphaOffset = max((offset - 65)/85, 0)
            self.articleIcon.alpha = alphaOffset

        }

    }

}

And then VieController

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    //MARK:- Variables
    //MARK: Constants


    //MARK: Variables
    var tableView:UITableView!
    var headerView:CustomHeaderView!
    var headerHeightConstraint:NSLayoutConstraint!



    //MARK: - Lifecycle methods
    override func viewDidLoad() {
        super.viewDidLoad()

        setUpHeader()
        setUpTableView()

    }



    //MARK: - Private methods
    private func setUpHeader() {

        headerView = CustomHeaderView(frame: CGRect.zero, title: "Articles")
        headerView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(headerView)

        headerHeightConstraint = headerView.heightAnchor.constraint(equalToConstant: 150)
        headerHeightConstraint.isActive = true

        let constraints:[NSLayoutConstraint] = [
            headerView.topAnchor.constraint(equalTo: view.topAnchor),
            headerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            headerView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
        ]

        NSLayoutConstraint.activate(constraints)

    }

    private func setUpTableView() {

        tableView = UITableView()
        tableView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(tableView)

        let constraints:[NSLayoutConstraint] = [
            tableView.topAnchor.constraint(equalTo: headerView.bottomAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ]

        NSLayoutConstraint.activate(constraints)
        tableView.register(UITableViewCell.self,forCellReuseIdentifier: "cell")

        tableView.dataSource = self
        tableView.delegate = self

    }

    private func animateHeader() {

        self.headerHeightConstraint.constant = 150
        UIView.animate(withDuration: 0.4, delay: 0.0, usingSpringWithDamping: 0.7, initialSpringVelocity: 0.5, options: [.curveEaseInOut], animations: {
            self.view.layoutIfNeeded()
        }, completion: nil)

    }



    //MARK: - UITableView implementation
    //MARK: UITableViewDataSource implementation
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 100
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "cell",   for: indexPath)
        cell.textLabel?.text = "Article \(indexPath.row)"
        return cell

    }


    //MARK: UITableViewDelegate implementation
    func scrollViewDidScroll(_ scrollView: UIScrollView) {

        if scrollView.contentOffset.y < 0 {

            self.headerHeightConstraint.constant += abs(scrollView.contentOffset.y)
            headerView.incrementColorAlpha(offset: self.headerHeightConstraint.constant)
            headerView.incrementArticleAlpha(offset: self.headerHeightConstraint.constant)

        }
        else if scrollView.contentOffset.y > 0 && self.headerHeightConstraint.constant >= 65 {

            self.headerHeightConstraint.constant -= scrollView.contentOffset.y/100
            headerView.decrementColorAlpha(offset: scrollView.contentOffset.y)
            headerView.decrementArticleAlpha(offset: self.headerHeightConstraint.constant)

            if self.headerHeightConstraint.constant < 65 {
                self.headerHeightConstraint.constant = 65
            }

        }

    }

    func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {

        if self.headerHeightConstraint.constant > 150 {
            animateHeader()
        }

    }

    func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {

        if self.headerHeightConstraint.constant > 150 {
            animateHeader()
        }

    }

}

Resulting to the video that shows the link provided. Next steps would add the safe area constraint and maybe add nib to the header but that it is completely up to you.

Blinnie answered 20/11, 2018 at 9:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.