Skip to content

Instantly share code, notes, and snippets.

@arturdev
Last active June 9, 2020 08:44
Show Gist options
  • Save arturdev/ae11db6a8af7bb623de6b75b92369e4f to your computer and use it in GitHub Desktop.
Save arturdev/ae11db6a8af7bb623de6b75b92369e4f to your computer and use it in GitHub Desktop.
//
// UIScrollView+ParallaxHeader.swift
// ParallaxHeader
//
// Created by Artur Mkrtchyan on 3/24/20.
// Copyright © 2020 Artur Mkrtchyan. All rights reserved.
//
import UIKit
private class ParralaxView: UIView {
let contentView: UIView
let minHeight: CGFloat
let heightConstraint: NSLayoutConstraint
let parallaxType: ParallaxHeaderType
var observationToken: NSKeyValueObservation?
init(contentView: UIView, minHeight: CGFloat, type: ParallaxHeaderType) {
self.contentView = contentView
self.minHeight = minHeight
self.heightConstraint = contentView.heightAnchor.constraint(equalToConstant: 0)
self.parallaxType = type
super.init(frame: contentView.bounds)
backgroundColor = .clear
contentView.translatesAutoresizingMaskIntoConstraints = false
addSubview(contentView)
contentView.topAnchor.constraint(equalTo: topAnchor).isActive = true
contentView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
contentView.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
self.heightConstraint.isActive = true
clipsToBounds = true
layer.zPosition = -.greatestFiniteMagnitude + 1
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var scrollView: UIScrollView? {
return superview as? UIScrollView
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
setupScrollView()
}
override func willMove(toSuperview newSuperview: UIView?) {
guard superview is UIScrollView else { return }
observationToken?.invalidate()
}
deinit {
observationToken?.invalidate()
}
private func setupScrollView() {
guard let scrollView = scrollView else { return }
scrollView.sendSubviewToBack(self)
observationToken = scrollView.observe(\.contentOffset, options: [.initial, .new]) {[weak self] (scrollView, change) in
guard let self = self else { return }
self.refreshContent()
}
}
private func refreshContent() {
guard let scrollView = scrollView else { return }
let topInset = scrollView.adjustedContentInset.top
var frame = self.frame
frame.size.width = scrollView.frame.width
frame.origin.x = scrollView.bounds.origin.x
if scrollView.bounds.origin.y < -topInset {
frame.origin.y = scrollView.bounds.origin.y
frame.size.height = abs(scrollView.bounds.origin.y)
self.heightConstraint.constant = abs(scrollView.bounds.origin.y)
} else {
if scrollView.bounds.origin.y + minHeight > 0 {
frame.origin.y = scrollView.bounds.origin.y - (minHeight + scrollView.bounds.origin.y)
frame.size.height = scrollView.bounds.origin.y < 0 ? (abs(min(scrollView.bounds.origin.y, 0)) + (minHeight + scrollView.bounds.origin.y)) : 0
} else {
frame.origin.y = scrollView.bounds.origin.y
frame.size.height = abs(min(scrollView.bounds.origin.y, 0))
}
self.heightConstraint.constant = self.parallaxType == .fill ? frame.size.height : topInset
}
self.frame = frame
self.layoutIfNeeded()
}
}
public enum ParallaxHeaderType {
case fill
case top
}
public extension UIScrollView {
func addParallaxHeader(header: UIView, minHeight: CGFloat = 0, type: ParallaxHeaderType = .fill) {
let parralaxView = ParralaxView(contentView: header, minHeight: minHeight, type: type)
addSubview(parralaxView)
}
}
@arturdev
Copy link
Author

arturdev commented Jun 9, 2020

Usage

private func setupParallaxHeader() {
	headerImageView.contentMode = .scaleAspectFill
	headerImageView.clipsToBounds = true
	collectionView.addParallaxHeader(header: headerImageView)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment