Skip to content

Instantly share code, notes, and snippets.

@honkmaster
Last active July 8, 2022 05:47
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 honkmaster/2db22b088a84c696c8f0a74553de686b to your computer and use it in GitHub Desktop.
Save honkmaster/2db22b088a84c696c8f0a74553de686b to your computer and use it in GitHub Desktop.
import SwiftUI
enum ParallaxDirection {
case vertical
case horizontal
case both
}
private struct ParallaxMotionViewControllerRepresentable<Content: View>: UIViewControllerRepresentable {
var content: Content
var parallaxDirection: ParallaxDirection
var maxOffset: CGFloat
func makeUIViewController(context: Context) -> UIHostingController<Content> {
let hostingController = UIHostingController(rootView: content)
hostingController.view.backgroundColor = .clear
let horizontalEffect = UIInterpolatingMotionEffect(keyPath: "center.x", type: .tiltAlongHorizontalAxis)
horizontalEffect.minimumRelativeValue = maxOffset
horizontalEffect.maximumRelativeValue = -maxOffset
let verticalEffect = UIInterpolatingMotionEffect(keyPath: "center.y", type: .tiltAlongVerticalAxis)
verticalEffect.minimumRelativeValue = maxOffset
verticalEffect.maximumRelativeValue = -maxOffset
let motionEffectGroup = UIMotionEffectGroup()
switch parallaxDirection {
case .vertical:
motionEffectGroup.motionEffects = [verticalEffect]
case .horizontal:
motionEffectGroup.motionEffects = [horizontalEffect]
case .both:
motionEffectGroup.motionEffects = [horizontalEffect, verticalEffect]
}
hostingController.view.addMotionEffect(motionEffectGroup)
return hostingController
}
func updateUIViewController(_ pageViewController: UIHostingController<Content>, context: Context) { }
}
struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value _: inout CGSize, nextValue _: () -> CGSize) {}
}
struct GetSizeViewModifier: ViewModifier {
@Binding private var size: CGSize?
func body(content: Content) -> some View {
content
.background(
GeometryReader { geometry in
Color.clear.preference(key: SizePreferenceKey.self, value: geometry.size)
}
)
.onPreferenceChange(SizePreferenceKey.self) {
if self.size != $0 { self.size = $0 }
}
}
// MARK: Lifecycle
init(_ size: Binding<CGSize?>) {
_size = size
}
}
extension View {
@warn_unqualified_access
func getSize(_ size: Binding<CGSize?>) -> some View {
modifier(GetSizeViewModifier(size))
}
}
struct ParallaxMotionContainerView<Content: View>: View {
var content: Content
var parallaxDirection: ParallaxDirection
var maxOffset: CGFloat
@State private var size: CGSize?
var body: some View {
ParallaxMotionViewControllerRepresentable(
content: content.getSize($size),
parallaxDirection: .both,
maxOffset: maxOffset
)
.frame(width: size?.width, height: size?.height)
}
}
struct ParallaxMotionViewModifier: ViewModifier {
var parallaxDirection: ParallaxDirection
var maxOffset: CGFloat
func body(content: Content) -> some View {
ParallaxMotionContainerView(content: content, parallaxDirection: parallaxDirection, maxOffset: maxOffset)
}
}
extension View {
@warn_unqualified_access
func parallaxMotion(parallaxDirection: ParallaxDirection = .both, maxOffset: CGFloat) -> some View {
modifier(ParallaxMotionViewModifier(parallaxDirection: parallaxDirection, maxOffset: maxOffset))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment