Skip to content

Instantly share code, notes, and snippets.

@cathandnya
Created March 19, 2021 23:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cathandnya/c055a0e6a24ae60f1c9d587e68e8bf33 to your computer and use it in GitHub Desktop.
Save cathandnya/c055a0e6a24ae60f1c9d587e68e8bf33 to your computer and use it in GitHub Desktop.
//
// ParallaxHeaderView.swift
//
// Created by nya on 2021/03/19.
//
import SwiftUI
import MXParallaxHeader
class ParallaxViewController: MXScrollViewController, MXScrollViewDelegate {
var headerHeight: CGFloat = 0
var headerMinimumHeight: CGFloat = 0
override func viewDidLoad() {
super.viewDidLoad()
scrollView.parallaxHeader.minimumHeight = headerMinimumHeight
scrollView.parallaxHeader.height = headerHeight
scrollView.delegate = self
}
func updateHeaderHeight(animated: Bool = false) {
let height = headerHeight
if animated {
UIView.animate(withDuration: 0.3) { [weak self] in
self?.scrollView.parallaxHeader.height = height
}
} else {
scrollView.parallaxHeader.height = height
view.setNeedsLayout()
UIView.performWithoutAnimation {
view.layoutIfNeeded()
scrollView.contentOffset.y = scrollView.contentInset.top - scrollView.adjustedContentInset.top
}
}
}
}
class ParallaxViewControllerHeader: UIViewController {
let controller: UIViewController
var heightConstraint: NSLayoutConstraint?
init(header: UIViewController) {
controller = header
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
view.clipsToBounds = false
addChild(controller)
view.addSubview(controller.view)
controller.didMove(toParent: self)
let height = controller.view.systemLayoutSizeFitting(CGSize(width: UIScreen.main.bounds.width, height: .infinity)).height
let heightConstraint = controller.view.heightAnchor.constraint(equalToConstant: height)
controller.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
controller.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
controller.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
//controller.view.topAnchor.constraint(equalTo: view.topAnchor),
controller.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
heightConstraint,
])
self.heightConstraint = heightConstraint
}
func updateHeight(animated: Bool = false) -> CGFloat {
heightConstraint?.isActive = false
controller.view.setNeedsLayout()
controller.view.layoutIfNeeded()
let height = controller.view.systemLayoutSizeFitting(CGSize(width: UIScreen.main.bounds.width, height: .infinity)).height
heightConstraint?.constant = height
heightConstraint?.isActive = true
if animated {
UIView.animate(withDuration: 0.3) { [weak self] in
self?.view.layoutIfNeeded()
}
}
return height
}
}
struct ParallaxHeaderView<Header: View, Content: View>: UIViewControllerRepresentable {
@Binding var update: Bool
let header: Header
let content: Content
init(update: Binding<Bool>, @ViewBuilder header: () -> Header, @ViewBuilder content: () -> Content) {
self._update = update
self.header = header()
self.content = content()
}
func makeUIViewController(context: Context) -> ParallaxViewController {
let vc = ParallaxViewController()
vc.childViewController = UIViewController.hostingController {
content
}
updateUIViewController(vc, context: context)
return vc
}
func updateUIViewController(_ vc: ParallaxViewController, context: Context) {
vc.headerViewController = ParallaxViewControllerHeader(header: UIViewController.hostingController {
header
})
if let height = (vc.headerViewController as? ParallaxViewControllerHeader)?.updateHeight(animated: true) {
vc.headerHeight = height
vc.updateHeaderHeight(animated: true)
}
}
}
struct ParallaxHeaderPreview: View {
@State var texts: [String] = [NSUUID().uuidString]
@State var update: Bool = false
var body: some View {
ParallaxHeaderView(update: $update) {
VStack {
Text("Header")
.font(.system(size: 17, weight: .bold))
.padding(.vertical)
ForEach(texts, id: \.self) {
Text($0)
.padding(.vertical)
}
HStack(spacing: 60) {
Button(action: {
if !texts.isEmpty {
texts.removeLast()
update.toggle()
}
}, label: {
Image(systemName: "minus")
.resizable()
.scaledToFit()
.frame(width: 30)
.frame(height: 30)
})
Button(action: {
texts.append(NSUUID().uuidString)
update.toggle()
}, label: {
Image(systemName: "plus")
.resizable()
.scaledToFit()
.frame(width: 30)
.frame(height: 30)
})
}
.padding()
}
.frame(maxWidth: .infinity)
.background(Color.blue.opacity(0.5))
} content: {
VStack(spacing: 0) {
HStack {
Text("Content")
.font(.system(size: 17, weight: .bold))
.padding(.vertical)
}
.frame(maxWidth: .infinity)
.frame(height: 44)
.background(Color.green.opacity(0.5))
List {
ForEach(0 ..< 100) {
Text("\($0)")
}
.listRowBackground(Color.green)
}
}
}
}
}
struct ParallaxHeaderView_Previews: PreviewProvider {
static var previews: some View {
ParallaxHeaderPreview()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment