Created
March 19, 2021 23:13
-
-
Save cathandnya/c055a0e6a24ae60f1c9d587e68e8bf33 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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